private WayInPolygon advanceNextWay(boolean rightmost) { Node headNode = !lastWayReverse ? lastWay.way.lastNode() : lastWay.way.firstNode(); Node prevNode = !lastWayReverse ? lastWay.way.getNode(lastWay.way.getNodesCount() - 2) : lastWay.way.getNode(1); // find best next way WayInPolygon bestWay = null; Node bestWayNextNode = null; boolean bestWayReverse = false; for (WayInPolygon way : availableWays) { if (way.way.firstNode().equals(headNode)) { // start adjacent to headNode Node nextNode = way.way.getNode(1); if (nextNode.equals(prevNode)) { // this is the path we came from - ignore it. } else if (bestWay == null || (Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) { // the new way is better bestWay = way; bestWayReverse = false; bestWayNextNode = nextNode; } } if (way.way.lastNode().equals(headNode)) { // end adjacent to headNode Node nextNode = way.way.getNode(way.way.getNodesCount() - 2); if (nextNode.equals(prevNode)) { // this is the path we came from - ignore it. } else if (bestWay == null || (Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) { // the new way is better bestWay = way; bestWayReverse = true; bestWayNextNode = nextNode; } } } lastWay = bestWay; lastWayReverse = bestWayReverse; return lastWay; }
/** * This method analyzes the way and assigns each part what direction polygon "inside" is. * * @param parts the split parts of the way * @param isInner - if true, reverts the direction (for multipolygon islands) * @return list of parts, marked with the inside orientation. */ private List<WayInPolygon> markWayInsideSide(List<Way> parts, boolean isInner) { List<WayInPolygon> result = new ArrayList<WayInPolygon>(); // prepare prev and next maps Map<Way, Way> nextWayMap = new HashMap<Way, Way>(); Map<Way, Way> prevWayMap = new HashMap<Way, Way>(); for (int pos = 0; pos < parts.size(); pos++) { if (!parts.get(pos).lastNode().equals(parts.get((pos + 1) % parts.size()).firstNode())) throw new RuntimeException("Way not circular"); nextWayMap.put(parts.get(pos), parts.get((pos + 1) % parts.size())); prevWayMap.put(parts.get(pos), parts.get((pos + parts.size() - 1) % parts.size())); } // find the node with minimum y - it's guaranteed to be outer. (What about the south pole?) Way topWay = null; Node topNode = null; int topIndex = 0; double minY = Double.POSITIVE_INFINITY; for (Way way : parts) { for (int pos = 0; pos < way.getNodesCount(); pos++) { Node node = way.getNode(pos); if (node.getEastNorth().getY() < minY) { minY = node.getEastNorth().getY(); topWay = way; topNode = node; topIndex = pos; } } } // get the upper way and it's orientation. boolean wayClockwise; // orientation of the top way. if (topNode.equals(topWay.firstNode()) || topNode.equals(topWay.lastNode())) { Node headNode = null; // the node at junction Node prevNode = null; // last node from previous path wayClockwise = false; // node is in split point - find the outermost way from this point headNode = topNode; // make a fake node that is downwards from head node (smaller Y). It will be a division point // between paths. prevNode = new Node( new EastNorth(headNode.getEastNorth().getX(), headNode.getEastNorth().getY() - 1e5)); topWay = null; wayClockwise = false; Node bestWayNextNode = null; for (Way way : parts) { if (way.firstNode().equals(headNode)) { Node nextNode = way.getNode(1); if (topWay == null || !Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) { // the new way is better topWay = way; wayClockwise = true; bestWayNextNode = nextNode; } } if (way.lastNode().equals(headNode)) { // end adjacent to headNode Node nextNode = way.getNode(way.getNodesCount() - 2); if (topWay == null || !Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) { // the new way is better topWay = way; wayClockwise = false; bestWayNextNode = nextNode; } } } } else { // node is inside way - pick the clockwise going end. Node prev = topWay.getNode(topIndex - 1); Node next = topWay.getNode(topIndex + 1); // there will be no parallel segments in the middle of way, so all fine. wayClockwise = Geometry.angleIsClockwise(prev, topNode, next); } Way curWay = topWay; boolean curWayInsideToTheRight = wayClockwise ^ isInner; // iterate till full circle is reached while (true) { // add cur way WayInPolygon resultWay = new WayInPolygon(curWay, curWayInsideToTheRight); result.add(resultWay); // process next way Way nextWay = nextWayMap.get(curWay); Node prevNode = curWay.getNode(curWay.getNodesCount() - 2); Node headNode = curWay.lastNode(); Node nextNode = nextWay.getNode(1); if (nextWay == topWay) { // full loop traversed - all done. break; } // find intersecting segments // the intersections will look like this: // // ^ // | // X wayBNode // | // wayB | // | // curWay | nextWay // ----X----------------->X----------------------X----> // prevNode ^headNode nextNode // | // | // wayA | // | // X wayANode // | int intersectionCount = 0; for (Way wayA : parts) { if (wayA == curWay) { continue; } if (wayA.lastNode().equals(headNode)) { Way wayB = nextWayMap.get(wayA); // test if wayA is opposite wayB relative to curWay and nextWay Node wayANode = wayA.getNode(wayA.getNodesCount() - 2); Node wayBNode = wayB.getNode(1); boolean wayAToTheRight = Geometry.isToTheRightSideOfLine(prevNode, headNode, nextNode, wayANode); boolean wayBToTheRight = Geometry.isToTheRightSideOfLine(prevNode, headNode, nextNode, wayBNode); if (wayAToTheRight != wayBToTheRight) { intersectionCount++; } } } // if odd number of crossings, invert orientation if (intersectionCount % 2 != 0) { curWayInsideToTheRight = !curWayInsideToTheRight; } curWay = nextWay; } return result; }