package org.insa.graphics.drawing; import java.awt.Dimension; import org.insa.graph.GraphStatistics.BoundingBox; public class MercatorProjection { public static final double MAX_LATITUDE = 82; public static final double MIN_LATITUDE = -MAX_LATITUDE; // From Wikipedia... for the above max/min latitude. private static final double IMAGE_WIDTH = 2058, IMAGE_HEIGHT = 1746; private static final double MAX_LATITUDE_PROJ = projectY(MAX_LATITUDE); private static final double MIN_LATITUDE_PROJ = projectY(MIN_LATITUDE); // Bounding box private final float minLatitude, minLongitude, maxLatitude, maxLongitude; // Projection of min and max latitude. private final double minLatitudeProj, maxLatitudeProj; // Dimension of the image private final double width, height; /** * Create a new MercatorProjection corresponding to the given BoundingBox and * maxSize. * * @param boundingBox Box for this projection. * @param maxSize Maximum size of any side (width / height) of the image to * which this projection should draw. */ public MercatorProjection(BoundingBox boundingBox, int maxSize) { // Find minimum/maximum longitude and latitude. this.minLongitude = boundingBox.getTopLeftPoint().getLongitude(); this.maxLongitude = boundingBox.getBottomRightPoint().getLongitude(); this.minLatitude = boundingBox.getBottomRightPoint().getLatitude(); this.maxLatitude = boundingBox.getTopLeftPoint().getLatitude(); // Compute projection this.minLatitudeProj = projectY(this.minLatitude); this.maxLatitudeProj = projectY(this.maxLatitude); Dimension imageDimension = computeImageSize(maxSize); this.width = imageDimension.getWidth(); this.height = imageDimension.getHeight(); } /** * @return Image width for this projection to work properly. */ public double getImageWidth() { return this.width; } /** * @return Image weight for this projection to work properly. */ public double getImageHeight() { return this.height; } /** * Compute the projection (without scaling) of the given latitude. * * @param latitude Latitude to project. * * @return Projection of the given latitude (without scaling). */ private static double projectY(double latitude) { double sinLatitude = Math.sin(latitude * Math.PI / 180.0); return Math.log((1 + sinLatitude) / (1 - sinLatitude)) / 2; } /** * Compute the dimension required for drawing a projection of the given box on * an image, ensuring that none of the side of image is greater than maxSize. * * @param maxSize Maximum side of any side of the image. * * @return Dimension corresponding to the preferred size for the image. */ protected Dimension computeImageSize(int maxSize) { double propWidth = (maxLongitude - minLongitude) * IMAGE_WIDTH / 360.0; double propHeight = (this.maxLatitudeProj - this.minLatitudeProj) / (MAX_LATITUDE_PROJ - MIN_LATITUDE_PROJ) * IMAGE_HEIGHT; return propWidth < propHeight ? new Dimension((int) (maxSize * propWidth / propHeight), maxSize) : new Dimension(maxSize, (int) (maxSize * propHeight / propWidth)); } /** * Project the given latitude on the image. * * @param latitude Latitude to project. * * @return Projected position of the latitude on the image. */ public int latitudeToPixelY(float latitude) { return (int) ((this.maxLatitudeProj - projectY(latitude)) / (this.maxLatitudeProj - this.minLatitudeProj) * this.height); } /** * Project the given longitude on the image. * * @param longitude Longitude to project. * * @return Projected position of the longitude on the image. */ public int longitudeToPixelX(float longitude) { return (int) (width * (longitude - minLongitude) / (maxLongitude - minLongitude)); } /** * Retrieve the latitude associated to the given projected point. * * @param py Projected y-position for which latitude should be retrieved. * * @return The original latitude of the point. */ public float pixelYToLatitude(double py) { float y = (float) (this.maxLatitudeProj - (py / this.height) * (this.maxLatitudeProj - this.minLatitudeProj)); return (float) (180 * (2 * Math.atan(Math.exp(y)) - Math.PI / 2) / Math.PI); } /** * Retrieve the longitude associated to the given projected point. * * @param px Projected x-position for which longitude should be retrieved. * * @return The original longitude of the point. */ public float pixelXToLongitude(double px) { return (float) ((px / this.width) * (this.maxLongitude - this.minLongitude) + this.minLongitude); } }