package aimax.osm.viewer;
import aimax.osm.data.BoundingBox;
import aimax.osm.data.Position;
/**
* Class which is responsible for transformations between world
* and view coordinates. It implements an equirectangular projection.
* World coordinates are specified in latitude and longitude, view coordinates
* in pixel coordinates (x and y). The actual screen resolution is maintained
* for scale computation. Additionally, a unit-per-inch factor is maintained
* for transforming logical symbol size into pixels.
* @author Ruediger Lunde
*
*/
public class CoordTransformer {
/** Longitude of left upper corner. */
private float originLon;
/** Latitude of left upper corner. */
private float originLat;
/** Correction factor for longitude values. */
private float lonCorr;
/** Number of pixels corresponding to one degree of latitude. */
private float dotsPerDeg;
/**
* Factor for scaling symbols (widths of lines, size of icons etc.).
* We use logical units for defining the size of different kinds of symbols
* and compute the actual number of pixels for a symbol by multiplying
* the symbol's logical size with <code>dotsPerInch/unitsPerInch</code>.
* Default value is 92. This means, that a symbol with a specified size
* of 92 units should have the size of one inch on the screen.
*/
public static float unitsPerInch = 92f;
/**
* Number of pixels per inch on the screen (100 per default). The value
* maintains the actual screen resolution (true size of a pixel) and is
* used for scale computation. Default value is {@link #unitsPerInch}.
*/
private float dotsPerInch = unitsPerInch;
/** Sets the screen resolution (used for scale computation). */
public void setScreenResolution(int dotsPerInch) {
this.dotsPerInch = dotsPerInch;
}
/**
* Adjusts the transformation with respect to a given world coordinate
* bounding box.
*/
public void adjustTransformation(BoundingBox bb, int viewWidth, int viewHeight) {
if (bb != null) {
lonCorr = (float) Math.cos((bb.getLatMax() + bb.getLatMin()) / 360.0 * Math.PI);
// adjust coordinates relative to the left upper corner of the graph area
float scaleX = 1f;
float scaleY = 1f;
if (bb.getLonMax() > bb.getLonMin())
scaleX = viewWidth / ((bb.getLonMax()-bb.getLonMin()) * lonCorr);
if (bb.getLatMax() > bb.getLatMin())
scaleY = viewHeight / (bb.getLatMax()-bb.getLatMin());
dotsPerDeg = Math.max(scaleX, scaleY);
originLon = bb.getLonMin();
originLat = bb.getLatMax();
originLon += (bb.getLonMax() - lon(viewWidth)) / 2.0;
originLat += (bb.getLatMin() - lat(viewHeight)) / 2.0;
} else {
lonCorr = 1.0f;
dotsPerDeg = 100;
originLon = 0;
originLat = 0;
}
}
/**
* Multiples the current scale with the specified factor and
* adjusts the view so that the objects shown at the
* specified view focus keep at their position.
*/
public void zoom(float factor, int focusX, int focusY) {
float focusLon = lon(focusX);
float focusLat = lat(focusY);
dotsPerDeg *= factor;
int focusXNew = x(focusLon);
int focusYNew = y(focusLat);
adjust(focusX-focusXNew, focusY-focusYNew);
}
/**
* Adjusts the transformation.
* @param dx Number of pixels for horizontal shift.
* @param dy Number of pixels for vertical shift.
*/
public void adjust(double dx, double dy) {
this.originLon -= dx / (dotsPerDeg * lonCorr);
this.originLat += dy / dotsPerDeg;
}
/** Returns the x_view of a given longitude value in world coordinates. */
public int x(double lon) { return (int) Math.round(dotsPerDeg * (lon - originLon) * lonCorr); }
/** Returns the y_view of a given latitude value in world coordinates. */
public int y(double lat) { return (int) Math.round(dotsPerDeg * (originLat - lat)); }
/** Computes the corresponding longitude for a given view x coordinate. */
public float lon(int x) {
return x / (dotsPerDeg * lonCorr) + originLon;
}
/** Computes the corresponding latitude for a given view y coordinate. */
public float lat(int y) {
return originLat - y / dotsPerDeg;
}
/** Returns the current {@link #dotsPerDeg} value. */
public float getDotsPerDeg() {
return dotsPerDeg;
}
/**
* Returns the scale. 1 / 100 000 means one cm on the screen corresponds to
* 1 km in real world. */
public float computeScale() {
//System.out.println(dotsPerInch + " ... " + Toolkit.getDefaultToolkit().getScreenResolution());
//System.out.println(Toolkit.getDefaultToolkit().getScreenSize().getWidth());
double kmPerInch = 0.0254e-3;
double kmPerDeg = Position.EARTH_RADIUS * Math.toRadians(1.0);
return (float) (dotsPerDeg / dotsPerInch * kmPerInch / kmPerDeg);
}
/** Returns a factor for converting logical symbol size units into screen dots. */
public float getDotsPerUnit() {
return dotsPerInch / unitsPerInch;
}
}