// **********************************************************************
//
// <copyright>
//
// BBN Technologies, a Verizon Company
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/proj/Cartesian.java,v $
// $RCSfile: Cartesian.java,v $
// $Revision: 1.4 $
// $Date: 2006/04/07 15:21:10 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.proj;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
/**
* The Cartesian projection is a non-wrapping, straight-forward scaling
* projection drawn in 2D. The simplest projection ever, it can be used for
* regular plotting.
*/
public class Cartesian extends Proj implements Projection, java.io.Serializable {
/**
* The Cartesian name.
*/
public final static transient String CartesianName = "Cartesian";
/**
* The coordinate limit of the left side of the projection. If the left side
* of the map projection would show coordinates more left than this value,
* the center of the map will be changed so that this value is on the edge.
*/
protected double leftLimit;
/**
* The coordinate limit of the right side of the projection. If the right
* side of the map projection would show coordinates more right than this
* value, the center of the map will be changed so that this value is on the
* edge.
*/
protected double rightLimit;
/**
* The coordinate limit of the top side of the projection. If the top side
* of the map projection would show coordinates higher than this value, the
* center of the map will be changed so that this value is on the edge.
*/
protected double topLimit;
/**
* The coordinate limit of the bottom side of the projection. If the bottom
* side of the map projection would show coordinates lower than this value,
* the center of the map will be changed so that this value is on the edge.
*/
protected double bottomLimit;
/**
* A point that can be used for force the projection against the limits. Is
* only used if the limits are set to be something other than infinity.
*/
protected Point2D limitAnchorPoint;
protected double scaleFactor;
protected transient double hWidth;
protected transient double hHeight;
protected transient double SFScale;
protected transient AffineTransform transform1;
protected transient AffineTransform transform2;
// protected transient AffineTransform transform3;
protected transient AffineTransform transform4;
/**
* Create a Cartesian projection that does straight scaling, no wrapping.
*
* @param center the coordinates of the center of the map.
* @param scale the scale to use for the map, referring to the difference of
* the ration between pixels versus coordinate values.
* @param width the pixel width of the map.
* @param height the pixel height of the map.
*/
public Cartesian(Point2D center, float scale, int width, int height) {
super(center, scale, width, height);
}
public void init() {
scaleFactor = 100000000;
// Limits are in coordinate space, dictating how much coordinate space
// can be viewable. The center point and scale should be adjusted as
// appropriate (during computeParameters).
leftLimit = Double.NEGATIVE_INFINITY;
rightLimit = Double.POSITIVE_INFINITY;
topLimit = Double.POSITIVE_INFINITY;
bottomLimit = Double.NEGATIVE_INFINITY;
}
protected void computeParameters() {
hWidth = width / 2.0;
hHeight = height / 2.0;
SFScale = scaleFactor / scale;
checkLimits();
transform1 = AffineTransform.getTranslateInstance(-centerX, -centerY);
transform2 = AffineTransform.getScaleInstance(SFScale, -SFScale);
// transform3 = AffineTransform.getScaleInstance(1 / SFScale,
// -1 / SFScale);
transform4 = AffineTransform.getTranslateInstance(hWidth, hHeight);
}
/**
* The method you want to call. Checks if there are limits set, and will
* call setLimits(checkScale) properly.
*/
protected void checkLimits() {
if (leftLimit != Double.NEGATIVE_INFINITY
|| rightLimit != Double.POSITIVE_INFINITY
|| topLimit != Double.POSITIVE_INFINITY
|| bottomLimit != Double.NEGATIVE_INFINITY) {
checkLimits(true);
}
}
/**
* Should only be called if you've checked there are limits set on the
* projection.
*
* @param checkScale true the first time through, if the scale changes this
* method calls itself with false to prevent a loop.
*/
protected void checkLimits(boolean checkScale) {
if (limitAnchorPoint != null) {
centerX = limitAnchorPoint.getX();
centerY = limitAnchorPoint.getY();
}
// Check limits.
Point2D p2 = checkUpperLimits();
if (p2 != null) {
centerX = p2.getX();
centerY = p2.getY();
}
Point2D p1 = checkLowerLimits();
if (p1 != null) {
centerX = p1.getX();
centerY = p1.getY();
}
if (checkScale && p1 != null && p2 != null) {
double newScale = checkScaleAgainstLimits();
if (newScale != scale) {
scale = newScale;
SFScale = scaleFactor / scale;
checkLimits(false);
}
}
}
/**
* Checks the lower limits and returns new center coordinates to keep the
* limits at the edge if necessary.
*/
protected Point2D checkLowerLimits() {
Point2D p1 = new Point2D.Double();
Point2D p2 = null;
double moveX = 0;
double moveY = 0;
inverse(0, height, p1);// LL corner
if (p1.getX() < leftLimit) {
p2 = forward(centerY, leftLimit, p2);
moveX = p2.getX();
}
if (p1.getY() < bottomLimit) {
p2 = forward(bottomLimit, centerX, p2);
moveY = height - p2.getY();
}
if (p2 != null) {
inverse(hWidth + moveX, hHeight - moveY, p2);
}
return p2;
}
/**
* Checks the upper limits and returns new center coordinates to keep the
* limits at the edge if necessary.
*/
protected Point2D checkUpperLimits() {
Point2D p1 = new Point2D.Double();
Point2D p2 = null;
double moveX = 0;
double moveY = 0;
inverse(width, 0, p1);// UR corner
if (p1.getX() > rightLimit) {
p2 = forward(centerY, rightLimit, p2);
moveX = width - p2.getX();
}
if (p1.getY() > topLimit) {
p2 = forward(topLimit, centerX, p2);
moveY = p2.getY();
}
if (p2 != null) {
inverse(hWidth - moveX, hHeight + moveY, p2);
}
return p2;
}
/**
* Checks the corner values against the limits and returns the right scale
* to keep limits at the edge if necessary.
*/
protected double checkScaleAgainstLimits() {
Point2D p1 = getUpperLeft();
Point2D p2 = getLowerRight();
double x = p1.getX();
double y = p1.getY();
if (topLimit != Double.POSITIVE_INFINITY) {
y = topLimit;
}
if (leftLimit != Double.NEGATIVE_INFINITY) {
x = leftLimit;
}
p1.setLocation(x, y);
x = p2.getX();
y = p2.getY();
if (bottomLimit != Double.NEGATIVE_INFINITY) {
y = bottomLimit;
}
if (rightLimit != Double.POSITIVE_INFINITY) {
x = rightLimit;
}
p2.setLocation(x, y);
// second p1, p2 meaningless
return getScale(p1, p2, p1, p2);
}
/**
* Forward project a world coordinate into screen space.
*
* @param wy vertical coordinate component in world units.
* @param wx horizontal coordinate component in world units.
* @param mapPoint screen point to load result into. OK if null, a new one
* will be created and returned.
* @return Point2D provided or new one created containing map coordinate.
*/
public Point2D forward(double wy, double wx, Point2D mapPoint) {
double x = ((wx - centerX) * SFScale) + hWidth;
double y = hHeight - ((wy - centerY) * SFScale);
if (mapPoint == null) {
mapPoint = new Point2D.Double(x, y);
} else {
mapPoint.setLocation(x, y);
}
/*
* fPoint1.setLocation(wx, wy); Point2D tmp =
* transform1.transform(fPoint1, fPoint2); tmp =
* transform2.transform(tmp, fPoint1); tmp = transform4.transform(tmp,
* fPoint2); mapPoint.setLocation(tmp.getX(), tmp.getY());
*/
return mapPoint;
}
// Used for AffineTransform forward and inverse methods, to save
// allocation expense.
// Point2D fPoint1 = new Point2D.Double();
// Point2D fPoint2 = new Point2D.Double();
// Point2D iPoint1 = new Point2D.Double();
// Point2D iPoint2 = new Point2D.Double();
/**
* Inverse projection a map coordinate into world space.
*
* @param x horizontal map coordinate from left side of map.
* @param y vertical map coordinate from top of map.
* @param worldPoint a Point2D object to load result into. OK if null, a new
* one will be created if necessary.
* @return Point2D provided or new one if created, containing the result.
*/
public Point2D inverse(double x, double y, Point2D worldPoint) {
double worldPointX = (x - hWidth) / SFScale + centerX;
double worldPointY = (hHeight - y) / SFScale + centerY;
if (worldPoint == null) {
worldPoint = new Point2D.Double(worldPointX, worldPointY);
} else {
worldPoint.setLocation(worldPointX, worldPointY);
}
/*
* try { iPoint1.setLocation(x, y); Point2D tmp =
* transform4.inverseTransform(iPoint1, iPoint2); tmp =
* transform3.transform(tmp, iPoint1); transform1.inverseTransform(tmp,
* worldPoint); } catch (NoninvertibleTransformException e) {
* e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); }
*/
return worldPoint;
}
/**
* @param Az direction, 0 is north, positive is clockwise.
* @param c number of world coordinates to pan.
*/
public void pan(double Az, double c) {
double currentX = centerX;
double currentY = centerY;
currentX -= c * Math.sin(Math.toRadians(Az) + Math.PI);
currentY -= c * Math.cos(Math.toRadians(Az) + Math.PI);
setCenter(new Point2D.Double(currentX, currentY));
}
/**
* Pan half a view.
*/
public void pan(double Az) {
pan(Az, (getUpperLeft().distance(getLowerRight()) / 4.0));
}
/**
* Takes a java.awt.Shape object and re-projects it for a the current view.
* Returns a GeneralPath.
*/
// public Shape forwardShape(Shape shape) {
// return super.forwardShape(shape);
// // return
// // transform4.createTransformedShape(transform2.createTransformedShape(transform1.createTransformedShape(shape)));
//
// // Set Proj.java for the iterator way of doing this.
// }
/**
*/
public String getName() {
return CartesianName;
}
/**
*/
public float getScale(Point2D ulWorldPoint, Point2D lrWorldPoint,
Point2D point1, Point2D point2) {
try {
double worldCoords;
double deltaPix;
double dx = Math.abs(lrWorldPoint.getX() - ulWorldPoint.getX());
double dy = Math.abs(lrWorldPoint.getY() - ulWorldPoint.getY());
if (dx <= dy) {
worldCoords = dx;
deltaPix = Math.abs(point2.getX() - point1.getX());
} else {
worldCoords = dy;
deltaPix = Math.abs(point2.getY() - point1.getY());
}
// The new scale...
return (float) (worldCoords / deltaPix * scaleFactor);
} catch (NullPointerException npe) {
com.bbn.openmap.util.Debug.error("CartesianProjection.getScale(): caught null pointer exception.");
return Float.MAX_VALUE;
}
}
public boolean isPlotable(double lat, double lon) {
return true;
}
public Point2D getCenter() {
return new Point2D.Double(centerX, centerY);
}
public double getBottomLimit() {
return bottomLimit;
}
public void setBottomLimit(double bottomLimit) {
this.bottomLimit = bottomLimit;
computeParameters();
}
public double getLeftLimit() {
return leftLimit;
}
public void setLeftLimit(double leftLimit) {
this.leftLimit = leftLimit;
computeParameters();
}
public Point2D getLimitAnchorPoint() {
return limitAnchorPoint;
}
public void setLimitAnchorPoint(Point2D limitAnchorPoint) {
this.limitAnchorPoint = limitAnchorPoint;
computeParameters();
}
public double getRightLimit() {
return rightLimit;
}
public void setRightLimit(double rightLimit) {
this.rightLimit = rightLimit;
computeParameters();
}
public double getTopLimit() {
return topLimit;
}
public void setTopLimit(double topLimit) {
this.topLimit = topLimit;
computeParameters();
}
public void setLimits(double top, double bottom, double left, double right,
Point2D anchor) {
this.topLimit = top;
this.bottomLimit = bottom;
this.leftLimit = left;
this.rightLimit = right;
this.limitAnchorPoint = anchor;
computeParameters();
}
}