| 1 | package org.openstreetmap.gui.jmapviewer;
|
|---|
| 2 |
|
|---|
| 3 | // License: GPL. Copyright 2007 by Tim Haussmann
|
|---|
| 4 |
|
|---|
| 5 | /**
|
|---|
| 6 | * This class implements the Mercator Projection as it is used by Openstreetmap
|
|---|
| 7 | * (and google). It provides methods to translate coordinates from 'map space'
|
|---|
| 8 | * into latitude and longitude (on the WGS84 ellipsoid) and vice versa. Map
|
|---|
| 9 | * space is measured in pixels. The origin of the map space is the top left
|
|---|
| 10 | * corner. The map space origin (0,0) has latitude ~85 and longitude -180
|
|---|
| 11 | *
|
|---|
| 12 | * @author Tim Haussmann
|
|---|
| 13 | *
|
|---|
| 14 | */
|
|---|
| 15 |
|
|---|
| 16 | public class OsmMercator {
|
|---|
| 17 |
|
|---|
| 18 | private static int TILE_SIZE = 256;
|
|---|
| 19 | public static final double MAX_LAT = 85.05112877980659;
|
|---|
| 20 | public static final double MIN_LAT = -85.05112877980659;
|
|---|
| 21 | private static double EARTH_RADIUS_KM = 6371;
|
|---|
| 22 |
|
|---|
| 23 | private static double KMTOM=1000;
|
|---|
| 24 | private static double MTOKM=1/1000;
|
|---|
| 25 |
|
|---|
| 26 | public static double kmToMeters(double kmeters) {
|
|---|
| 27 | return kmeters*KMTOM;
|
|---|
| 28 | }
|
|---|
| 29 |
|
|---|
| 30 | public static double metersToKm(double meters) {
|
|---|
| 31 | return meters*MTOKM;
|
|---|
| 32 | }
|
|---|
| 33 |
|
|---|
| 34 | public static double radius(int aZoomlevel) {
|
|---|
| 35 | return (TILE_SIZE * (1 << aZoomlevel)) / (2.0 * Math.PI);
|
|---|
| 36 | }
|
|---|
| 37 |
|
|---|
| 38 | /**
|
|---|
| 39 | * Returns the absolut number of pixels in y or x, defined as: 2^Zoomlevel *
|
|---|
| 40 | * TILE_WIDTH where TILE_WIDTH is the width of a tile in pixels
|
|---|
| 41 | *
|
|---|
| 42 | * @param aZoomlevel
|
|---|
| 43 | * @return
|
|---|
| 44 | */
|
|---|
| 45 | public static int getMaxPixels(int aZoomlevel) {
|
|---|
| 46 | return TILE_SIZE * (1 << aZoomlevel);
|
|---|
| 47 | }
|
|---|
| 48 |
|
|---|
| 49 | public static int falseEasting(int aZoomlevel) {
|
|---|
| 50 | return getMaxPixels(aZoomlevel) / 2;
|
|---|
| 51 | }
|
|---|
| 52 |
|
|---|
| 53 | public static int falseNorthing(int aZoomlevel) {
|
|---|
| 54 | return (-1 * getMaxPixels(aZoomlevel) / 2);
|
|---|
| 55 | }
|
|---|
| 56 |
|
|---|
| 57 | /**
|
|---|
| 58 | * Transform pixelspace to coordinates and get the distance.
|
|---|
| 59 | *
|
|---|
| 60 | * @param x1 the first x coordinate
|
|---|
| 61 | * @param y1 the first y coordinate
|
|---|
| 62 | * @param x2 the second x coordinate
|
|---|
| 63 | * @param y2 the second y coordinate
|
|---|
| 64 | *
|
|---|
| 65 | * @param zoomLevel the zoom level
|
|---|
| 66 | * @return the distance
|
|---|
| 67 | * @author Jason Huntley
|
|---|
| 68 | */
|
|---|
| 69 | public static double getDistance(int x1, int y1, int x2, int y2, int zoomLevel) {
|
|---|
| 70 | double la1 = YToLat(y1, zoomLevel);
|
|---|
| 71 | double lo1 = XToLon(x1, zoomLevel);
|
|---|
| 72 | double la2 = YToLat(y2, zoomLevel);
|
|---|
| 73 | double lo2 = XToLon(x2, zoomLevel);
|
|---|
| 74 |
|
|---|
| 75 | return getDistance(la1, lo1, la2, lo2);
|
|---|
| 76 | }
|
|---|
| 77 |
|
|---|
| 78 | /**
|
|---|
| 79 | * Gets the distance using Spherical law of cosines.
|
|---|
| 80 | *
|
|---|
| 81 | * @param la1 the Latitude in degrees
|
|---|
| 82 | * @param lo1 the Longitude in degrees
|
|---|
| 83 | * @param la2 the Latitude from 2nd coordinate in degrees
|
|---|
| 84 | * @param lo2 the Longitude from 2nd coordinate in degrees
|
|---|
| 85 | * @return the distance
|
|---|
| 86 | * @author Jason Huntley
|
|---|
| 87 | */
|
|---|
| 88 | public static double getDistance(double la1, double lo1, double la2, double lo2) {
|
|---|
| 89 | double aStartLat = Math.toRadians(la1);
|
|---|
| 90 | double aStartLong = Math.toRadians(lo1);
|
|---|
| 91 | double aEndLat =Math.toRadians(la2);
|
|---|
| 92 | double aEndLong = Math.toRadians(lo2);
|
|---|
| 93 |
|
|---|
| 94 | double distance = Math.acos(Math.sin(aStartLat) * Math.sin(aEndLat)
|
|---|
| 95 | + Math.cos(aStartLat) * Math.cos(aEndLat)
|
|---|
| 96 | * Math.cos(aEndLong - aStartLong));
|
|---|
| 97 |
|
|---|
| 98 | return (EARTH_RADIUS_KM * distance);
|
|---|
| 99 | }
|
|---|
| 100 |
|
|---|
| 101 | /**
|
|---|
| 102 | * Transform longitude to pixelspace
|
|---|
| 103 | *
|
|---|
| 104 | * <p>
|
|---|
| 105 | * Mathematical optimization<br>
|
|---|
| 106 | * <code>
|
|---|
| 107 | * x = radius(aZoomlevel) * toRadians(aLongitude) + falseEasting(aZoomLevel)<br>
|
|---|
| 108 | * x = getMaxPixels(aZoomlevel) / (2 * PI) * (aLongitude * PI) / 180 + getMaxPixels(aZoomlevel) / 2<br>
|
|---|
| 109 | * x = getMaxPixels(aZoomlevel) * aLongitude / 360 + 180 * getMaxPixels(aZoomlevel) / 360<br>
|
|---|
| 110 | * x = getMaxPixels(aZoomlevel) * (aLongitude + 180) / 360<br>
|
|---|
| 111 | * </code>
|
|---|
| 112 | * </p>
|
|---|
| 113 | *
|
|---|
| 114 | * @param aLongitude
|
|---|
| 115 | * [-180..180]
|
|---|
| 116 | * @return [0..2^Zoomlevel*TILE_SIZE[
|
|---|
| 117 | * @author Jan Peter Stotz
|
|---|
| 118 | */
|
|---|
| 119 | public static int LonToX(double aLongitude, int aZoomlevel) {
|
|---|
| 120 | int mp = getMaxPixels(aZoomlevel);
|
|---|
| 121 | int x = (int) ((mp * (aLongitude + 180l)) / 360l);
|
|---|
| 122 | x = Math.min(x, mp - 1);
|
|---|
| 123 | return x;
|
|---|
| 124 | }
|
|---|
| 125 |
|
|---|
| 126 | /**
|
|---|
| 127 | * Transforms latitude to pixelspace
|
|---|
| 128 | * <p>
|
|---|
| 129 | * Mathematical optimization<br>
|
|---|
| 130 | * <code>
|
|---|
| 131 | * log(u) := log((1.0 + sin(toRadians(aLat))) / (1.0 - sin(toRadians(aLat))<br>
|
|---|
| 132 | *
|
|---|
| 133 | * y = -1 * (radius(aZoomlevel) / 2 * log(u)))) - falseNorthing(aZoomlevel))<br>
|
|---|
| 134 | * y = -1 * (getMaxPixel(aZoomlevel) / 2 * PI / 2 * log(u)) - -1 * getMaxPixel(aZoomLevel) / 2<br>
|
|---|
| 135 | * y = getMaxPixel(aZoomlevel) / (-4 * PI) * log(u)) + getMaxPixel(aZoomLevel) / 2<br>
|
|---|
| 136 | * y = getMaxPixel(aZoomlevel) * ((log(u) / (-4 * PI)) + 1/2)<br>
|
|---|
| 137 | * </code>
|
|---|
| 138 | * </p>
|
|---|
| 139 | * @param aLat
|
|---|
| 140 | * [-90...90]
|
|---|
| 141 | * @return [0..2^Zoomlevel*TILE_SIZE[
|
|---|
| 142 | * @author Jan Peter Stotz
|
|---|
| 143 | */
|
|---|
| 144 | public static int LatToY(double aLat, int aZoomlevel) {
|
|---|
| 145 | if (aLat < MIN_LAT)
|
|---|
| 146 | aLat = MIN_LAT;
|
|---|
| 147 | else if (aLat > MAX_LAT)
|
|---|
| 148 | aLat = MAX_LAT;
|
|---|
| 149 | double sinLat = Math.sin(Math.toRadians(aLat));
|
|---|
| 150 | double log = Math.log((1.0 + sinLat) / (1.0 - sinLat));
|
|---|
| 151 | int mp = getMaxPixels(aZoomlevel);
|
|---|
| 152 | int y = (int) (mp * (0.5 - (log / (4.0 * Math.PI))));
|
|---|
| 153 | y = Math.min(y, mp - 1);
|
|---|
| 154 | return y;
|
|---|
| 155 | }
|
|---|
| 156 |
|
|---|
| 157 | /**
|
|---|
| 158 | * Transforms pixel coordinate X to longitude
|
|---|
| 159 | *
|
|---|
| 160 | * <p>
|
|---|
| 161 | * Mathematical optimization<br>
|
|---|
| 162 | * <code>
|
|---|
| 163 | * lon = toDegree((aX - falseEasting(aZoomlevel)) / radius(aZoomlevel))<br>
|
|---|
| 164 | * lon = 180 / PI * ((aX - getMaxPixels(aZoomlevel) / 2) / getMaxPixels(aZoomlevel) / (2 * PI)<br>
|
|---|
| 165 | * lon = 180 * ((aX - getMaxPixels(aZoomlevel) / 2) / getMaxPixels(aZoomlevel))<br>
|
|---|
| 166 | * lon = 360 / getMaxPixels(aZoomlevel) * (aX - getMaxPixels(aZoomlevel) / 2)<br>
|
|---|
| 167 | * lon = 360 * aX / getMaxPixels(aZoomlevel) - 180<br>
|
|---|
| 168 | * </code>
|
|---|
| 169 | * </p>
|
|---|
| 170 | * @param aX
|
|---|
| 171 | * [0..2^Zoomlevel*TILE_WIDTH[
|
|---|
| 172 | * @return ]-180..180[
|
|---|
| 173 | * @author Jan Peter Stotz
|
|---|
| 174 | */
|
|---|
| 175 | public static double XToLon(int aX, int aZoomlevel) {
|
|---|
| 176 | return ((360d * aX) / getMaxPixels(aZoomlevel)) - 180.0;
|
|---|
| 177 | }
|
|---|
| 178 |
|
|---|
| 179 | /**
|
|---|
| 180 | * Transforms pixel coordinate Y to latitude
|
|---|
| 181 | *
|
|---|
| 182 | * @param aY
|
|---|
| 183 | * [0..2^Zoomlevel*TILE_WIDTH[
|
|---|
| 184 | * @return [MIN_LAT..MAX_LAT] is about [-85..85]
|
|---|
| 185 | */
|
|---|
| 186 | public static double YToLat(int aY, int aZoomlevel) {
|
|---|
| 187 | aY += falseNorthing(aZoomlevel);
|
|---|
| 188 | double latitude = (Math.PI / 2) - (2 * Math.atan(Math.exp(-1.0 * aY / radius(aZoomlevel))));
|
|---|
| 189 | return -1 * Math.toDegrees(latitude);
|
|---|
| 190 | }
|
|---|
| 191 |
|
|---|
| 192 | }
|
|---|