package com.mapbox.mapboxsdk.tileprovider; import android.graphics.Rect; import com.mapbox.mapboxsdk.constants.GeoConstants; import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.geometry.BoundingBox; import com.mapbox.mapboxsdk.tileprovider.constants.TileLayerConstants; /** * A map tile is distributed using the observer pattern. The tile is delivered by a tile provider * (i.e. a descendant of {@link com.mapbox.mapboxsdk.tileprovider.modules.MapTileModuleLayerBase} * or * {@link MapTileLayerBase} to a consumer of tiles (e.g. descendant of * {@link com.mapbox.mapboxsdk.overlay.TilesOverlay}). Tiles are typically images (e.g. png or * jpeg). */ public class MapTile implements GeoConstants, MapboxConstants, TileLayerConstants { public static final int MAPTILE_SUCCESS_ID = 0; public static final int MAPTILE_FAIL_ID = MAPTILE_SUCCESS_ID + 1; // This class must be immutable because it's used as the key in the cache hash map // (ie all the fields are final). private final int x; private final int y; private final int z; private final String path; private final String cacheKey; private final int code; private Rect mTileRect; // For lat/lng bounds calculation private double tileSize = DEFAULT_TILE_SIZE; private double originShift = 2 * Math.PI * RADIUS_EARTH_METERS / 2.0; private double initialResolution = 2 * Math.PI * RADIUS_EARTH_METERS / tileSize; public MapTile(final int az, final int ax, final int ay) { this("", az, ax, ay); } public MapTile(final String aCacheKey, final int az, final int ax, final int ay) { this.z = az; this.x = ax; this.y = ay; this.path = (new StringBuilder()).append(z).append('/').append(x).append('/').append(y).toString(); this.cacheKey = aCacheKey + "/" + path; this.code = ((17 * (37 + z)) * (37 * x)) * (37 + y); } public int getZ() { return z; } public int getX() { return x; } public int getY() { return y; } public String getPath() { return path; } public String getCacheKey() { return cacheKey; } @Override public String toString() { return path; } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (!(obj instanceof MapTile)) { return false; } final MapTile rhs = (MapTile) obj; return z == rhs.z && x == rhs.x && y == rhs.y; } @Override public int hashCode() { return this.code; } public void setTileRect(final Rect rect) { mTileRect = rect; } public final Rect getTileRect() { return mTileRect; } public BoundingBox getTileLatLonBounds() { // Returns bounds of the given tile in EPSG:900913 coordinates double[] bounds = TileBounds(this.getX(), this.getY(), this.getZ()); double[] minLatLon = MetersToLatLon(bounds[0], bounds[3]); double[] maxLatLon = MetersToLatLon(bounds[2], bounds[1]); return new BoundingBox(maxLatLon[0], maxLatLon[1], minLatLon[0], minLatLon[1]); } private double[] TileBounds(int tx, int ty, int zoom) { // Returns bounds of the given tile in EPSG:900913 coordinates double[] wn = PixelsToMeters(tx * tileSize, ty * tileSize, zoom); double[] es = PixelsToMeters((tx + 1) * tileSize, (ty + 1) * tileSize, zoom); return new double[]{wn[0], wn[1], es[0], es[1]}; } private double[] PixelsToMeters(double px, double py, double zoom) { // Converts pixel coordinates in given zoom level of pyramid to EPSG:900913 double res = Resolution(zoom); double mx = px * res - originShift; double my = py * res - originShift; return new double[]{mx, my}; } private double[] MetersToLatLon(double mx, double my) { // Converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84 Datum 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}; } private double Resolution(double zoom) { // Resolution (meters/pixel) for given zoom level (measured at Equator) return initialResolution / Math.pow(2, zoom); } }