package org.vertexium.type; import org.vertexium.VertexiumException; import java.io.Serializable; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class GeoPoint implements Serializable, GeoShape, Comparable<GeoPoint> { private static final long serialVersionUID = 1L; private static final double COMPARE_TOLERANCE = 0.00001; private static double EARTH_RADIUS = 6371; // km private static final Pattern HOUR_MIN_SECOND_PATTERN = Pattern.compile("\\s*(-)?([0-9\\.]+)°(\\s*([0-9\\.]+)'(\\s*([0-9\\.]+)\")?)?"); private static final Pattern WITH_DESCRIPTION_PATTERN = Pattern.compile("(.*)\\[(.*)\\]"); private double latitude; private double longitude; private Double altitude; private String description; protected GeoPoint() { latitude = 0; longitude = 0; altitude = null; description = null; } public GeoPoint(double latitude, double longitude, Double altitude, String description) { this.latitude = latitude; this.longitude = longitude; this.altitude = altitude; this.description = description; } public GeoPoint(double latitude, double longitude, Double altitude) { this(latitude, longitude, altitude, null); } public GeoPoint(double latitude, double longitude) { this(latitude, longitude, null, null); } public GeoPoint(double latitude, double longitude, String description) { this(latitude, longitude, null, description); } public double getLatitude() { return latitude; } public double getLongitude() { return longitude; } public Double getAltitude() { return altitude; } public String getDescription() { return description; } @Override public String toString() { return "(" + getLatitude() + ", " + getLongitude() + ")"; } @Override public boolean within(GeoShape geoShape) { throw new VertexiumException("Not implemented for argument type " + geoShape.getClass().getName()); } @Override public int hashCode() { int hash = 3; hash = 47 * hash + (int) (Double.doubleToLongBits(this.latitude) ^ (Double.doubleToLongBits(this.latitude) >>> 32)); hash = 47 * hash + (int) (Double.doubleToLongBits(this.longitude) ^ (Double.doubleToLongBits(this.longitude) >>> 32)); hash = 47 * hash + (this.altitude != null ? this.altitude.hashCode() : 0); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final GeoPoint other = (GeoPoint) obj; if (Math.abs(distanceBetween(this, other)) > 0.0001) { return false; } if (this.altitude != other.altitude && (this.altitude == null || !this.altitude.equals(other.altitude))) { return false; } return true; } public static double distanceBetween(GeoPoint geoPoint1, GeoPoint geoPoint2) { return distanceBetween( geoPoint1.getLatitude(), geoPoint1.getLongitude(), geoPoint2.getLatitude(), geoPoint2.getLongitude()); } // see http://www.movable-type.co.uk/scripts/latlong.html public static double distanceBetween(double latitude1, double longitude1, double latitude2, double longitude2) { double dLat = toRadians(latitude2 - latitude1); double dLon = toRadians(longitude2 - longitude1); latitude1 = toRadians(latitude1); latitude2 = toRadians(latitude2); double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(latitude1) * Math.cos(latitude2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return EARTH_RADIUS * c; } private static double toRadians(double v) { return v * Math.PI / 180; } @Override public int compareTo(GeoPoint other) { int i; if ((i = compare(getLatitude(), other.getLatitude())) != 0) { return i; } if ((i = compare(getLongitude(), other.getLongitude())) != 0) { return i; } if (getAltitude() != null && other.getAltitude() != null) { return compare(getAltitude(), other.getAltitude()); } if (getAltitude() != null) { return 1; } if (other.getAltitude() != null) { return -1; } return 0; } private static int compare(double d1, double d2) { if (Math.abs(d1 - d2) < COMPARE_TOLERANCE) { return 0; } if (d1 < d2) { return -1; } if (d1 > d2) { return 1; } return 0; } public static GeoPoint parse(String str) { String description; Matcher m = WITH_DESCRIPTION_PATTERN.matcher(str); if (m.matches()) { description = m.group(1).trim(); str = m.group(2).trim(); } else { description = null; } String[] parts = str.split(","); if (parts.length < 2) { throw new VertexiumException("Too few parts to GeoPoint string. Expected at least 2 found " + parts.length + " for string: " + str); } if (parts.length >= 4) { throw new VertexiumException("Too many parts to GeoPoint string. Expected less than or equal to 3 found " + parts.length + " for string: " + str); } double latitude = parsePart(parts[0]); double longitude = parsePart(parts[1]); Double altitude = null; if (parts.length >= 3) { altitude = Double.parseDouble(parts[2]); } return new GeoPoint(latitude, longitude, altitude, description); } private static double parsePart(String part) { Matcher m = HOUR_MIN_SECOND_PATTERN.matcher(part); if (m.matches()) { String deg = m.group(2); double result = Double.parseDouble(deg); if (m.groupCount() >= 4) { String minutes = m.group(4); result += Double.parseDouble(minutes) / 60.0; if (m.groupCount() >= 6) { String seconds = m.group(6); result += Double.parseDouble(seconds) / (60.0 * 60.0); } } if (m.group(1) != null && m.group(1).equals("-")) { result = -result; } return result; } return Double.parseDouble(part); } public boolean isSouthEastOf(GeoPoint pt) { return isSouthOf(pt) && isEastOf(pt); } private boolean isEastOf(GeoPoint pt) { return longitudinalDistanceTo(pt) > 0; } public boolean isSouthOf(GeoPoint pt) { return getLatitude() < pt.getLatitude(); } public boolean isNorthWestOf(GeoPoint pt) { return isNorthOf(pt) && isWestOf(pt); } private boolean isWestOf(GeoPoint pt) { return longitudinalDistanceTo(pt) < 0; } public double longitudinalDistanceTo(GeoPoint pt) { double me = getLongitude(); double them = pt.getLongitude(); double result = Math.abs(me - them) > 180.0 ? (them - me) : (me - them); if (result > 180.0) { result -= 360.0; } if (result < -180.0) { result += 360.0; } return result; } public boolean isNorthOf(GeoPoint pt) { return getLatitude() > pt.getLatitude(); } public static GeoPoint calculateCenter(List<GeoPoint> geoPoints) { double totalLat = 0.0; double totalLon = 0.0; double totalAlt = 0.0; int altitudeCount = 0; for (GeoPoint geoPoint : geoPoints) { totalLat += geoPoint.getLatitude(); totalLon += geoPoint.getLongitude(); if (geoPoint.getAltitude() != null) { totalAlt += geoPoint.getAltitude(); altitudeCount++; } } return new GeoPoint( totalLat / (double) geoPoints.size(), totalLon / (double) geoPoints.size(), totalAlt / (double) altitudeCount ); } }