package org.droidplanner.services.android.impl.core.helpers.geoTools; import org.droidplanner.services.android.impl.core.helpers.units.Area; import org.droidplanner.services.android.impl.core.polygon.Polygon; import com.o3dr.services.android.lib.coordinate.LatLong; import com.o3dr.services.android.lib.coordinate.LatLongAlt; import com.o3dr.services.android.lib.util.MathUtils; import java.util.List; import static java.lang.Math.PI; import static java.lang.Math.abs; import static java.lang.Math.atan2; import static java.lang.Math.cos; import static java.lang.Math.sin; import static java.lang.Math.tan; import static java.lang.Math.toRadians; public class GeoTools { private static final double RADIUS_OF_EARTH = 6378137.0;// In meters. // Source: WGS84 public List<LatLong> waypoints; public GeoTools() { } /** * Returns the distance between two points * * @return distance between the points in degrees */ public static Double getAproximatedDistance(LatLong p1, LatLong p2) { return (Math.hypot((p1.getLatitude() - p2.getLatitude()), (p1.getLongitude() - p2.getLongitude()))); } private static Double metersTolat(double meters) { return Math.toDegrees(meters / RADIUS_OF_EARTH); } public static Double latToMeters(double lat) { return Math.toRadians(lat) * RADIUS_OF_EARTH; } /** * Extrapolate latitude/longitude given a heading and distance thanks to * http://www.movable-type.co.uk/scripts/latlong.html * * @param origin Point of origin * @param bearing bearing to navigate * @param distance distance to be added * @return New point with the added distance */ public static LatLong newCoordFromBearingAndDistance(LatLong origin, double bearing, double distance) { return newCoordFromBearingAndDistance(origin.getLatitude(), origin.getLongitude(), bearing, distance); } /** * Extrapolate latitude/longitude given a heading and distance thanks to * http://www.movable-type.co.uk/scripts/latlong.html * * @param lat latitude * @param lon longitude * @param bearing bearing to navigate * @param distance distance to be added * @return New point with the added distance */ private static LatLong newCoordFromBearingAndDistance(double lat, double lon, double bearing, double distance) { double lat1 = Math.toRadians(lat); double lon1 = Math.toRadians(lon); double brng = Math.toRadians(bearing); double dr = distance / RADIUS_OF_EARTH; double lat2 = Math.asin(Math.sin(lat1) * Math.cos(dr) + Math.cos(lat1) * Math.sin(dr) * Math.cos(brng)); double lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(dr) * Math.cos(lat1), Math.cos(dr) - Math.sin(lat1) * Math.sin(lat2)); return (new LatLong(Math.toDegrees(lat2), Math.toDegrees(lon2))); } /** * Offset a coordinate by a local distance * * @param origin location in WGS84 * @param xMeters Offset distance in the east direction * @param yMeters Offset distance in the north direction * @return new coordinate with the offset */ public static LatLong moveCoordinate(LatLong origin, double xMeters, double yMeters) { double lon = origin.getLongitude(); double lat = origin.getLatitude(); double lon1 = Math.toRadians(lon); double lat1 = Math.toRadians(lat); double lon2 = lon1 + Math.toRadians(metersTolat(xMeters)); double lat2 = lat1 + Math.toRadians(metersTolat(yMeters)); return (new LatLong(Math.toDegrees(lat2), Math.toDegrees(lon2))); } /** * Calculates the arc between two points * http://en.wikipedia.org/wiki/Haversine_formula * * @return the arc in degrees */ static double getArcInRadians(LatLong from, LatLong to) { double latitudeArc = Math.toRadians(from.getLatitude() - to.getLatitude()); double longitudeArc = Math.toRadians(from.getLongitude() - to.getLongitude()); double latitudeH = Math.sin(latitudeArc * 0.5); latitudeH *= latitudeH; double lontitudeH = Math.sin(longitudeArc * 0.5); lontitudeH *= lontitudeH; double tmp = Math.cos(Math.toRadians(from.getLatitude())) * Math.cos(Math.toRadians(to.getLatitude())); return Math.toDegrees(2.0 * Math.asin(Math.sqrt(latitudeH + tmp * lontitudeH))); } /** * Computes the distance between two coordinates * * @return distance in meters */ public static double getDistance(LatLong from, LatLong to) { return RADIUS_OF_EARTH * Math.toRadians(getArcInRadians(from, to)); } /** * Computes the distance between two coordinates taking in account the * height difference * * @return distance in meters */ public static double get3DDistance(LatLongAlt end, LatLongAlt start) { double horizontalDistance = getDistance(end, start); double altitudeDiff = Math.abs((end.getAltitude() - start.getAltitude())); return MathUtils.hypot(horizontalDistance, altitudeDiff); } /** * Computes the heading between two coordinates * * @return heading in degrees */ public static double getHeadingFromCoordinates(LatLong fromLoc, LatLong toLoc) { double fLat = Math.toRadians(fromLoc.getLatitude()); double fLng = Math.toRadians(fromLoc.getLongitude()); double tLat = Math.toRadians(toLoc.getLatitude()); double tLng = Math.toRadians(toLoc.getLongitude()); double degree = Math.toDegrees(Math.atan2( Math.sin(tLng - fLng) * Math.cos(tLat), Math.cos(fLat) * Math.sin(tLat) - Math.sin(fLat) * Math.cos(tLat) * Math.cos(tLng - fLng))); return warpToPositiveAngle(degree); } public static double warpToPositiveAngle(double degree) { if (degree >= 0) { return degree; } else { return 360 + degree; } } /** * Copied from android-map-utils (licensed under Apache v2) * com.google.maps.android.SphericalUtil.java * * @return area in m� */ public static Area getArea(Polygon poly) { List<LatLong> path = poly.getPoints(); int size = path.size(); if (size < 3) { return new Area(0); } double total = 0; LatLong prev = path.get(size - 1); double prevTanLat = tan((PI / 2 - toRadians(prev.getLatitude())) / 2); double prevLng = toRadians(prev.getLongitude()); // For each edge, accumulate the signed area of the triangle formed by // the North Pole // and that edge ("polar triangle"). for (LatLong point : path) { double tanLat = tan((PI / 2 - toRadians(point.getLatitude())) / 2); double lng = toRadians(point.getLongitude()); total += polarTriangleArea(tanLat, lng, prevTanLat, prevLng); prevTanLat = tanLat; prevLng = lng; } return new Area(abs(total * (RADIUS_OF_EARTH * RADIUS_OF_EARTH))); } /** * Copied from android-map-utils (licensed under Apache v2) * com.google.maps.android.SphericalUtil.java * <p/> * Returns the signed area of a triangle which has North Pole as a vertex. * Formula derived from * "Area of a spherical triangle given two edges and the included angle" as * per "Spherical Trigonometry" by Todhunter, page 71, section 103, point 2. * See http://books.google.com/books?id=3uBHAAAAIAAJ&pg=PA71 The arguments * named "tan" are tan((pi/2 - latitude)/2). */ private static double polarTriangleArea(double tan1, double lng1, double tan2, double lng2) { double deltaLng = lng1 - lng2; double t = tan1 * tan2; return 2 * atan2(t * sin(deltaLng), 1 + t * cos(deltaLng)); } public static LatLong pointAlongTheLine(LatLong start, LatLong end, int distance) { return newCoordFromBearingAndDistance(start, getHeadingFromCoordinates(start, end), distance); } }