package no.priv.garshol.duke.comparators;
import no.priv.garshol.duke.Comparator;
import no.priv.garshol.duke.DukeException;
// The formula is taken from here:
// http://www.movable-type.co.uk/scripts/latlong.html
/**
* Comparator which compares two geographic positions given by
* coordinates by the distance between them along the earth's surface.
* It assumes the parameters are of the form '59.917516,10.757933',
* where the numbers are degrees latitude and longitude, respectively.
*
* <p>The computation simply assumes a sphere with a diameter of 6371
* kilometers, so no particular geodetic model is assumed. WGS83
* coordinates will work fine, while UTM coordinates will not work.
*
* @since 1.0
*/
public class GeopositionComparator implements Comparator {
private static final double R = 6371000; // in meters
private double maxdist; // we default to 100 meters as the max
private boolean strict; // whether to fail on errors
public GeopositionComparator() {
this.maxdist = 100;
}
public boolean isTokenized() {
return false;
}
public double compare(String v1, String v2) {
try {
double lat1 = getLatitude(v1);
double lon1 = getLongitude(v1);
double lat2 = getLatitude(v2);
double lon2 = getLongitude(v2);
if (!valid(lat1, lon1) || !valid(lat2, lon2))
return 0.5;
double dist = distance(lat1, lon1, lat2, lon2);
if (dist > maxdist)
return 0.0;
return ((1.0 - (dist / maxdist)) * 0.5 ) + 0.5;
} catch (NumberFormatException e) {
if (strict)
throw new DukeException("Invalid number: " + e, e);
return 0.5;
}
}
public void setStrict(boolean strict) {
this.strict = strict;
}
public void setMaxDistance(double maxdist) {
this.maxdist = maxdist;
}
public double getMaxDistance() {
return maxdist;
}
/**
* Returns the distance between the two points in meters.
*/
public static double distance(double lat1, double lon1,
double lat2, double lon2) {
double dLat = Math.toRadians(lat2-lat1);
double dLon = Math.toRadians(lon2-lon1);
lat1 = Math.toRadians(lat1);
lat2 = Math.toRadians(lat2);
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 R * c;
}
private static double getLongitude(String v) {
int pos = v.indexOf(',');
if (pos == -1)
throw new NumberFormatException("No comma separating lat/long");
return Double.parseDouble(v.substring(pos + 1));
}
private static double getLatitude(String v) {
int pos = v.indexOf(',');
if (pos == -1)
throw new NumberFormatException("No comma separating lat/long");
return Double.parseDouble(v.substring(0, pos));
}
private boolean valid(double lat, double lon) {
if (lat > 90.0 || lat < -90.0 || // north pole: 90 deg north
lon > 180.0 || lon < -180.0) { // date line at -180 and +180
if (strict)
throw new DukeException("Position outside legal range: " + lat + ", " +
lon);
return false;
} else
return true;
}
}