package de.blau.android.util.mapbox.turf; import java.util.List; import de.blau.android.util.mapbox.geojson.Feature; import de.blau.android.util.mapbox.geojson.FeatureCollection; import de.blau.android.util.mapbox.geojson.Geometry; import de.blau.android.util.mapbox.geojson.LineString; import de.blau.android.util.mapbox.geojson.Point; import de.blau.android.util.mapbox.models.Position; /** * Class contains an assortment of methods used to calculate measurments such as bearing, * destination, midpoint, etc. * * @see <a href="http://turfjs.org/docs/">Turf documentation</a> * @since 1.2.0 */ class TurfMeasurement { /** * Takes two positions and finds the geographic bearing between them. * * @param p1 Starting {@link Position}. * @param p2 Ending {@link Position}. * @return bearing in decimal degrees. * @see <a href="http://turfjs.org/docs/#bearing">Turf Bearing documentation</a> * @since 1.3.0 */ private static double bearing(Position p1, Position p2) { return bearing(Point.fromCoordinates(p1), Point.fromCoordinates(p2)); } /** * Takes two points and finds the geographic bearing between them. * * @param p1 Starting {@link Point}. * @param p2 Ending {@link Point}. * @return bearing in decimal degrees. * @see <a href="http://turfjs.org/docs/#bearing">Turf Bearing documentation</a> * @since 1.2.0 */ public static double bearing(Point p1, Point p2) { double degrees2radians = Math.PI / 180; double radians2degrees = 180 / Math.PI; Position coordinates1 = p1.getCoordinates(); Position coordinates2 = p2.getCoordinates(); double lon1 = degrees2radians * coordinates1.getLongitude(); double lon2 = degrees2radians * coordinates2.getLongitude(); double lat1 = degrees2radians * coordinates1.getLatitude(); double lat2 = degrees2radians * coordinates2.getLatitude(); double a = Math.sin(lon2 - lon1) * Math.cos(lat2); double b = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1); return radians2degrees * Math.atan2(a, b); } /** * Takes a Position and calculates the location of a destination point given a distance in * degrees, radians, miles, or kilometers; and bearing in degrees. This uses the Haversine * formula to account for global curvature. * * @param p1 Starting point. * @param distance Distance from the starting point. * @param bearing Ranging from -180 to 180. * @param units Miles, kilometers, degrees, or radians (defaults kilometers). * @return destination {@link Point} * @throws TurfException TurfException Signals that a Turf exception of some sort has occurred. * @see <a href="http://turfjs.org/docs/#destination">Turf Destination documetation</a> * @since 1.3.0 */ private static Position destination(Position p1, double distance, double bearing, String units) throws TurfException { return destination(Point.fromCoordinates(p1), distance, bearing, units).getCoordinates(); } /** * Takes a Point and calculates the location of a destination point given a distance in * degrees, radians, miles, or kilometers; and bearing in degrees. This uses the Haversine * formula to account for global curvature. * * @param point1 Starting point. * @param distance Distance from the starting point. * @param bearing Ranging from -180 to 180. * @param units Miles, kilometers, degrees, or radians (defaults kilometers). * @return destination {@link Point} * @throws TurfException TurfException Signals that a Turf exception of some sort has occurred. * @see <a href="http://turfjs.org/docs/#destination">Turf Destination documetation</a> * @since 1.2.0 */ public static Point destination(Point point1, double distance, double bearing, String units) throws TurfException { double degrees2radians = Math.PI / 180; double radians2degrees = 180 / Math.PI; Position coordinates1 = point1.getCoordinates(); double longitude1 = degrees2radians * coordinates1.getLongitude(); double latitude1 = degrees2radians * coordinates1.getLatitude(); double bearingRad = degrees2radians * bearing; double radians = TurfHelpers.distanceToRadians(distance, units); double latitude2 = Math.asin(Math.sin(latitude1) * Math.cos(radians) + Math.cos(latitude1) * Math.sin(radians) * Math.cos(bearingRad)); double longitude2 = longitude1 + Math.atan2(Math.sin(bearingRad) * Math.sin(radians) * Math.cos(latitude1), Math.cos(radians) - Math.sin(latitude1) * Math.sin(latitude2)); return Point.fromCoordinates( Position.fromCoordinates(radians2degrees * longitude2, radians2degrees * latitude2)); } /** * Calculates the distance between two positions in degress, radians, miles, or kilometers. This * uses the Haversine formula to account for global curvature. * * @param point1 Origin position. * @param point2 Destination position. * @param units Miles, kilometers, degrees, or radians (defaults kilometers). * @return Distance between the two positions. * @throws TurfException TurfException Signals that a Turf exception of some sort has occurred. * @see <a href="http://turfjs.org/docs/#distance">Turf distance documentation</a> * @since 1.3.0 */ private static double distance(Position point1, Position point2, String units) throws TurfException { return distance(Point.fromCoordinates(point1), Point.fromCoordinates(point2), units); } /** * Calculates the distance between two points in kilometers. This uses the Haversine formula to * account for global curvature. * * @param point1 Origin point. * @param point2 Destination point. * @return Distance between the two points. * @throws TurfException TurfException Signals that a Turf exception of some sort has occurred. * @see <a href="http://turfjs.org/docs/#distance">Turf distance documentation</a> * @since 1.2.0 */ public static double distance(Point point1, Point point2) throws TurfException { return distance(point1, point2, TurfConstants.UNIT_DEFAULT); } /** * Calculates the distance between two points in degress, radians, miles, or kilometers. This * uses the Haversine formula to account for global curvature. * * @param point1 Origin point. * @param point2 Destination point. * @param units Can be degrees, radians, miles, or kilometers (defaults kilometers). * @return Distance between the two points. * @throws TurfException TurfException Signals that a Turf exception of some sort has occurred. * @see <a href="http://turfjs.org/docs/#distance">Turf distance documentation</a> * @since 1.2.0 */ public static double distance(Point point1, Point point2, String units) throws TurfException { double degrees2radians = Math.PI / 180; Position coordinates1 = point1.getCoordinates(); Position coordinates2 = point2.getCoordinates(); double dLat = degrees2radians * (coordinates2.getLatitude() - coordinates1.getLatitude()); double dLon = degrees2radians * (coordinates2.getLongitude() - coordinates1.getLongitude()); double lat1 = degrees2radians * coordinates1.getLatitude(); double lat2 = degrees2radians * coordinates2.getLatitude(); double a = Math.pow(Math.sin(dLat / 2), 2) + Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2); return TurfHelpers.radiansToDistance(2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)), units); } /** * Takes a line and measures its length in the specified units. * * @param line Line to measure. * @param units Can be degrees, radians, miles, or kilometers (defaults kilometers). * @return Length of the input line. * @throws TurfException TurfException Signals that a Turf exception of some sort has occurred. * @see <a href="http://turfjs.org/docs/#linedistance">Turf Line Distance documentation</a> * @since 1.2.0 */ public static double lineDistance(FeatureCollection line, String units) throws TurfException { double d = 0; for (int i = 0; i < line.getFeatures().size(); i++) { d += lineDistance(line.getFeatures().get(i), units); } return d; } /** * Takes a line and measures its length in the specified units. * * @param line Line to measure. * @param units Can be degrees, radians, miles, or kilometers (defaults kilometers). * @return Length of the input line. * @throws TurfException TurfException Signals that a Turf exception of some sort has occurred. * @see <a href="http://turfjs.org/docs/#linedistance">Turf Line Distance documentation</a> * @since 1.2.0 */ private static double lineDistance(Feature line, String units) throws TurfException { return lineDistance(line.getGeometry(), units); } /** * Takes a line and measures its length in the specified units. * * @param line Line to measure. * @param units Can be degrees, radians, miles, or kilometers (defaults kilometers). * @return Length of the input line. * @throws TurfException TurfException Signals that a Turf exception of some sort has occurred. * @see <a href="http://turfjs.org/docs/#linedistance">Turf Line Distance documentation</a> * @since 1.2.0 */ private static double lineDistance(Geometry line, String units) throws TurfException { double d; if (line.getType().equals("LineString")) { List<Position> coordinates = (List<Position>) line.getCoordinates(); return length(coordinates, units); } else if (line.getType().equals("Polygon") || line.getType().equals("MultiLineString")) { List<List<Position>> coordinates = (List<List<Position>>) line.getCoordinates(); d = 0; for (int i = 0; i < coordinates.size(); i++) { d += length(coordinates.get(i), units); } return d; } else if (line.getType().equals("MultiPolygon")) { List<List<List<Position>>> coordinates = (List<List<List<Position>>>) line.getCoordinates(); d = 0; for (int i = 0; i < coordinates.size(); i++) { for (int j = 0; j < coordinates.get(i).size(); j++) { d += length(coordinates.get(i).get(j), units); } } return d; } else { throw new TurfException("Input must be a LineString, MultiLineString, " + "Polygon, or MultiPolygon Feature or Geometry (or a FeatureCollection " + "containing only those types)"); } } private static double length(List<Position> coords, String units) throws TurfException { double travelled = 0; Point prevCoords = Point.fromCoordinates(coords.get(0)); Point curCoords = Point.fromCoordinates(coords.get(0)); Point temp; for (int i = 1; i < coords.size(); i++) { curCoords.setCoordinates(coords.get(i)); travelled += distance(prevCoords, curCoords, units); temp = prevCoords; prevCoords = curCoords; curCoords = temp; } return travelled; } /** * Takes two {@link Position} and returns a position midway between them. The midpoint is * calculated geodesically, meaning the curvature of the earth is taken into account. * * @param from First point. * @param to Second point. * @return A {@link Position} midway between pt1 and pt2. * @throws TurfException TurfException Signals that a Turf exception of some sort has occurred. * @see <a href="http://turfjs.org/docs/#midpoint">Turf Midpoint documentation</a> * @since 1.3.0 */ public static Position midpoint(Position from, Position to) throws TurfException { Point midpointResult = midpoint(Point.fromCoordinates(from), Point.fromCoordinates(to)); return Position.fromCoordinates(midpointResult.getCoordinates().getLongitude(), midpointResult.getCoordinates().getLatitude()); } /** * Takes two {@link Point} and returns a point midway between them. The midpoint is calculated * geodesically, meaning the curvature of the earth is taken into account. * * @param from First point. * @param to Second point. * @return A {@link Point} midway between pt1 and pt2. * @throws TurfException TurfException Signals that a Turf exception of some sort has occurred. * @see <a href="http://turfjs.org/docs/#midpoint">Turf Midpoint documentation</a> * @since 1.3.0 */ private static Point midpoint(Point from, Point to) throws TurfException { double dist = distance(from, to, TurfConstants.UNIT_MILES); double heading = bearing(from, to); return destination(from, dist / 2, heading, TurfConstants.UNIT_MILES); } public static Point along(LineString line, double distance, String units) throws TurfException { List<Position> coords = line.getCoordinates(); double travelled = 0; for (int i = 0; i < coords.size(); i++) { if (distance >= travelled && i == coords.size() - 1) { break; } else if (travelled >= distance) { double overshot = distance - travelled; if (overshot == 0) { return Point.fromCoordinates(coords.get(i)); } else { double direction = bearing(coords.get(i), coords.get(i - 1)) - 180; return Point.fromCoordinates(destination(coords.get(i), overshot, direction, units)); } } else { travelled += distance(coords.get(i), coords.get(i + 1), units); } } return Point.fromCoordinates(coords.get(coords.size() - 1)); } }