package de.blau.android.util.mapbox.turf; import java.util.ArrayList; import java.util.List; import de.blau.android.util.mapbox.geojson.Feature; import de.blau.android.util.mapbox.geojson.LineString; import de.blau.android.util.mapbox.geojson.Point; import de.blau.android.util.mapbox.models.Position; import de.blau.android.util.mapbox.turf.models.LineIntersectsResult; /** * Class contains all the miscellaneous methods that Turf can perform. * * @see <a href="http://turfjs.org/docs/">Turf documentation</a> * @since 1.2.0 */ class TurfMisc { /** * Takes a line, a start {@link Point}, and a stop point and returns the line in between those * points. * * @param startPt Starting point. * @param stopPt Stopping point. * @param line Line to slice. * @return Sliced line. * @throws TurfException signals that a Turf exception of some sort has occurred. * @see <a href="http://turfjs.org/docs/#lineslice">Turf Line slice documentation</a> * @since 1.2.0 */ public static LineString lineSlice(Point startPt, Point stopPt, Feature line) throws TurfException { if (!line.getGeometry().getType().equals("LineString")) { throw new TurfException("input must be a LineString Feature or Geometry"); } return lineSlice(startPt, stopPt, (LineString) line.getGeometry()); } /** * Takes a line, a start {@link Point}, and a stop point and returns the line in between those * points. * * @param startPt Starting point. * @param stopPt Stopping point. * @param line Line to slice. * @return Sliced line. * @throws TurfException signals that a Turf exception of some sort has occurred. * @see <a href="http://turfjs.org/docs/#lineslice">Turf Line slice documentation</a> * @since 1.2.0 */ private static LineString lineSlice(Point startPt, Point stopPt, LineString line) throws TurfException { List<Position> coords = line.getCoordinates(); Feature startVertex = pointOnLine(startPt, coords); Feature stopVertex = pointOnLine(stopPt, coords); List<Feature> ends = new ArrayList<>(); if ((int) startVertex.getNumberProperty("index") <= (int) stopVertex.getNumberProperty("index")) { ends.add(startVertex); ends.add(stopVertex); } else { ends.add(stopVertex); ends.add(startVertex); } List<Position> positions = new ArrayList<>(); positions.add(((Point) ends.get(0).getGeometry()).getCoordinates()); LineString clipLine = LineString.fromCoordinates(positions); for (int i = (int) ends.get(0).getNumberProperty("index") + 1; i < (int) ends.get(1).getNumberProperty("index") + 1; i++) { List<Position> coordinates = clipLine.getCoordinates(); coordinates.add(coords.get(i)); clipLine.setCoordinates(coordinates); } List<Position> coordinates = clipLine.getCoordinates(); coordinates.add(((Point) ends.get(1).getGeometry()).getCoordinates()); clipLine.setCoordinates(coordinates); return clipLine; } /** * Takes a {@link Point} and a {@link LineString} and calculates the closest Point on the * LineString. * * @param pt point to snap from * @param coords line to snap to * @return closest point on the line to point * @throws TurfException signals that a Turf exception of some sort has occurred. * @since 1.3.0 */ private static Feature pointOnLine(Point pt, List<Position> coords) throws TurfException { String units = TurfConstants.UNIT_MILES; Feature closestPt = Feature.fromGeometry( Point.fromCoordinates( Position.fromCoordinates(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY))); closestPt.addNumberProperty("dist", Double.POSITIVE_INFINITY); for (int i = 0; i < coords.size() - 1; i++) { Feature start = Feature.fromGeometry(Point.fromCoordinates(coords.get(i))); Feature stop = Feature.fromGeometry(Point.fromCoordinates(coords.get(i + 1))); //start start.addNumberProperty("dist", TurfMeasurement.distance(pt, (Point) start.getGeometry(), units)); //stop stop.addNumberProperty("dist", TurfMeasurement.distance(pt, (Point) stop.getGeometry(), units)); //perpendicular double direction = TurfMeasurement.bearing((Point) start.getGeometry(), (Point) stop.getGeometry()); Feature perpendicularPt = Feature.fromGeometry( TurfMeasurement.destination(pt, 1000, direction + 90, units)); // 1000 = gross LineIntersectsResult intersect = lineIntersects( pt.getCoordinates().getLongitude(), pt.getCoordinates().getLatitude(), ((Point) perpendicularPt.getGeometry()).getCoordinates().getLongitude(), ((Point) perpendicularPt.getGeometry()).getCoordinates().getLatitude(), ((Point) start.getGeometry()).getCoordinates().getLongitude(), ((Point) start.getGeometry()).getCoordinates().getLatitude(), ((Point) stop.getGeometry()).getCoordinates().getLongitude(), ((Point) stop.getGeometry()).getCoordinates().getLatitude() ); if (intersect == null) { perpendicularPt = Feature.fromGeometry( TurfMeasurement.destination(pt, 1000, direction - 90, units)); // 1000 = gross intersect = lineIntersects( pt.getCoordinates().getLongitude(), pt.getCoordinates().getLatitude(), ((Point) perpendicularPt.getGeometry()).getCoordinates().getLongitude(), ((Point) perpendicularPt.getGeometry()).getCoordinates().getLatitude(), ((Point) start.getGeometry()).getCoordinates().getLongitude(), ((Point) start.getGeometry()).getCoordinates().getLatitude(), ((Point) stop.getGeometry()).getCoordinates().getLongitude(), ((Point) stop.getGeometry()).getCoordinates().getLatitude() ); } perpendicularPt.addNumberProperty("dist", Double.POSITIVE_INFINITY); Feature intersectPt = null; if (intersect != null) { intersectPt = Feature.fromGeometry( Point.fromCoordinates(Position.fromCoordinates(intersect.getY(), intersect.getX()))); intersectPt.addNumberProperty("dist", TurfMeasurement.distance(pt, (Point) intersectPt.getGeometry(), units)); } if ((double) start.getNumberProperty("dist") < (double) closestPt.getNumberProperty("dist")) { closestPt = start; closestPt.addNumberProperty("index", i); } if ((double) stop.getNumberProperty("dist") < (double) closestPt.getNumberProperty("dist")) { closestPt = stop; closestPt.addNumberProperty("index", i); } if (intersectPt != null && (double) intersectPt.getNumberProperty("dist") < (double) closestPt.getNumberProperty("dist")) { closestPt = intersectPt; closestPt.addNumberProperty("index", i); } } return closestPt; } private static LineIntersectsResult lineIntersects(double line1StartX, double line1StartY, double line1EndX, double line1EndY, double line2StartX, double line2StartY, double line2EndX, double line2EndY) { // If the lines intersect, the result contains the x and y of the intersection // (treating the lines as infinite) and booleans for whether line segment 1 or line // segment 2 contain the point double denominator; double a; double b; double numerator1; double numerator2; LineIntersectsResult result = new LineIntersectsResult(); denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY)); if (denominator == 0) { if (result.getX() != null && result.getY() != null) { return result; } else { return null; } } a = line1StartY - line2StartY; b = line1StartX - line2StartX; numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b); numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b); a = numerator1 / denominator; b = numerator2 / denominator; // if we cast these lines infinitely in both directions, they intersect here: result.setX(line1StartX + (a * (line1EndX - line1StartX))); result.setY(line1StartY + (a * (line1EndY - line1StartY))); // if line1 is a segment and line2 is infinite, they intersect if: if (a > 0 && a < 1) { result.setOnLine1(true); } // if line2 is a segment and line1 is infinite, they intersect if: if (b > 0 && b < 1) { result.setOnLine2(true); } // if line1 and line2 are segments, they intersect if both of the above are true if (result.isOnLine1() && result.isOnLine2()) { return result; } else { return null; } } }