package de.westnordost.streetcomplete.util; import de.westnordost.osmapi.map.data.BoundingBox; import de.westnordost.osmapi.map.data.LatLon; import de.westnordost.osmapi.map.data.OsmLatLon; import static java.lang.Math.*; /** Calculate stuff assuming a spherical Earth. The Earth is not spherical, but it is a good * approximation and totally sufficient for our use here. */ public class SphericalEarthMath { /** * In meters. See https://en.wikipedia.org/wiki/Earth_radius#Mean_radius */ public static final double EARTH_RADIUS = 6371000; /** * Calculate a bounding box that contains the given circle. In other words, it is a square * centered at the given position and with a side length of radius*2 * @param center of the circle * @param radius in meters * @return The bounding box that contains the area */ public static BoundingBox enclosingBoundingBox(LatLon center, double radius) { double distance = sqrt(2) * radius; LatLon min = translate(center, distance, 225); LatLon max = translate(center, distance, 45); return new BoundingBox(min, max); } /** @return a new position in the given distance and angle from the original position */ public static LatLon translate(LatLon pos, double distance, double angle) { double φ1 = Math.toRadians(pos.getLatitude()); double λ1 = Math.toRadians(pos.getLongitude()); double α1 = Math.toRadians(angle); double σ12 = distance / EARTH_RADIUS; double y = sin(φ1) * cos(σ12) + cos(φ1) * sin(σ12) * cos(α1); double a = cos(φ1) * cos(σ12) - sin(φ1) * sin(σ12) * cos(α1); double b = sin(σ12) * sin(α1); double x = sqrt(sqr(a) + sqr(b)); double φ2 = atan2(y, x); double λ2 = λ1 + atan2(b, a); return createTranslated(Math.toDegrees(φ2), Math.toDegrees(λ2)); } /** area enclosed in the given bbox in m²*/ public static double enclosedArea(BoundingBox bbox) { LatLon min = bbox.getMin(); LatLon max = bbox.getMax(); LatLon minLatMaxLon = new OsmLatLon(min.getLatitude(), max.getLongitude()); LatLon maxLatMinLon = new OsmLatLon(max.getLatitude(), min.getLongitude()); return distance(min, minLatMaxLon) * distance(min, maxLatMinLon); } private static LatLon createTranslated(double lat, double lon) { if(lon > 180) lon -= 360; else if(lon < -180) lon += 360; boolean crossedPole = false; // north pole if(lat > 90) { lat = 180-lat; crossedPole = true; } // south pole else if(lat < -90) { lat = -180-lat; crossedPole = true; } if(crossedPole) { lon += 180; if(lon > 180) lon -= 360; } return new OsmLatLon(lat, lon); } /** * @return distance between two points in meters */ public static double distance(LatLon pos1, LatLon pos2) { return EARTH_RADIUS * distance( Math.toRadians(pos1.getLatitude()), Math.toRadians(pos1.getLongitude()), Math.toRadians(pos2.getLatitude()), Math.toRadians(pos2.getLongitude() )); } /** @return initial bearing from one point to the other.<br/> * If you take a globe and draw a line straight up to the north pole from pos1 and a * second one that connects pos1 and pos2, this is the angle between those two * lines */ public static double bearing(LatLon pos1, LatLon pos2) { double bearing = Math.toDegrees(bearing( Math.toRadians(pos1.getLatitude()), Math.toRadians(pos1.getLongitude()), Math.toRadians(pos2.getLatitude()), Math.toRadians(pos2.getLongitude()) )); if(bearing < 0) bearing += 360; if(bearing >= 360) bearing -= 360; return bearing; } /** @return final initial bearing from one point to the other.<br/> * If you take a globe and draw a line straight up to the north pole from <em>pos2</em> * and a second one that connects pos1 and pos2 (and goes on straight after this), this * is the angle between those two lines */ public static double finalBearing(LatLon pos1, LatLon pos2) { double bearing = Math.toDegrees(finalBearing( Math.toRadians(pos1.getLatitude()), Math.toRadians(pos1.getLongitude()), Math.toRadians(pos2.getLatitude()), Math.toRadians(pos2.getLongitude()) )); if(bearing < 0) bearing += 360; return bearing; } // https://en.wikipedia.org/wiki/Great-circle_navigation#cite_note-2 private static double distance(double φ1, double λ1, double φ2, double λ2) { double Δλ = λ2 - λ1; double y = sqrt(sqr(cos(φ2)*sin(Δλ)) + sqr(cos(φ1)*sin(φ2) - sin(φ1)*cos(φ2)*cos(Δλ))); double x = sin(φ1)*sin(φ2) + cos(φ1)*cos(φ2)*cos(Δλ); return atan2(y, x); } //See https://en.wikipedia.org/wiki/Great-circle_navigation#Course_and_distance private static double bearing(double φ1, double λ1, double φ2, double λ2) { double Δλ = λ2 - λ1; return Math.atan2(sin(Δλ), cos(φ1) * tan(φ2) - sin(φ1) * cos(Δλ)); } private static double finalBearing(double φ1, double λ1, double φ2, double λ2) { double Δλ = λ2 - λ1; return Math.atan2(sin(Δλ), -cos(φ2)*tan(φ1) + sin(φ1)*cos(Δλ)); } private static double sqr(double x) { return Math.pow(x, 2); } }