package edu.kit.pse.ws2013.routekit.util;
/**
* A pair of geographic coordinates.
*/
public class Coordinates {
private float lat;
private float lon;
/**
* Creates a new object from the given coordinates.
*
* @param lat
* the latitude
* @param lon
* the longitude
* @throws IllegalArgumentException
* if the coordinates are out of range
*/
public Coordinates(float lat, float lon) {
if (lat < -90 || lat > 90 || lon <= -181 || lon >= 181) {
throw new IllegalArgumentException();
}
if (lat < -85) {
lat = -85;
}
if (lat > 85) {
lat = 85;
}
this.lat = lat;
this.lon = lon;
}
/**
* Calculates the angle enclosed between a line from this point to the given
* first point and a line from this point to the given second point.
*
* @param coords1
* the coordinates of the first point
* @param coords2
* the coordinates of the second point
* @return that angle
*/
public float angleBetween(Coordinates coords1, Coordinates coords2) {
final double b1 = getBearing(this, coords2);
final double b2 = getBearing(coords1, this);
return (float) Math.IEEEremainder(b1 - b2, 360) + 180;
}
private static double getBearing(Coordinates c1, Coordinates c2) {
final float lat1 = c1.getLatitude();
final float lat2 = c2.getLatitude();
final float lon1 = c1.getLongitude();
final float lon2 = c2.getLongitude();
final double dx = Math.cos(Math.PI / 180 * lat1) * (lon2 - lon1);
final double dy = lat2 - lat1;
final double angle = 180 / Math.PI * Math.atan2(dy, dx);
return angle;
}
/**
* Calculates the distance (air-line, in meters) between these and the given
* coordinates.
*
* @param other
* the other coordinates
* @return the distance between the two coordinates
*/
public float distanceTo(Coordinates other) {
double toRad = Math.PI / 180;
double R = 6371000; // m
double dLat = (other.lat - lat) * toRad;
double dLon = (other.lon - lon) * toRad;
double lat1 = lat * toRad;
double lat2 = other.lat * toRad;
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2)
* Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return (float) (R * c);
}
/**
* Calculates the coordinates of the point with the specified position on a
* line between these coordinates and the given coordinates. This method can
* be used to determine the coordinates of a {@link PointOnEdge}.
*
* @param to
* indicates the direction into which to go
* @param position
* a value between 0 and 1 indicating how far to go into that
* direction
* @return the wanted coordinates
*/
public Coordinates goIntoDirection(Coordinates to, float position) {
return new Coordinates(lat + (to.getLatitude() - lat) * position, lon
+ (to.getLongitude() - lon) * position);
}
/**
* Calculates the SlippyMap tile x-component of these coordinates.
*
* @param zoom
* the zoom level
* @return the SMT x-component
*/
public float getSmtX(int zoom) {
return (lon + 180) / 360 * (1 << zoom);
}
/**
* Calculates the SlippyMap tile y-component of these coordinates.
*
* @param zoom
* the zoom level
* @return the SMT y-component
*/
public float getSmtY(int zoom) {
return (float) ((1 - Math.log(Math.tan(Math.toRadians(lat)) + 1
/ Math.cos(Math.toRadians(lat)))
/ Math.PI) / 2 * (1 << zoom));
}
/**
* Creates a new {@code Coordinates} object from the given SlippyMap tile
* coordinates.
*
* @param x
* the SMT x-component
* @param y
* the SMT y-component
* @param zoom
* the zoom level
*
* @see <a
* href="http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames">SlippyMap
* tiles in the OSM wiki</a>
* @return the coordinates of the given point
*/
public static Coordinates fromSmt(float x, float y, int zoom) {
int limit = 1 << zoom;
x %= limit;
y %= limit;
if (x < 0) {
x = (x + limit) % limit;
}
if (y < 0) {
y = (y + limit) % limit;
}
float lon = (float) (x / Math.pow(2.0, zoom) * 360.0 - 180);
double n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, zoom);
float lat = (float) Math.toDegrees(Math.atan(Math.sinh(n)));
return new Coordinates(lat, lon);
}
/**
* Returns the latitude.
*
* @return the latitude
*/
public float getLatitude() {
return lat;
}
/**
* Returns the longitude.
*
* @return the longitude
*/
public float getLongitude() {
return lon;
}
/**
* <p>
* Returns a string representation of the coordinates suitable for parsing
* with {@link #fromString(String) fromString}.
* </p>
* <p>
* The exact format is: {@code lat lon}, where {@code lat} and {@code lon}
* are the latitude and the longitude respectively, in decimal floating
* point number format.
* </p>
*
* @return A string representation of the coordinates.
*/
@Override
public String toString() {
return lat + " " + lon;
}
@Override
public boolean equals(Object other) {
if (other instanceof Coordinates) {
return lat == ((Coordinates) other).lat
&& lon == ((Coordinates) other).lon;
}
return false;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Float.floatToIntBits(lat);
result = prime * result + Float.floatToIntBits(lon);
return result;
}
/**
* Parses a coordinates string as returned by {@link #toString()} back into
* {@link Coordinates}.
*
* @param s
* the coordinates string
* @return The {@link Coordinates} parsed from the string.
* @throws IllegalArgumentException
* if the coordinates string cannot be parsed
*/
public static Coordinates fromString(String s) {
if (s == null) {
throw new IllegalArgumentException("Coordinates string is null!",
new NullPointerException());
}
String[] coords = s.split(" ");
if (coords.length != 2) {
throw new IllegalArgumentException(
"Coordinates string must contain exactly two space-separated components!");
}
float lat = parseLatitude(coords[0]);
float lon = parseLongitude(coords[1]);
return new Coordinates(lat, lon);
}
/**
* Parses a latitude specification from a string.
*
* @param s
* the string to be parsed
* @return the latitude parsed from the string
* @throws IllegalArgumentException
* if the string cannot be parsed
*/
public static float parseLatitude(String s) {
if (s == null) {
throw new IllegalArgumentException();
}
float lat;
try {
lat = Float.parseFloat(s);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Can’t parse latitude!", e);
}
if (lat < -85 || lat > 85
|| (Math.abs(lat) == 85 && !s.matches("-?85(.0+)?"))) {
throw new IllegalArgumentException(
"Latitude must be in range [-85°,85°]!");
}
return lat;
}
/**
* Parses a longitude specification from a string.
*
* @param s
* the string to be parsed
* @return the longitude parsed from the string
* @throws IllegalArgumentException
* if the string cannot be parsed
*/
public static float parseLongitude(String s) {
if (s == null) {
throw new IllegalArgumentException();
}
float lon;
try {
lon = Float.parseFloat(s);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Can’t parse longitude!", e);
}
if (lon < -180 || lon > 180
|| (Math.abs(lon) == 180 && !s.matches("-?180(.0+)?"))) {
throw new IllegalArgumentException(
"Longitude must be in range [-180°,180°]!");
}
return lon;
}
}