package me.guillaumin.android.osmtracker.util;
/**
* Geopoint to 2D projection using Mercator system.
*
* @author Nicolas Guillaumin
*
*/
public class MercatorProjection {
/**
* Maximum latitude useable with Mercator projection.
*/
private static final double MAX_LATITUDE = 85.0511f;
/**
* X & longitude offset in used 2-dim arrays.
*/
public static final byte X = 0, LONGITUDE = 0;
/**
* Y & latitude offsets in used 2-dim arrays.
*/
public static final byte Y = 1, LATITUDE = 1;
/**
* Width & Height of the projection (pixels)
*/
private int width, height;
/**
* Scale of the projection
*/
private double scale;
/**
* Four corners of the projection, in converted coordinates
*/
private double topX, topY, bottomX, bottomY;
/**
* Dimensions of projected space.
*/
private double dimX, dimY;
public MercatorProjection(double minLat, double minLon, double maxLat, double maxLon, int w, int h) {
width = w;
height = h;
// Get data range for X and Y
double rangeX = Math.abs(convertLongitude(maxLon) - convertLongitude(minLon));
double rangeY = Math.abs(convertLatitude(maxLat) - convertLatitude(minLat));
// Determine scale for each axis
double scaleX = rangeX / width;
double scaleY = rangeY / height;
// Determine which scale to use. We take the greater to
// be able to fit in width AND height
scale = (scaleX > scaleY) ? scaleX : scaleY;
// Determine offset for X & Y, to translate
// lon/lat into screen center
double offsetX = (width * scale) - rangeX;
double offsetY = (height * scale) - rangeY;
// Determine 4 corners of projection
topX = convertLongitude(minLon) - (offsetX / 2);
topY = convertLatitude(minLat) - (offsetY / 2);
bottomX = convertLongitude(maxLon) + (offsetX / 2);
bottomY = convertLatitude(maxLat) + (offsetY / 2);
// Calculate projection dimensions
dimX = bottomX - topX;
dimY = bottomY - topY;
}
/**
* Projects lon/lat coordinates into this projection.
*
* @param longitude
* Longitude to project
* @param latitude
* Latitude to project
* @return An array of 2 int projected coordinates (use
* {@link MercatorProjection.X} and {@link MercatorProjection.Y} for
* access.
*/
public int[] project(double longitude, double latitude) {
int[] out = new int[2];
out[X] = (int) Math.round(((convertLongitude(longitude) - topX) / dimX) * width);
out[Y] = (int) Math.round(height - (((convertLatitude(latitude) - topY) / dimY) * height));
return out;
}
/**
* Convert longitude to X coordinate.
*
* @param longitude
* Longitude to convert.
* @return Converted X coordinate.
*/
private double convertLongitude(double longitude) {
return longitude;
}
/**
* Converts latitude to Y coordinate.
*
* @param latitude
* Latitude to convert.
* @return Converted Y coordinate.
*/
private double convertLatitude(double latitude) {
if (latitude < -MAX_LATITUDE) {
latitude = -MAX_LATITUDE;
} else if (latitude > MAX_LATITUDE) {
latitude = MAX_LATITUDE;
}
return Math.log(Math.tan(Math.PI / 4 + (latitude * Math.PI / 180 / 2))) / (Math.PI / 180);
}
public double getScale() {
return scale;
}
/**
* Given a float degree value (latitude or longitude), format it to Degrees/Minutes/Seconds.
* @param degrees The value, such as 43.0438
* @param isLatitude Is this latitude, not longitude?
* @return The Degrees,Minutes,Seconds, such as: 43° 2' 38" N
*/
public static String formatDegreesAsDMS(Float degrees, final boolean isLatitude) {
if (degrees == null) {
return "";
}
final boolean neg;
if (degrees > 0) {
neg = false;
} else {
neg = true;
degrees = -degrees;
}
StringBuffer dms = new StringBuffer();
int n = degrees.intValue();
dms.append(n);
dms.append("\u00B0 ");
degrees = (degrees - n) * 60.0f;
n = degrees.intValue();
dms.append(n);
dms.append("' ");
degrees = (degrees - n) * 60.0f;
n = degrees.intValue();
dms.append(n);
dms.append("\" ");
if (isLatitude)
dms.append(neg ? 'S' : 'N');
else
dms.append(neg ? 'W' : 'E');
return dms.toString();
}
}