package org.activityinfo.legacy.shared.reports.util.mapping;
/*
* #%L
* ActivityInfo Server
* %%
* Copyright (C) 2009 - 2013 UNICEF
* %%
* 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 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 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/gpl-3.0.html>.
* #L%
*/
import org.activityinfo.model.type.geo.AiLatLng;
import org.activityinfo.legacy.shared.reports.content.Point;
/**
* Provides a series of utility functions useful for processing map tiles
* created in the Googlian sense (spherical mercator) on the server.
* <p/>
* See:
* <ul>
* <li>http://en.wikipedia.org/wiki/Mercator_projection</li>
* <li>
* http://code.google.com/apis/maps/documentation/overlays.html#CustomMapTiles</li>
* </ul>
*
* @author Alex Bertram
*/
public final class TileMath {
private static final int MAX_ZOOM = 16;
/*
* Various math constants
*/
private static final double D2R = 0.0174532925;
private static final double EPSLN = 1.0e-10;
private static final double HALF_PI = 1.5707963267948966192313216916398;
private static final double TWO_PI = 6.283185307179586476925286766559;
private static final double FORTPI = 0.78539816339744833;
private static final double DEGREES_PER_RADIAN = 57.2957795;
public static final int TILE_SIZE = 256;
private TileMath() {
}
/**
* Returns the circumference of the projected coordinate system at the
* equator (and elsewhere perhaps? )
*
* @param zoom
* @return
*/
public static int size(int zoom) {
return (int) (float) (Math.pow(2, zoom) * TILE_SIZE);
}
public static double radius(int zoom) {
return size(zoom) / TWO_PI;
}
/**
* Projects a geographic X (longitude) coordinate forward into the Googlian
* screen pixel coordinate system, based on a given zoom level
*
* @param lng
* @param zoom
* @return
*/
public static Point fromLatLngToPixel(AiLatLng latlng, int zoom) {
double size = size(zoom);
double radius = size / TWO_PI;
double x = (latlng.getLng() + 180.0) / 360.0 * size;
double lat = latlng.getLat() * D2R;
if (Math.abs(Math.abs(lat) - HALF_PI) <= EPSLN) {
throw new IllegalArgumentException("Too close to the poles to project");
}
double ty = (size) / 2.0;
double y = radius * Math.log(Math.tan(FORTPI + 0.5 * lat));
y = ty - y;
return new Point(x, y);
}
public static AiLatLng inverse(Point px, int zoom) {
double size = size(zoom);
double radius = size / TWO_PI;
double lng = (px.getDoubleX() / size * 360d) - 180d;
double ty = (size) / 2.0;
double y = (ty - px.getDoubleY()) / radius;
double lat = Math.atan(Math.sinh(y)) * DEGREES_PER_RADIAN;
return new AiLatLng(lat, lng);
}
public static Extents tileBounds(int zoom, int x, int y) {
Point upperLeft = pointForTile(new Tile(x, y));
Point lowerRight = pointForTile(new Tile(x + 1, y + 1));
AiLatLng northWest = inverse(upperLeft, zoom);
AiLatLng southEast = inverse(lowerRight, zoom);
return new Extents(southEast.getLat(), northWest.getLat(), northWest.getLng(), southEast.getLng());
}
/**
* Returns the maximum zoom level at which the given extents will fit inside
* the map of the given size
*
* @param extent
* @param mapWidth
* @param mapHeight
* @return
*/
public static int zoomLevelForExtents(Extents extent, int mapWidth, int mapHeight) {
int zoomLevel = 1;
do {
Point upperLeft = fromLatLngToPixel(new AiLatLng(extent.getMaxLat(), extent.getMinLon()), zoomLevel);
Point lowerRight = fromLatLngToPixel(new AiLatLng(extent.getMinLat(), extent.getMaxLon()), zoomLevel);
int extentWidth = lowerRight.getX() - upperLeft.getX();
// assert extentWidth >= 0;
if (extentWidth > mapWidth) {
return zoomLevel - 1;
}
int extentHeight = lowerRight.getY() - upperLeft.getY();
if (extentHeight > mapHeight) {
return zoomLevel - 1;
}
zoomLevel++;
} while (zoomLevel < MAX_ZOOM);
return zoomLevel;
}
/**
* Returns the coordinate of the tile given a point in the projected
* coordinate system.
*
* @param px
* @return
*/
public static Tile tileForPoint(Point px) {
return new Tile(px.getX() / TILE_SIZE, px.getY() / TILE_SIZE);
}
public static Point pointForTile(Tile tile) {
return new Point(tile.getX() * TILE_SIZE, tile.getY() * TILE_SIZE);
}
}