package net.osmand.data; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateList; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LinearRing; import net.osmand.osm.edit.Node; import net.osmand.osm.edit.OsmMapUtils; import net.osmand.osm.edit.Way; import net.osmand.util.MapAlgorithms; /** * A ring is a list of CONTIGUOUS ways that form a simple boundary or an area.

* * @author sander */ public class Ring implements Comparable { /** * This is a list of the ways added by the user * The order can be changed with methods from this class */ // private final ArrayList ways; private static final int INDEX_RING_NODES_FAST_CHECK = 100; private static final int INDEX_SIZE = 100; private double[] indexedRingIntervals = null; private List[] indexedRingNodes = null; /** * a concatenation of the ways to form the border * this is NOT necessarily a CLOSED way * The id is random, so this may never leave the Ring object */ private Way border; /** * area can be asked a lot of times when comparing rings, cache it */ private double area = -1; /** * Construct a Ring with a list of ways * * @param ways the ways that make up the Ring */ Ring(Way w) { border = w; indexForFastCheck(); } @SuppressWarnings("unchecked") private void indexForFastCheck() { if(border.getNodes().size() > INDEX_RING_NODES_FAST_CHECK) { // calculate min/max lat double maxLat = Double.MIN_VALUE; double minLat = Double.MAX_VALUE; Node lastNode = null; for(Node n : border.getNodes()) { if(n == null) { continue; } lastNode = n; if(n.getLatitude() > maxLat) { maxLat = n.getLatitude(); } else if(n.getLatitude() < minLat) { minLat = n.getLatitude(); } } maxLat += 0.0001; minLat -= 0.0001; // create interval array [minLat, minLat+interval, ..., maxLat] double interval = (maxLat - minLat) / (INDEX_SIZE - 1); indexedRingIntervals = new double[INDEX_SIZE]; indexedRingNodes = new List[INDEX_SIZE]; for(int i = 0; i < INDEX_SIZE; i++) { indexedRingIntervals[i] = minLat + i * interval; indexedRingNodes[i] = new ArrayList(); } // split nodes by intervals Node prev = lastNode; for(int i = 0; i < border.getNodes().size(); i++) { Node current = border.getNodes().get(i); if(current == null) { continue; } int i1 = getIndexedLessOrEq(current.getLatitude()); int i2 = getIndexedLessOrEq(prev.getLatitude()); int min, max; if(i1 > i2) { min = i2; max = i1; } else { min = i1; max = i2; } for (int j = min; j <= max; j++) { indexedRingNodes[j].add(prev); indexedRingNodes[j].add(current); } prev = current; } } } private int getIndexedLessOrEq(double latitude) { int ind1 = Arrays.binarySearch(indexedRingIntervals, latitude); if(ind1 < 0) { ind1 = -(ind1 + 1); } return ind1; } /** * check if this ring is closed by nature * * @return true if this ring is closed, false otherwise */ public boolean isClosed() { return border.getFirstNodeId() == border.getLastNodeId(); } public List getBorder() { return border.getNodes(); } /** * check if this Ring contains the node * * @param n the Node to check * @return yes if the node is inside the ring */ public boolean containsNode(Node n) { return containsPoint(n.getLatitude(), n.getLongitude()); } /** * check if this Ring contains the point * * @param latitude lat of the point * @param longitude lon of the point * @return yes if the point is inside the ring */ public boolean containsPoint(double latitude, double longitude) { if(indexedRingIntervals != null) { int intersections = 0; int indx = getIndexedLessOrEq(latitude); if(indx == 0 || indx >= indexedRingNodes.length) { return false; } List lst = indexedRingNodes[indx]; for (int k = 0; k < lst.size(); k += 2) { Node first = lst.get(k); Node last = lst.get(k + 1); if (OsmMapUtils.ray_intersect_lon(first, last, latitude, longitude) != -360.0d) { intersections++; } } return intersections % 2 == 1; } return MapAlgorithms.containsPoint(getBorder(), latitude, longitude); } /** * Check if this is in Ring r * @param r the ring to check * @return true if this Ring is inside Ring r (false if it is undetermined) */ public boolean speedIsIn(Ring r) { /* * bi-directional check is needed because some concave rings can intersect * and would only fail on one of the checks */ List points = this.getBorder(); if(points.size() < 2) { return false; } double minlat = points.get(0).getLatitude(); double maxlat = points.get(0).getLatitude(); double minlon = points.get(0).getLongitude(); double maxlon = points.get(0).getLongitude(); // r should contain all nodes of this for (Node n : points) { minlat = Math.min(n.getLatitude(), minlat); maxlat = Math.max(n.getLatitude(), maxlat); minlon = Math.min(n.getLongitude(), minlon); maxlon = Math.max(n.getLongitude(), maxlon); } // r should contain all nodes of this if (!r.containsPoint(minlat, minlon)) { return false; } if (!r.containsPoint(maxlat, minlon)) { return false; } if (!r.containsPoint(minlat, maxlon)) { return false; } if (!r.containsPoint(maxlat, maxlon)) { return false; } // this should not contain a node from r for (Node n : r.getBorder()) { if(n.getLatitude() > minlat && n.getLatitude() < maxlat && n.getLongitude() > minlon && n.getLongitude() < maxlon) { return false; } } return true; } /** * Check if this is in Ring r * * @param r the ring to check * @return true if this Ring is inside Ring r */ public boolean isIn(Ring r) { if(speedIsIn(r)) { return true; } /* * bi-directional check is needed because some concave rings can intersect * and would only fail on one of the checks */ List points = this.getBorder(); // r should contain all nodes of this for (Node n : points) { if (!r.containsNode(n)) { return false; } } points = r.getBorder(); // this should not contain a node from r for (Node n : points) { if (this.containsNode(n)) { return false; } } return true; } /** * If this Ring is not complete * (some ways are not initialized * because they are not included in the OSM file)

* * We are trying to close this Ring by using the other Ring.

* * The other Ring must be complete, and the part of this Ring * inside the other Ring must also be complete. * * @param other the other Ring (which is complete) used to close this one */ public void closeWithOtherRing(Ring other) { List thisBorder = getBorder(); List thisSwitchPoints = new ArrayList(); boolean insideOther = other.containsNode(thisBorder.get(0)); // Search the node pairs for which the ring goes inside or out the other for (int i = 0; i < thisBorder.size(); i++) { Node n = thisBorder.get(i); if (other.containsNode(n) != insideOther) { // we are getting out or in the boundary now. // toggle switch insideOther = !insideOther; thisSwitchPoints.add(i); } } List otherSwitchPoints = new ArrayList(); // Search the according node pairs in the other ring for (int i : thisSwitchPoints) { LatLon a = thisBorder.get(i - 1).getLatLon(); LatLon b = thisBorder.get(i).getLatLon(); otherSwitchPoints.add(getTheSegmentRingIntersectsSegment(a, b)); } /* * TODO: * * * Split the other Ring into ways from splitPoint to splitPoint * * * Split this ring into ways from splitPoint to splitPoint * * * Filter out the parts of way from this that are inside the other Ring * Use the insideOther var and the switchPoints list for this. * * * For each two parts of way from this, search a part of way connecting the two. * If there are two, take the shortest. */ } /** * Get the segment of the Ring that intersects a segment * going from point a to point b * * @param a the begin point of the segment * @param b the end point of the segment * @return an integer i which is the index so that the segment * from getBorder().get(i-1) to getBorder().get(i) intersects with * the segment from parameters a to b.

*

* 0 if the segment from a to b doesn't intersect with the Ring. */ private int getTheSegmentRingIntersectsSegment(LatLon a, LatLon b) { List border = getBorder(); for (int i = 1; i < border.size(); i++) { LatLon c = border.get(i - 1).getLatLon(); LatLon d = border.get(i).getLatLon(); if (MapAlgorithms.linesIntersect( a.getLatitude(), a.getLongitude(), b.getLatitude(), b.getLongitude(), c.getLatitude(), c.getLongitude(), d.getLatitude(), d.getLongitude())) { return i; } } return 0; } public double getArea() { if (area == -1) { //cache the area area = OsmMapUtils.getArea(getBorder()); } return area; } public LinearRing toLinearRing() { GeometryFactory geometryFactory = new GeometryFactory(); CoordinateList coordinates = new CoordinateList(); for (Node node : border.getNodes()) { coordinates.add(new Coordinate(node.getLatitude(), node.getLongitude()), true); } coordinates.closeRing(); return geometryFactory.createLinearRing(coordinates.toCoordinateArray()); } /** * Use area size as comparable metric */ @Override public int compareTo(Ring r) { return Double.compare(getArea(), r.getArea()); } }