org.jgrasstools.gears.modules.r.tmsgenerator.GlobalMercator Maven / Gradle / Ivy
The newest version!
package org.jgrasstools.gears.modules.r.tmsgenerator;
import java.util.Arrays;
/**
* Copyright (C) 2009, 2010
* State of California,
* Department of Water Resources.
* This file is part of DSM2 Grid Map
* The DSM2 Grid Map 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 3 of the License, or
* (at your option) any later version.
* DSM2 Grid Map 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. [http://www.gnu.org/licenses]
*
* @author Nicky Sandhu
*
*/
/**
* TMS Global Mercator OmsProfile ---------------------------
*
* Functions necessary for generation of tiles in Spherical Mercator projection,
* EPSG:900913 (EPSG:gOOglE, Google Maps Global Mercator), EPSG:3785,
* OSGEO:41001.
*
* Such tiles are compatible with Google Maps, Microsoft Virtual Earth, Yahoo
* Maps, UK Ordnance Survey OpenSpace API, ... and you can overlay them on top
* of base maps of those web mapping applications.
*
* Pixel and tile coordinates are in TMS notation (origin [0,0] in bottom-left).
*
* What coordinate conversions do we need for TMS Global Mercator tiles::
*
* LatLon <-> Meters <-> Pixels <-> Tile
*
* WGS84 coordinates Spherical Mercator Pixels in pyramid Tiles in pyramid
* lat/lon XY in metres XY pixels Z zoom XYZ from TMS EPSG:4326 EPSG:900913
* .----. --------- -- TMS / \ <-> | | <-> /----/ <-> Google \ / | | /--------/
* QuadTree ----- --------- /------------/ KML, public WebMapService Web Clients
* TileMapService
*
* What is the coordinate extent of Earth in EPSG:900913?
*
* [-20037508.342789244, -20037508.342789244, 20037508.342789244,
* 20037508.342789244] Constant 20037508.342789244 comes from the circumference
* of the Earth in meters, which is 40 thousand kilometers, the coordinate
* origin is in the middle of extent. In fact you can calculate the constant as:
* 2 * Math.PI * 6378137 / 2.0 $ echo 180 85 | gdaltransform -s_srs EPSG:4326
* -t_srs EPSG:900913 Polar areas with abs(latitude) bigger then 85.05112878 are
* clipped off.
*
* What are zoom level constants (pixels/meter) for pyramid with EPSG:900913?
*
* whole region is on top of pyramid (zoom=0) covered by 256x256 pixels tile,
* every lower zoom level resolution is always divided by two initialResolution
* = 20037508.342789244 * 2 / 256 = 156543.03392804062
*
* What is the difference between TMS and Google Maps/QuadTree tile name
* convention?
*
* The tile raster itself is the same (equal extent, projection, pixel size),
* there is just different identification of the same raster tile. Tiles in TMS
* are counted from [0,0] in the bottom-left corner, id is XYZ. Google placed
* the origin [0,0] to the top-left corner, reference is XYZ. Microsoft is
* referencing tiles by a QuadTree name, defined on the website:
* http://msdn2.microsoft.com/en-us/library/bb259689.aspx
*
* The lat/lon coordinates are using WGS84 datum, yeh?
*
* Yes, all lat/lon we are mentioning should use WGS84 Geodetic Datum. Well, the
* web clients like Google Maps are projecting those coordinates by Spherical
* Mercator, so in fact lat/lon coordinates on sphere are treated as if the were
* on the WGS84 ellipsoid.
*
* From MSDN documentation: To simplify the calculations, we use the spherical
* form of projection, not the ellipsoidal form. Since the projection is used
* only for map display, and not for displaying numeric coordinates, we don't
* need the extra precision of an ellipsoidal projection. The spherical
* projection causes approximately 0.33 percent scale distortion in the Y
* direction, which is not visually noticable.
*
* How do I create a raster in EPSG:900913 and convert coordinates with PROJ.4?
*
* You can use standard GIS tools like gdalwarp, cs2cs or gdaltransform. All of
* the tools supports -t_srs 'epsg:900913'.
*
* For other GIS programs check the exact definition of the projection: More
* info at http://spatialreference.org/ref/user/google-projection/ The same
* projection is degined as EPSG:3785. WKT definition is in the official EPSG
* database.
*
* Proj4 Text: +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0
* +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs
*
* Human readable WKT format of EPGS:900913:
* PROJCS["Google Maps Global Mercator", GEOGCS["WGS 84", DATUM["WGS_1984",
* SPHEROID["WGS 84",6378137,298.2572235630016, AUTHORITY["EPSG","7030"]],
* AUTHORITY["EPSG","6326"]], PRIMEM["Greenwich",0],
* UNIT["degree",0.0174532925199433], AUTHORITY["EPSG","4326"]],
* PROJECTION["Mercator_1SP"], PARAMETER["central_meridian",0],
* PARAMETER["scale_factor",1], PARAMETER["false_easting",0],
* PARAMETER["false_northing",0], UNIT["metre",1, AUTHORITY["EPSG","9001"]]]
*
*
*/
public class GlobalMercator {
public static final int TILE_SIZE = 256;
private final int tileSize;
private final double initialResolution;
private final double originShift;
public GlobalMercator() {
tileSize = TILE_SIZE;
initialResolution = 2 * Math.PI * 6378137 / tileSize;
// 156543.03392804062 for tileSize 256 pixels
originShift = 2 * Math.PI * 6378137 / 2.0;
// 20037508.342789244
}
/**
* Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator
* EPSG:900913
*
* @param lat
* @param lon
* @return
*/
public double[] LatLonToMeters( double lat, double lon ) {
double mx = lon * originShift / 180.0;
double my = Math.log(Math.tan((90 + lat) * Math.PI / 360.0)) / (Math.PI / 180.0);
my = my * originShift / 180.0;
return new double[]{mx, my};
}
/**
* Converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84
* Datum
*
* @return
*/
public double[] MetersToLatLon( double mx, double my ) {
double lon = (mx / originShift) * 180.0;
double lat = (my / originShift) * 180.0;
lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0);
return new double[]{lat, lon};
}
/**
* Converts pixel coordinates in given zoom level of pyramid to EPSG:900913
*
* @return
*/
public double[] PixelsToMeters( double px, double py, int zoom ) {
double res = Resolution(zoom);
double mx = px * res - originShift;
double my = py * res - originShift;
return new double[]{mx, my};
}
/**
* Converts EPSG:900913 to pyramid pixel coordinates in given zoom level
*
* @param mx
* @param my
* @param zoom
* @return
*/
public int[] MetersToPixels( double mx, double my, int zoom ) {
double res = Resolution(zoom);
int px = (int) Math.round((mx + originShift) / res);
int py = (int) Math.round((my + originShift) / res);
return new int[]{px, py};
}
/**
* Returns a tile covering region in given pixel coordinates
*
* @param px
* @param py
* @return
*/
public int[] PixelsToTile( int px, int py ) {
int tx = (int) Math.ceil(px / ((double) tileSize) - 1);
int ty = (int) Math.ceil(py / ((double) tileSize) - 1);
return new int[]{tx, ty};
}
/**
* Move the origin of pixel coordinates to top-left corner
*
* @param px
* @param py
* @param zoom
* @return
*/
public int[] PixelsToRaster( int px, int py, int zoom ) {
int mapSize = tileSize << zoom;
return new int[]{px, mapSize - py};
}
/**
* Returns tile for given mercator coordinates
*
* @return
*/
public int[] MetersToTile( double mx, double my, int zoom ) {
int[] p = MetersToPixels(mx, my, zoom);
return PixelsToTile(p[0], p[1]);
}
public int[] metersToTileUp( double mx, double my, int zoom ) {
int[] p = metersToPixelsUp(mx, my, zoom);
return pixelsToTileUp(p[0], p[1]);
}
public int[] metersToPixelsUp( double mx, double my, int zoom ) {
double res = Resolution(zoom);
int px = (int) Math.ceil((mx + originShift) / res);
int py = (int) Math.ceil((my + originShift) / res);
return new int[]{px, py};
}
public int[] pixelsToTileUp( int px, int py ) {
int tx = (int) Math.ceil(px / ((double) tileSize) - 1);
int ty = (int) Math.ceil(py / ((double) tileSize) - 1);
return new int[]{tx, ty};
}
public int[] metersToTileDown( double mx, double my, int zoom ) {
int[] p = metersToPixelsDown(mx, my, zoom);
return pixelsToTileDown(p[0], p[1]);
}
public int[] metersToPixelsDown( double mx, double my, int zoom ) {
double res = Resolution(zoom);
int px = (int) Math.floor((mx + originShift) / res);
int py = (int) Math.floor((my + originShift) / res);
return new int[]{px, py};
}
public int[] pixelsToTileDown( int px, int py ) {
int tx = (int) Math.floor(px / ((double) tileSize) - 1);
int ty = (int) Math.floor(py / ((double) tileSize) - 1);
return new int[]{tx, ty};
}
/**
* Returns bounds of the given tile in EPSG:900913 coordinates
*
* @param tx
* @param ty
* @param zoom
* @return
*/
public double[] TileBounds( int tx, int ty, int zoom ) {
double[] min = PixelsToMeters(tx * tileSize, ty * tileSize, zoom);
double minx = min[0], miny = min[1];
double[] max = PixelsToMeters((tx + 1) * tileSize, (ty + 1) * tileSize, zoom);
double maxx = max[0], maxy = max[1];
return new double[]{minx, miny, maxx, maxy};
}
/**
* Returns bounds of the given tile in latitude/longitude using WGS84 datum
*
*/
public double[] TileLatLonBounds( int tx, int ty, int zoom ) {
double[] bounds = TileBounds(tx, ty, zoom);
double[] mins = MetersToLatLon(bounds[0], bounds[1]);
double[] maxs = MetersToLatLon(bounds[2], bounds[3]);
return new double[]{mins[0], mins[1], maxs[0], maxs[1]};
}
/**
* Resolution (meters/pixel) for given zoom level (measured at Equator)
*
* @return
*/
public double Resolution( int zoom ) {
// return (2 * Math.PI * 6378137) / (this.tileSize * 2**zoom)
return initialResolution / Math.pow(2, zoom);
}
/**
* Maximal scaledown zoom of the pyramid closest to the pixelSize
*
* @param pixelSize
* @return
*/
public int ZoomForPixelSize( int pixelSize ) {
for( int i = 0; i < 30; i++ ) {
if (pixelSize > Resolution(i)) {
if (i != 0) {
return i - 1;
} else {
return 0; // We don't want to scale up
}
}
}
return 0;
}
/**
* Converts TMS tile coordinates to Google Tile coordinates
*
* @param tx
* @param ty
* @param zoom
* @return
*/
public int[] GoogleTile( int tx, int ty, int zoom ) {
// coordinate origin is moved from bottom-left to top-left corner of the
// extent
return new int[]{tx, (int) ((Math.pow(2, zoom) - 1) - ty)};
}
public int[] TMSTileFromGoogleTile( int tx, int ty, int zoom ) {
// coordinate origin is moved from bottom-left to top-left corner of the
// extent
return new int[]{tx, (int) ((Math.pow(2, zoom) - 1) - ty)};
}
/**
* Converts a lat long coordinates to Google Tile Coordinates
*
* @param lat
* @param lon
* @param zoom
* @return
*/
public int[] GoogleTile( double lat, double lon, int zoom ) {
double[] meters = LatLonToMeters(lat, lon);
int[] tile = MetersToTile(meters[0], meters[1], zoom);
return this.GoogleTile(tile[0], tile[1], zoom);
}
/**
* Converts TMS tile coordinates to Microsoft QuadTree
*
* @return
*/
public String QuadTree( int tx, int ty, int zoom ) {
String quadKey = "";
ty = (int) ((Math.pow(2, zoom) - 1) - ty);
for( int i = zoom; i < 0; i-- ) {
int digit = 0;
int mask = 1 << (i - 1);
if ((tx & mask) != 0) {
digit += 1;
}
if ((ty & mask) != 0) {
digit += 2;
}
quadKey += (digit + "");
}
return quadKey;
}
public static void main( String[] args ) {
int[][] TMS = {//
{0, 1, 1},//
{16, 20, 5},//
{8930, 10684, 14},//
};
int[][] GOOGLE = {//
{0, 0, 1}, //
{16, 11, 5}, //
{8930, 5699, 14},//
};
GlobalMercator gm = new GlobalMercator();
for( int i = 0; i < GOOGLE.length; i++ ) {
int[] googleTile = gm.TMSTileFromGoogleTile(GOOGLE[i][0], GOOGLE[i][1], GOOGLE[i][2]);
System.out.println(Arrays.toString(googleTile));
}
}
}