/******************************************************************************* * Copyright (c) MOBAC developers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package mobac.mapsources.mapspace; import java.awt.Point; import java.awt.geom.Point2D; import mobac.gui.mapview.PreviewMap; import mobac.program.interfaces.MapSpace; /** * Mercator projection with a world width and height of 256 * 2<sup>zoom</sup> pixel. This is the common projection used * by OpenStreetMap and Google. It provides methods to translate coordinates from 'map space' into latitude and * longitude (on the WGS84 ellipsoid) and vice versa. Map space is measured in pixels. The origin of the map space is * the top left corner. The map space origin (0,0) has latitude ~85 and longitude -180 * * <p> * This is the only implementation that is currently supported by Mobile Atlas Creator. * </p> * <p> * DO NOT TRY TO IMPLEMENT YOUR OWN. IT WILL MOST LIKELY NOT WORK! * </p> * * @see MapSpace */ public class MercatorPower2MapSpace implements MapSpace { public static final double MAX_LAT = 85.05112877980659; public static final double MIN_LAT = -85.05112877980659; protected final int tileSize; /** * Pre-computed values for the world size (height respectively width) in the different zoom levels. */ protected final int[] worldSize; public static final MapSpace INSTANCE_256 = new MercatorPower2MapSpace(256); protected MercatorPower2MapSpace(int tileSize) { this.tileSize = tileSize; worldSize = new int[PreviewMap.MAX_ZOOM + 1]; for (int zoom = 0; zoom < worldSize.length; zoom++) worldSize[zoom] = 256 * (1 << zoom); //����ֱ�����tileSize�޹� } protected double radius(int zoom) { return getMaxPixels(zoom) / (2.0 * Math.PI); } public ProjectionCategory getProjectionCategory() { return ProjectionCategory.SPHERE; } /** * Returns the absolute number of pixels in y or x, defined as: 2<sup>zoom</sup> * <code>tileSize</code> * * @param zoom * [0..22] (for tileSize = 256) * @return */ public int getMaxPixels(int zoom) { return worldSize[zoom]; } protected int falseNorthing(int aZoomlevel) { return (-1 * getMaxPixels(aZoomlevel) / 2); } /** * Transforms latitude to pixelspace * * @param lat * [-90...90] * @param zoom * [0..22] (for tileSize = 256) * @return [0..2^zoom*tileSize[ * @author Jan Peter Stotz */ public int cLatToY(double lat, int zoom) { lat = Math.max(MIN_LAT, Math.min(MAX_LAT, lat)); double sinLat = Math.sin(Math.toRadians(lat)); double log = Math.log((1.0 + sinLat) / (1.0 - sinLat)); int mp = getMaxPixels(zoom); int y = (int) (mp * (0.5 - (log / (4.0 * Math.PI)))); y = Math.min(y, mp - 1); return y; } /** * Transform longitude to pixelspace * * @param lon * [-180..180] * @param zoom * [0..22] (for tileSize = 256) * @return [0..2^zoom*TILE_SIZE[ * @author Jan Peter Stotz */ public int cLonToX(double lon, int zoom) { int mp = getMaxPixels(zoom); int x = (int) ((mp * (lon + 180l)) / 360l); x = Math.min(x, mp - 1); return x; } /** * Transforms pixel coordinate X to longitude * * @param x * [0..2^zoom * tileSize[ * @param zoom * [0..22] * @return ]-180..180[ * @author Jan Peter Stotz */ public double cXToLon(int x, int zoom) { return ((360d * x) / getMaxPixels(zoom)) - 180.0; } /** * Transforms pixel coordinate Y to latitude * * @param y * [0..2^zoom * tileSize[ * @param zoom * [0..22] * @return [MIN_LAT..MAX_LAT] is about [-85..85] */ public double cYToLat(int y, int zoom) { y += falseNorthing(zoom); double latitude = (Math.PI / 2) - (2 * Math.atan(Math.exp(-1.0 * y / radius(zoom)))); return -1 * Math.toDegrees(latitude); } public int getTileSize() { return tileSize; } public int moveOnLatitude(int startX, int y, int zoom, double angularDist) { y += falseNorthing(zoom); double lat = -1 * ((Math.PI / 2) - (2 * Math.atan(Math.exp(-1.0 * y / radius(zoom))))); double lon = cXToLon(startX, zoom); double sinLat = Math.sin(lat); lon += Math .toDegrees(Math.atan2(Math.sin(angularDist) * Math.cos(lat), Math.cos(angularDist) - sinLat * sinLat)); int newX = cLonToX(lon, zoom); int w = newX - startX; return w; } public double horizontalDistance(int zoom, int y, int xDist) { y = Math.max(y, 0); y = Math.min(y, getMaxPixels(zoom)); double lat = cYToLat(y, zoom); double lon1 = -180.0; double lon2 = cXToLon(xDist, zoom); double dLon = Math.toRadians(lon2 - lon1); double cos_lat = Math.cos(Math.toRadians(lat)); double sin_dLon_2 = Math.sin(dLon) / 2; double a = cos_lat * cos_lat * sin_dLon_2 * sin_dLon_2; return 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } public int xChangeZoom(int x, int oldZoom, int newZoom) { int zoomDiff = oldZoom - newZoom; return (zoomDiff > 0) ? x >> zoomDiff : x << -zoomDiff; } public int yChangeZoom(int y, int oldZoom, int newZoom) { int zoomDiff = oldZoom - newZoom; return (zoomDiff > 0) ? y >> zoomDiff : y << -zoomDiff; } public Point changeZoom(Point pixelCoordinate, int oldZoom, int newZoom) { int x = xChangeZoom(pixelCoordinate.x, oldZoom, newZoom); int y = yChangeZoom(pixelCoordinate.y, oldZoom, newZoom); return new Point(x, y); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + tileSize; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MercatorPower2MapSpace other = (MercatorPower2MapSpace) obj; if (tileSize != other.tileSize) return false; return true; } public Point cLonLatToXY(double lon, double lat, int zoom) { Point p = new Point(); p.x = cLonToX(lon, zoom); p.y = cLatToY(lat, zoom); return p; } public Point2D.Double cXYToLonLat(int x, int y, int zoom) { Point2D.Double p = new Point2D.Double(); p.x = cXToLon(x, zoom); p.y = cYToLat(y, zoom); return p; } @Override public MapSpaceType getMapSpaceType() { return MapSpaceType.msMercatorSpherical; } }