/*
* Copyright (C) 2014 Alec Dhuse
*
* 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/>.
*/
package co.foldingmap.map;
import co.foldingmap.Logger;
import co.foldingmap.map.tile.TileMath;
import co.foldingmap.map.vector.Coordinate;
/**
* Enables maps to be rendered using the MercatorProjection.
* Reference: http://en.wikipedia.org/wiki/Mercator_projection
*
* @author Alec
*/
public class MercatorProjection extends MapProjection {
/**
* Constructor with default options.
*/
public MercatorProjection() {
this.referenceLatitude = 83;
this.referenceLongitude = -180;
this.zoomLevel = 0.022f;
}
/**
* Constructor with default options.
*
* @param refLat
* @param refLon
* @param zoom
*/
public MercatorProjection(double refLat, double refLon, float zoom) {
if (refLat <= 90 && refLat >= -90) {
this.referenceLatitude = refLat;
} else {
this.referenceLatitude = 85;
Logger.log(Logger.ERR, "Error in MercatorProjection Constructor Latitude out of range: " + refLat);
}
if (refLon <= 180 && refLon >= -180) {
this.referenceLongitude = refLon;
} else {
this.referenceLongitude = -180;
Logger.log(Logger.ERR, "Error in MercatorProjection Constructor Longitude out of range: " + refLon);
}
if (getX(new Coordinate(0,0,-180)) >= 0) {
displayLeft = true;
} else {
displayLeft = false;
}
if (getX(new Coordinate(0,0,-180)) <= displayWidth) {
displayRight = true;
} else {
displayRight = false;
}
this.zoomLevel = zoom;
}
/**
* Returns the Latitude for a given x,y on the screen.
* Adjusts for reference.
*
* @param x
* @param y
* @return
*/
@Override
public double getLatitude(double x, double y) {
double yRef = getY(referenceLatitude);
double yAdjust = ((y / zoomLevel));
double yCorrect = yAdjust - yRef;
double latitude = getLatitude(yCorrect) * -1;
return latitude;
}
/**
* Gets a latitude from a y value.
* Does not adjust for reference.
*
* @param y
* @return
*/
private double getLatitude(double y) {
double latitude;
double p1 = Math.pow(Math.E, (y / EARTH_RADIUS));
double p2 = (2 * Math.atan(p1));
double lat = (p2 - (Math.PI / 2));
latitude = Coordinate.getLatitudeInDecimal(lat);
return latitude;
}
/**
* Gets a Longitude for a given x,y on the screen.
* Adjusts for reference.
*
* @param x
* @param y
* @return
*/
@Override
public double getLongitude(double x, double y) {
double xCorrect;
double longitude, lon, ref;
xCorrect = x / zoomLevel;
ref = Coordinate.getLongitudeInRadians(referenceLongitude);
lon = (xCorrect / EARTH_RADIUS) + ref;
longitude = Coordinate.getLongitudeInDecimal(lon);
return longitude;
}
/**
* Returns a string containing the view info in the form:
* longitude,latitude,zoom
*
* @return
*/
@Override
public String getViewInfo() {
String x, y, z;
x = Double.toString(getReferenceLongitude());
y = Double.toString(MapUtilities.normalizeLongitude(getReferenceLatitude()));
z = Double.toString(TileMath.getTileMapZoom(zoomLevel));
return x + "," + y + "," + z;
}
/**
* Returns the x position on the screen that corresponds to the given
* longitude.
*
* @param c
* @return The x coordinate on the screen the longitude corresponds to.
*/
@Override
public final double getX(Coordinate c) {
double x, ref, lon, mod;
ref = Coordinate.getLongitudeInRadians(referenceLongitude);
lon = c.getLongitudeInRadians();
mod = (lon - ref);
x = mod * EARTH_RADIUS;
return x * zoomLevel;
}
/**
* Returns the x position on the screen that corresponds to the given
* longitude.
*
* @param latitude
* @param longitude
* @return The x coordinate on the screen the longitude corresponds to.
*/
@Override
public final double getX(double latitude, double longitude) {
double x, ref, lon, mod;
ref = Coordinate.getLongitudeInRadians(referenceLongitude);
lon = Coordinate.getLongitudeInRadians(longitude);
mod = (lon - ref);
x = mod * EARTH_RADIUS;
return x * zoomLevel;
}
/**
* Returns the y position on the screen that corresponds to the given latitude.
* Adjusts for reference.
*
* @param c
* @return The y position on the screen that corresponds to the given latitude.
*/
@Override
public final double getY(Coordinate c) {
//Adjust max coordinate so map is more uniform.
if (c.getLatitude() == -90.0f)
c.setLatitude(-89.99f);
double yGiv = getY(c.getLatitude());
double yRef = getY(referenceLatitude);
double y = (yRef - yGiv);
return (y) * zoomLevel;
}
/**
* Returns the y position on the screen that corresponds to the given latitude.
* Does not adjusts for reference.
*
* @param latitude
* @return
*/
private float getY(double latitude) {
double lat = Coordinate.getLatitudeInRadians(latitude);
double tan = (float) Math.tan((Math.PI / 4.0f) + (lat / 2.0f));
double log = (float) Math.log(tan);
double y = log * EARTH_RADIUS;
return (float) y;
}
/**
* Returns the y position on the screen that corresponds to the given latitude.
* Adjusts for reference.
*
* @param latitude
* @param longitude
* @return The y position on the screen that corresponds to the given latitude.
*/
@Override
public final double getY(double latitude, double longitude) {
//Adjust max coordinate so map is more uniform.
if (latitude == -90.0f)
latitude = (-89.99f);
double yGiv = getY(latitude);
double yRef = getY(referenceLatitude);
double y = (yRef - yGiv);
return (y) * zoomLevel;
}
/**
* Sets the center of the viewable projection to a given screen coordinate.
*
* @param x
* @param y
*/
private void setCenter(float x, float y) {
double xLon, yLat;
double centerLon, centerLat;
double difLon, difLat;
xLon = getLongitude(x, y);
yLat = getLatitude(x, y);
centerLon = getLongitude(displayHeight / 2.0f, displayWidth / 2.0f);
centerLat = getLatitude(displayHeight / 2.0f, displayWidth / 2.0f);
difLon = (xLon - centerLon) * 0.15f;
difLat = (yLat - centerLat) * 0.15f;
referenceLatitude += difLat;
referenceLongitude += difLon;
}
/**
* Sets the zoom level to be used in this projection.
*
* @param zoomLevel
*/
@Override
public void setZoomLevel(float zoomLevel) {
this.zoomLevel = zoomLevel;
}
/**
* Shifts the map reference of the projection.
* This is used to construct the view port.
*
* @param x
* @param y
*/
@Override
public void shiftMapReference(double x, double y) {
double idlX, newLatitude, newLongitude;
x *= -1;
y *= -1;
newLongitude = getLongitude(x, y);
newLatitude = getLatitude(x, y);
if (newLatitude > 90)
newLatitude = 90;
if (newLatitude < -90)
newLatitude = -90;
referenceLatitude = newLatitude;
referenceLongitude = newLongitude;
idlX = getX(new Coordinate(0,0,-180));
if (idlX >= 0 && idlX <= displayWidth) {
displayLeft = true;
} else {
displayLeft = false;
}
if (getLongitude(displayWidth, 0) >= 180) {
//if (idlX >= 0 && idlX <= displayWidth) {
displayRight = true;
} else {
displayRight = false;
}
}
/**
* Zoom in the view port.
*
* @param x
* @param y
*/
@Override
public void zoomIn(double x, double y) {
float tileZoom = TileMath.getTileMapZoom(zoomLevel) + 0.5f;
float newZoom = TileMath.getVectorMapZoom(tileZoom);
float scale = newZoom / zoomLevel;
zoomLevel = newZoom;
float centerX = getDisplayWidth() / 2.0f;
float centerY = getDisplayHeight() / 2.0f;
float deltaX = centerX - (centerX * scale);
float deltaY = centerY - (centerY * scale);
shiftMapReference(deltaX, deltaY);
}
/**
* Zoom out the view port.
*
* @param x
* @param y
*/
@Override
public void zoomOut(double x, double y) {
float tileZoom = TileMath.getTileMapZoom(zoomLevel) - 0.5f;
float newZoom = TileMath.getVectorMapZoom(tileZoom);
float scale = newZoom / zoomLevel;
zoomLevel = newZoom;
float centerX = getDisplayWidth() / 2.0f;
float centerY = getDisplayHeight() / 2.0f;
float deltaX = centerX - (centerX * scale);
float deltaY = centerY - (centerY * scale);
shiftMapReference(deltaX, deltaY);
}
}