package org.syzygy.gps;
public final class MercatorProjection
{
private final double wa = Math.PI / 180.0;
private final int tileSize;
private final double[] pixelsPerLonDegree;
private final double[] pixelsPerLonRadian;
private final double[] bitmapOrigo;
public MercatorProjection(int tileSize, int zoomLevels)
{
this.tileSize = tileSize;
this.pixelsPerLonDegree = new double[zoomLevels];
this.pixelsPerLonRadian = new double[zoomLevels];
this.bitmapOrigo = new double[zoomLevels];
int c = tileSize;
double bc = 2 * Math.PI;
for (int z = 0; z < zoomLevels; z++) {
pixelsPerLonDegree[z] = c / (double) 360;
pixelsPerLonRadian[z] = c / bc;
bitmapOrigo[z] = c / 2;
c *= 2;
}
}
public double getLongitude(double x, int z)
{
return (x * tileSize - bitmapOrigo[z]) / pixelsPerLonDegree[z];
}
public double getLatitude(double y, int z)
{
double a = 2 * (bitmapOrigo[z] - y * tileSize) / pixelsPerLonRadian[z];
double ab = 1;
for (int i = 29; i > 0; i--)
ab = 1 + a / (double) i * ab;
double b = ab;
//double b = Math.pow(Math.E, a);
double e = (b - 1) / (b + 1);
//double exact = Math.asin(e);
double e2 = e * e;
double term = e, approx = e;
// term_n / term_n-1 = (2n-1)^2.e^2 / 2n(2n+1); t_0 = e;
// asin(e) = term_0 + term_1 + term_2 + ...
for (int i = 1; i < 29; i++) {
double n2 = i + i;
term = term * e2 * (n2 - 1) * (n2 - 1) / n2 / (n2 + 1);
approx += term;
}
// return exact / wa;
return approx / wa;
}
public double mapLongitude(double lon, int z)
{
return (bitmapOrigo[z] + lon * pixelsPerLonDegree[z]) / tileSize;
}
public double mapLatitude(double lat, int z)
{
// computes an approximation to the inverse gudermannian function
// see (6), http://mathworld.wolfram.com/GudermannianFunction.html
double as = Math.sin(lat * wa);
double as2 = as * as;
// inv_gd = ln((1 + s) / 1 - s) / 2;
// =~ s + s^3/3 + s^5/5 + ...
double approx = 1;
for (int d = 49; d > 1;) {
int n = d - 2;
approx = 1 + n * as2 / (double) d * approx;
d = n;
}
approx *= as;
return (bitmapOrigo[z] - approx * pixelsPerLonRadian[z]) / tileSize;
}
public double getLongitudinalDistance(double lat, double dlon)
{
return MEAN_RADIUS * Math.cos(Math.toRadians(lat)) * Math.toRadians(dlon);
}
public double getLatitudinalDistance(double dlat)
{
return MEAN_RADIUS * Math.toRadians(dlat);
}
private static final double MEAN_RADIUS = 6.371e6;
}