/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2015, Open Source Geospatial Foundation (OSGeo)
*
* This library 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;
* version 2.1 of the License.
*
* This library 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.
*/
package org.geotools.tile.impl.bing;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.tile.impl.WebMercatorTileService;
import com.vividsolutions.jts.geom.Envelope;
/**
* BingTileUtil contains code ported from <a
* href="https://msdn.microsoft.com/en-us/library/bb259689.aspx>Bing Maps</a>
* offering a collection of utilities methods concerning Bing Maps and its tile
* model.
* <p>
* Disclaimer: code contained here has been ported and, thus, re-written. The
* comments, on the other hand, have been mostly taken verbatim with the sole
* purpose of precision and keeping the original intent left clear. The author
* does not claim copyrights or authorship for comments. Original comments are
* <q>quoted</q>.
* </p>
*
* @author Ugo Taddei
* @since 12
* @source $URL:
* http://svn.osgeo.org/geotools/trunk/modules/unsupported/tile-client
* /src/main/java/org/geotools/tile/impl/bing/BingTileUtil.java $
*/
public final class BingTileUtil {
private BingTileUtil() {
// utility class
}
/**
* <q>Converts a point from latitude/longitude WGS-84 coordinates (in
* degrees) into pixel XY coordinates at a specified level of detail.</q>
* <p>
* The Term "Level of detail" is a synonym for zoom level.
* </p>
*
* @param longitude Longitude of the point, in degrees.
* @param latitude Latitude of the point, in degrees.
* @param zoomLevel The zoom level or "Level of detail", from 1 (lowest
* detail) to 23 (highest detail).
* @return
*/
public static int[] lonLatToPixelXY(double longitude, double latitude,
int zoomLevel) {
double _latitude = clip(latitude, WebMercatorTileService.MIN_LATITUDE,
WebMercatorTileService.MAX_LATITUDE);
double _longitude = clip(longitude,
WebMercatorTileService.MIN_LONGITUDE,
WebMercatorTileService.MAX_LONGITUDE);
double x = (_longitude + 180) / 360;
double sinLatitude = Math.sin(_latitude * Math.PI / 180);
double y = 0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude))
/ (4 * Math.PI);
int mapSize = mapSize(zoomLevel);
int pixelX = (int) clip(x * mapSize + 0.5, 0, mapSize - 1);
int pixelY = (int) clip(y * mapSize + 0.5, 0, mapSize - 1);
return new int[] { pixelX, pixelY };
}
/**
* <q>Converts a pixel from pixel XY coordinates at a specified level of
* detail [i.e. zoom level] into latitude/longitude WGS-84 coordinates (in
* degrees).</q>
* <p>
* Note that the X and Y coordinates of a "virtual image" that contains all
* tiles of a given level. They are not the coordinates of a given column or
* row.
* </p>
*
* @param pixelX X coordinate of the point, in pixels.
* @param pixelY Y coordinates of the point, in pixels.
* @param zoomLevel zoom level or "Level of detail", from 1 (lowest detail)
* to 23 (highest detail)
* @return
*/
public static double[] pixelXYToLonLat(int pixelX, int pixelY, int zoomLevel) {
double mapSize = mapSize(zoomLevel);
double x = (clip(pixelX, 0, mapSize - 1) / mapSize) - 0.5;
double y = 0.5 - (clip(pixelY, 0, mapSize - 1) / mapSize);
double latitude = 90 - 360 * Math.atan(Math.exp(-y * 2 * Math.PI))
/ Math.PI;
double longitude = 360 * x;
return new double[] { longitude, latitude };
}
/**
* <q>Determines the map width and height (in pixels) at a specified level
* of detail.</q>
*
* @param zoomLevel Zoom level or "Level of detail", from 1 (lowest detail)
* to 23 (highest detail)
* @return the map size
*/
public static int mapSize(int zoomLevel) {
return BingTile.DEFAULT_TILE_SIZE << zoomLevel;
}
/**
* <q>Converts pixel XY coordinates into tile XY coordinates of the tile
* containing the specified pixel.</q>
*
* @param pixelX Pixel X coordinate.
* @param pixelY Pixel Y coordinate.
* @return
*/
public static int[] pixelXYToTileXY(int pixelX, int pixelY) {
int tileX = pixelX / BingTile.DEFAULT_TILE_SIZE;
int tileY = pixelY / BingTile.DEFAULT_TILE_SIZE;
return new int[] { tileX, tileY };
}
/**
* <q>Converts tile XY coordinates into a QuadKey at a specified level of
* detail.</q>
*
* @param tileX Tile X coordinate.
* @param tileY Tile Y coordinate.
* @param zoomLevel Zoom level or "Level of detail", from 1 (lowest detail)
* to 23 (highest detail)
* @return A string containing the QuadKey.
*/
public static String tileXYToQuadKey(int tileX, int tileY, int zoomLevel) {
StringBuilder quadKey = new StringBuilder();
for (int i = zoomLevel; i > 0; i--) {
char digit = '0';
int mask = 1 << (i - 1);
if ((tileX & mask) != 0) {
digit++;
}
if ((tileY & mask) != 0) {
digit++;
digit++;
}
quadKey.append(digit);
}
return quadKey.toString();
}
/**
* <q>Clips a number to the specified minimum and maximum values.</q>
*
* @param n The number to clip.
* @param minValue Minimum allowable value.
* @param maxValue Maximum allowable value.
* @return The clipped value.
*/
private static double clip(double n, double minValue, double maxValue) {
return Math.min(Math.max(n, minValue), maxValue);
}
/**
* Finds the quadkey of a tile for a given pair of coordinates and at a
* given zoom level.
*
* @param lon The longitude
* @param lat The latitude
* @param zoomLevel The zoom level
* @return A string denoting the quadkey of the tile.
*/
public static String lonLatToQuadKey(double lon, double lat, int zoomLevel) {
int[] pixelXY = BingTileUtil.lonLatToPixelXY(lon, lat, zoomLevel);
int[] tileXY = BingTileUtil.pixelXYToTileXY(pixelXY[0], pixelXY[1]);
return BingTileUtil.tileXYToQuadKey(tileXY[0], tileXY[1], zoomLevel);
}
/**
* Calculates the extent of a tile, given the coordinates and a zoom level
*
* @param lon The longitude
* @param lat The latitude
* @param zoomLevel
* @return A string denoting the quadkey of the tile.
*/
public static ReferencedEnvelope getTileBoundingBox(double lon, double lat,
int zoomLevel) {
int[] imageXY = lonLatToPixelXY(lon, lat, zoomLevel);
int numberOfTilesX = imageXY[0] / BingTile.DEFAULT_TILE_SIZE;
int numberOfTilesY = imageXY[1] / BingTile.DEFAULT_TILE_SIZE;
int tileTopLeftPixelX = numberOfTilesX * BingTile.DEFAULT_TILE_SIZE;
int tileTopLeftPixelY = numberOfTilesY * BingTile.DEFAULT_TILE_SIZE;
double[] topLeftCoords = pixelXYToLonLat(tileTopLeftPixelX,
tileTopLeftPixelY, zoomLevel);
double[] bottomRightCoords = pixelXYToLonLat(tileTopLeftPixelX
+ BingTile.DEFAULT_TILE_SIZE, tileTopLeftPixelY
+ BingTile.DEFAULT_TILE_SIZE, zoomLevel);
Envelope envelope = new Envelope(topLeftCoords[0],
bottomRightCoords[0], topLeftCoords[1], bottomRightCoords[1]);
return new ReferencedEnvelope(envelope, DefaultGeographicCRS.WGS84);
}
}