/* * Copyright 2010, 2011, 2012 mapsforge.org * * This program is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation, either version 3 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package org.mapsforge.core.util; import org.mapsforge.core.model.Tile; /** * An implementation of the spherical Mercator projection. */ public final class MercatorProjection { /** * The circumference of the earth at the equator in meters. */ public static final double EARTH_CIRCUMFERENCE = 40075016.686; /** * Maximum possible latitude coordinate of the map. */ public static final double LATITUDE_MAX = 85.05112877980659; /** * Minimum possible latitude coordinate of the map. */ public static final double LATITUDE_MIN = -LATITUDE_MAX; /** * Calculates the distance on the ground that is represented by a single pixel on the map. * * @param latitude * the latitude coordinate at which the resolution should be calculated. * @param zoomLevel * the zoom level at which the resolution should be calculated. * @return the ground resolution at the given latitude and zoom level. */ public static double calculateGroundResolution(double latitude, byte zoomLevel) { long mapSize = getMapSize(zoomLevel); return Math.cos(latitude * (Math.PI / 180)) * EARTH_CIRCUMFERENCE / mapSize; } /** * Computes the amount of latitude degrees for a given distance in pixel at a given zoom level. * * @param deltaPixel * the delta in pixel * @param lat * the latitude * @param zoom * the zoom level * @return the delta in degrees */ public static double deltaLat(double deltaPixel, double lat, byte zoom) { double pixelY = latitudeToPixelY(lat, zoom); double lat2 = pixelYToLatitude(pixelY + deltaPixel, zoom); return Math.abs(lat2 - lat); } /** * @param zoomLevel * the zoom level for which the size of the world map should be returned. * @return the horizontal and vertical size of the map in pixel at the given zoom level. * @throws IllegalArgumentException * if the given zoom level is negative. */ public static long getMapSize(byte zoomLevel) { if (zoomLevel < 0) { throw new IllegalArgumentException("zoom level must not be negative: " + zoomLevel); } long mapSize = ((long) Tile.TILE_SIZE) << zoomLevel; return mapSize; } /** * Converts a latitude coordinate (in degrees) to a pixel Y coordinate at a certain zoom level. * * @param latitude * the latitude coordinate that should be converted. * @param zoomLevel * the zoom level at which the coordinate should be converted. * @return the pixel Y coordinate of the latitude value. */ public static double latitudeToPixelY(double latitude, byte zoomLevel) { double sinLatitude = Math.sin(latitude * (Math.PI / 180)); long mapSize = getMapSize(zoomLevel); // FIXME improve this formula so that it works correctly without the clipping double pixelY = (0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI)) * mapSize; return Math.min(Math.max(0, pixelY), mapSize); } /** * Converts a latitude coordinate (in degrees) to a tile Y number at a certain zoom level. * * @param latitude * the latitude coordinate that should be converted. * @param zoomLevel * the zoom level at which the coordinate should be converted. * @return the tile Y number of the latitude value. */ public static long latitudeToTileY(double latitude, byte zoomLevel) { return pixelYToTileY(latitudeToPixelY(latitude, zoomLevel), zoomLevel); } /** * Converts a longitude coordinate (in degrees) to a pixel X coordinate at a certain zoom level. * * @param longitude * the longitude coordinate that should be converted. * @param zoomLevel * the zoom level at which the coordinate should be converted. * @return the pixel X coordinate of the longitude value. */ public static double longitudeToPixelX(double longitude, byte zoomLevel) { long mapSize = getMapSize(zoomLevel); return (longitude + 180) / 360 * mapSize; } /** * Converts a longitude coordinate (in degrees) to the tile X number at a certain zoom level. * * @param longitude * the longitude coordinate that should be converted. * @param zoomLevel * the zoom level at which the coordinate should be converted. * @return the tile X number of the longitude value. */ public static long longitudeToTileX(double longitude, byte zoomLevel) { return pixelXToTileX(longitudeToPixelX(longitude, zoomLevel), zoomLevel); } /** * Converts a pixel X coordinate at a certain zoom level to a longitude coordinate. * * @param pixelX * the pixel X coordinate that should be converted. * @param zoomLevel * the zoom level at which the coordinate should be converted. * @return the longitude value of the pixel X coordinate. * @throws IllegalArgumentException * if the given pixelX coordinate is invalid. */ public static double pixelXToLongitude(double pixelX, byte zoomLevel) { long mapSize = getMapSize(zoomLevel); if (pixelX < 0 || pixelX > mapSize) { throw new IllegalArgumentException("invalid pixelX coordinate at zoom level " + zoomLevel + ": " + pixelX); } return 360 * ((pixelX / mapSize) - 0.5); } /** * Converts a pixel X coordinate to the tile X number. * * @param pixelX * the pixel X coordinate that should be converted. * @param zoomLevel * the zoom level at which the coordinate should be converted. * @return the tile X number. */ public static long pixelXToTileX(double pixelX, byte zoomLevel) { return (long) Math.min(Math.max(pixelX / Tile.TILE_SIZE, 0), Math.pow(2, zoomLevel) - 1); } /** * Converts a pixel Y coordinate at a certain zoom level to a latitude coordinate. * * @param pixelY * the pixel Y coordinate that should be converted. * @param zoomLevel * the zoom level at which the coordinate should be converted. * @return the latitude value of the pixel Y coordinate. * @throws IllegalArgumentException * if the given pixelY coordinate is invalid. */ public static double pixelYToLatitude(double pixelY, byte zoomLevel) { long mapSize = getMapSize(zoomLevel); if (pixelY < 0 || pixelY > mapSize) { throw new IllegalArgumentException("invalid pixelY coordinate at zoom level " + zoomLevel + ": " + pixelY); } double y = 0.5 - (pixelY / mapSize); return 90 - 360 * Math.atan(Math.exp(-y * (2 * Math.PI))) / Math.PI; } /** * Converts a pixel Y coordinate to the tile Y number. * * @param pixelY * the pixel Y coordinate that should be converted. * @param zoomLevel * the zoom level at which the coordinate should be converted. * @return the tile Y number. */ public static long pixelYToTileY(double pixelY, byte zoomLevel) { return (long) Math.min(Math.max(pixelY / Tile.TILE_SIZE, 0), Math.pow(2, zoomLevel) - 1); } /** * Converts a tile X number at a certain zoom level to a longitude coordinate. * * @param tileX * the tile X number that should be converted. * @param zoomLevel * the zoom level at which the number should be converted. * @return the longitude value of the tile X number. */ public static double tileXToLongitude(long tileX, byte zoomLevel) { return pixelXToLongitude(tileX * Tile.TILE_SIZE, zoomLevel); } /** * Converts a tile Y number at a certain zoom level to a latitude coordinate. * * @param tileY * the tile Y number that should be converted. * @param zoomLevel * the zoom level at which the number should be converted. * @return the latitude value of the tile Y number. */ public static double tileYToLatitude(long tileY, byte zoomLevel) { return pixelYToLatitude(tileY * Tile.TILE_SIZE, zoomLevel); } private MercatorProjection() { throw new IllegalStateException(); } }