- Timestamp:
- 2010-10-24T10:25:12+02:00 (14 years ago)
- Location:
- trunk/src/org/openstreetmap/josm
- Files:
-
- 2 added
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/JoinAreasAction.java
r3595 r3636 2 2 package org.openstreetmap.josm.actions; 3 3 4 import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.combineTigerTags;5 import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.completeTagCollectionForEditing;6 import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing;7 4 import static org.openstreetmap.josm.tools.I18n.marktr; 8 5 import static org.openstreetmap.josm.tools.I18n.tr; … … 12 9 import java.awt.event.KeyEvent; 13 10 import java.awt.geom.Area; 14 import java.awt.geom.Line2D;15 11 import java.util.ArrayList; 16 12 import java.util.Collection; 17 13 import java.util.Collections; 18 import java.util.Comparator;19 14 import java.util.HashMap; 20 15 import java.util.HashSet; … … 39 34 import org.openstreetmap.josm.data.UndoRedoHandler; 40 35 import org.openstreetmap.josm.data.coor.EastNorth; 41 import org.openstreetmap.josm.data.coor.LatLon;42 36 import org.openstreetmap.josm.data.osm.DataSet; 43 37 import org.openstreetmap.josm.data.osm.Node; 38 import org.openstreetmap.josm.data.osm.NodePositionComparator; 44 39 import org.openstreetmap.josm.data.osm.OsmPrimitive; 45 40 import org.openstreetmap.josm.data.osm.Relation; … … 48 43 import org.openstreetmap.josm.data.osm.Way; 49 44 import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog; 45 import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil; 46 import org.openstreetmap.josm.tools.Geometry; 50 47 import org.openstreetmap.josm.tools.Pair; 51 48 import org.openstreetmap.josm.tools.Shortcut; 52 49 50 /** 51 * Join Areas (i.e. closed ways and multipolygons) 52 */ 53 53 public class JoinAreasAction extends JosmAction { 54 54 // This will be used to commit commands and unite them into one large command sequence at the end … … 110 110 //saves a way and the "inside" side 111 111 // insideToTheLeft: if true left side is "in", false -right side is "in". Left and right are determined along the orientation of way. 112 p rivatestatic class WayInPolygon {112 public static class WayInPolygon { 113 113 public final Way way; 114 114 public boolean insideToTheRight; … … 137 137 * 138 138 */ 139 p rivatestatic class AssembledPolygon {139 public static class AssembledPolygon { 140 140 public List<WayInPolygon> ways; 141 141 … … 164 164 } 165 165 166 167 166 public static class AssembledMultipolygon { 168 167 public AssembledPolygon outerWay; … … 209 208 210 209 public WayInPolygon startNewWay() { 211 if (availableWays. size() == 0) {210 if (availableWays.isEmpty()) { 212 211 lastWay = null; 213 212 } else { … … 247 246 //this is the path we came from - ignore it. 248 247 } 249 else if (bestWay == null || ( isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) {248 else if (bestWay == null || (Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) { 250 249 //the new way is better 251 250 bestWay = way; … … 262 261 //this is the path we came from - ignore it. 263 262 } 264 else if (bestWay == null || ( isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) {263 else if (bestWay == null || (Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) { 265 264 //the new way is better 266 265 bestWay = way; … … 291 290 292 291 /** 293 * Provides some node order , based on coordinates, nodes with equal coordinates are equal.294 * @author viesturs295 *296 */297 private class NodePositionComparator implements Comparator<Node> {298 299 @Override300 public int compare(Node n1, Node n2) {301 302 double dLat = n1.getCoor().lat() - n2.getCoor().lat();303 double dLon = n1.getCoor().lon() - n2.getCoor().lon();304 305 if (dLat > 0)306 return 1;307 else if (dLat < 0)308 return -1;309 else if (dLon == 0) //dlat is 0 here310 return 0;311 else312 return dLon > 0 ? 1 : -1;313 }314 }315 316 317 /**318 292 * Helper storage class for finding findOuterWays 319 293 * @author viesturs … … 328 302 } 329 303 } 330 331 304 332 305 // Adds the menu entry, Shortcuts, etc. … … 415 388 } 416 389 417 418 390 /** 419 391 * Tests if the areas have some intersections to join. … … 430 402 431 403 //find intersection points 432 ArrayList<Node> nodes = addIntersections(allStartingWays, true);404 ArrayList<Node> nodes = Geometry.addIntersections(allStartingWays, true, cmds); 433 405 return nodes.size() > 0; 434 406 } … … 466 438 467 439 //find intersection points 468 ArrayList<Node> nodes = addIntersections(allStartingWays, false);440 ArrayList<Node> nodes = Geometry.addIntersections(allStartingWays, false, cmds); 469 441 470 442 //no intersections, return. 471 if (nodes.size() == 0) return result; 443 if (nodes.isEmpty()) 444 return result; 472 445 commitCommands(marktr("Added node on all intersections")); 473 446 … … 529 502 } 530 503 531 532 504 makeCommitsOneAction(marktr("Joined overlapping areas")); 533 505 … … 563 535 TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways); 564 536 TagCollection completeWayTags = new TagCollection(wayTags); 565 combineTigerTags(completeWayTags);566 normalizeTagCollectionBeforeEditing(completeWayTags, ways);537 TagConflictResolutionUtil.combineTigerTags(completeWayTags); 538 TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing(completeWayTags, ways); 567 539 TagCollection tagsToEdit = new TagCollection(completeWayTags); 568 completeTagCollectionForEditing(tagsToEdit);540 TagConflictResolutionUtil.completeTagCollectionForEditing(tagsToEdit); 569 541 570 542 CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance(); … … 596 568 } 597 569 598 599 570 /** 600 571 * This method removes duplicate points (if any) from the input way. … … 659 630 return totalNodesRemoved > 0; 660 631 } 661 662 663 664 /**665 * Will find all intersection and add nodes there for list of given ways. Handles self-intersections too.666 * And make commands to add the intersection points to ways.667 * @param List<Way> - a list of ways to test668 * @return ArrayList<Node> List of new nodes669 * Prerequisite: no two nodes have the same coordinates.670 */671 private ArrayList<Node> addIntersections(List<Way> ways, boolean test) {672 //TODO: this is a bit slow - O( (number of nodes)^2 + numberOfIntersections * numberOfNodes )673 674 //stupid java, cannot instantiate array of generic classes..675 @SuppressWarnings("unchecked")676 ArrayList<Node>[] newNodes = new ArrayList[ways.size()];677 boolean[] changedWays = new boolean[ways.size()];678 679 Set<Node> intersectionNodes = new LinkedHashSet<Node>();680 681 for (int pos = 0; pos < ways.size(); pos ++) {682 newNodes[pos] = new ArrayList<Node>(ways.get(pos).getNodes());683 changedWays[pos] = false;684 }685 686 //iterate over all segment pairs and introduce the intersections687 688 Comparator<Node> coordsComparator = new NodePositionComparator();689 690 int seg1Way = 0;691 int seg1Pos = -1;692 693 while (true) {694 //advance to next segment695 seg1Pos++;696 if (seg1Pos > newNodes[seg1Way].size() - 2) {697 seg1Way++;698 seg1Pos = 0;699 700 if (seg1Way == ways.size()) { //finished701 break;702 }703 }704 705 706 //iterate over secondary segment707 708 int seg2Way = seg1Way;709 int seg2Pos = seg1Pos + 1;//skip the adjacent segment710 711 while (true) {712 713 //advance to next segment714 seg2Pos++;715 if (seg2Pos > newNodes[seg2Way].size() - 2) {716 seg2Way++;717 seg2Pos = 0;718 719 if (seg2Way == ways.size()) { //finished720 break;721 }722 }723 724 //need to get them again every time, because other segments may be changed725 Node seg1Node1 = newNodes[seg1Way].get(seg1Pos);726 Node seg1Node2 = newNodes[seg1Way].get(seg1Pos + 1);727 Node seg2Node1 = newNodes[seg2Way].get(seg2Pos);728 Node seg2Node2 = newNodes[seg2Way].get(seg2Pos + 1);729 730 int commonCount = 0;731 //test if we have common nodes to add.732 if (seg1Node1 == seg2Node1 || seg1Node1 == seg2Node2) {733 commonCount ++;734 735 if (seg1Way == seg2Way &&736 seg1Pos == 0 &&737 seg2Pos == newNodes[seg2Way].size() -2) {738 //do not add - this is first and last segment of the same way.739 } else {740 intersectionNodes.add(seg1Node1);741 }742 }743 744 if (seg1Node2 == seg2Node1 || seg1Node2 == seg2Node2) {745 commonCount ++;746 747 intersectionNodes.add(seg1Node2);748 }749 750 //no common nodes - find intersection751 if (commonCount == 0) {752 LatLon intersection = getLineLineIntersection(753 seg1Node1.getEastNorth().east(), seg1Node1.getEastNorth().north(),754 seg1Node2.getEastNorth().east(), seg1Node2.getEastNorth().north(),755 seg2Node1.getEastNorth().east(), seg2Node1.getEastNorth().north(),756 seg2Node2.getEastNorth().east(), seg2Node2.getEastNorth().north());757 758 if (intersection != null) {759 if (test) {760 intersectionNodes.add(seg2Node1);761 return new ArrayList<Node>(intersectionNodes);762 }763 764 Node newNode = new Node(intersection);765 Node intNode = newNode;766 boolean insertInSeg1 = false;767 boolean insertInSeg2 = false;768 769 //find if the intersection point is at end point of one of the segments, if so use that point770 771 //segment 1772 if (coordsComparator.compare(newNode, seg1Node1) == 0) {773 intNode = seg1Node1;774 } else if (coordsComparator.compare(newNode, seg1Node2) == 0) {775 intNode = seg1Node2;776 } else {777 insertInSeg1 = true;778 }779 780 //segment 2781 if (coordsComparator.compare(newNode, seg2Node1) == 0) {782 intNode = seg2Node1;783 } else if (coordsComparator.compare(newNode, seg2Node2) == 0) {784 intNode = seg2Node2;785 } else {786 insertInSeg2 = true;787 }788 789 if (insertInSeg1) {790 newNodes[seg1Way].add(seg1Pos +1, intNode);791 changedWays[seg1Way] = true;792 793 //fix seg2 position, as indexes have changed, seg2Pos is always bigger than seg1Pos on the same segment.794 if (seg2Way == seg1Way) {795 seg2Pos ++;796 }797 }798 799 if (insertInSeg2) {800 newNodes[seg2Way].add(seg2Pos +1, intNode);801 changedWays[seg2Way] = true;802 803 //Do not need to compare again to already split segment804 seg2Pos ++;805 }806 807 intersectionNodes.add(intNode);808 809 if (intNode == newNode) {810 cmds.add(new AddCommand(intNode));811 }812 }813 }814 else if (test && intersectionNodes.size() > 0)815 return new ArrayList<Node>(intersectionNodes);816 }817 }818 819 for (int pos = 0; pos < ways.size(); pos ++) {820 if (changedWays[pos] == false) {821 continue;822 }823 824 Way way = ways.get(pos);825 Way newWay = new Way(way);826 newWay.setNodes(newNodes[pos]);827 828 cmds.add(new ChangeCommand(way, newWay));829 }830 831 return new ArrayList<Node>(intersectionNodes);832 }833 834 /**835 * Finds the intersection of two lines836 * @return LatLon null if no intersection was found, the LatLon coordinates of the intersection otherwise837 */838 static private LatLon getLineLineIntersection(839 double x1, double y1, double x2, double y2,840 double x3, double y3, double x4, double y4) {841 842 if (!Line2D.linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) return null;843 844 // Convert line from (point, point) form to ax+by=c845 double a1 = y2 - y1;846 double b1 = x1 - x2;847 double c1 = x2*y1 - x1*y2;848 849 double a2 = y4 - y3;850 double b2 = x3 - x4;851 double c2 = x4*y3 - x3*y4;852 853 // Solve the equations854 double det = a1*b2 - a2*b1;855 if (det == 0) return null; // Lines are parallel856 857 return Main.proj.eastNorth2latlon(new EastNorth(858 (b1*c2 - b2*c1)/det,859 (a2*c1 -a1*c2)/det860 ));861 }862 863 864 632 865 633 /** … … 884 652 } 885 653 886 887 654 /** 888 655 * This method analyzes the way and assigns each part what direction polygon "inside" is. … … 950 717 Node nextNode = way.getNode(1); 951 718 952 if (topWay == null || ! isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) {719 if (topWay == null || !Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) { 953 720 //the new way is better 954 721 topWay = way; … … 962 729 Node nextNode = way.getNode(way.getNodesCount() - 2); 963 730 964 if (topWay == null || ! isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) {731 if (topWay == null || !Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) { 965 732 //the new way is better 966 733 topWay = way; … … 976 743 977 744 //there will be no parallel segments in the middle of way, so all fine. 978 wayClockwise = angleIsClockwise(prev, topNode, next);745 wayClockwise = Geometry.angleIsClockwise(prev, topNode, next); 979 746 } 980 747 … … 999 766 break; 1000 767 } 1001 1002 768 1003 769 //find intersecting segments … … 1037 803 Node wayBNode = wayB.getNode(1); 1038 804 1039 boolean wayAToTheRight = isToTheRightSideOfLine(prevNode, headNode, nextNode, wayANode);1040 boolean wayBToTheRight = isToTheRightSideOfLine(prevNode, headNode, nextNode, wayBNode);805 boolean wayAToTheRight = Geometry.isToTheRightSideOfLine(prevNode, headNode, nextNode, wayANode); 806 boolean wayBToTheRight = Geometry.isToTheRightSideOfLine(prevNode, headNode, nextNode, wayBNode); 1041 807 1042 808 if (wayAToTheRight != wayBToTheRight) { … … 1084 850 } 1085 851 1086 1087 /**1088 * Simple chunking version. Does not care about circular ways and result beingproper, we will glue it all back together later on.852 /** 853 * Simple chunking version. Does not care about circular ways and result being 854 * proper, we will glue it all back together later on. 1089 855 * @param way the way to chunk 1090 856 * @param splitNodes the places where to cut. … … 1185 951 return result; 1186 952 } 1187 1188 1189 953 1190 954 /** … … 1285 1049 } 1286 1050 1287 1288 1051 /** 1289 1052 * This method checks if polygons have several touching parts and splits them in several polygons. … … 1323 1086 return newPolygons; 1324 1087 } 1325 1326 1327 /**1328 * Tests if given point is to the right side of path consisting of 3 points.1329 * @param lineP1 first point in path1330 * @param lineP2 second point in path1331 * @param lineP3 third point in path1332 * @param testPoint1333 * @return true if to the right side, false otherwise1334 */1335 public static boolean isToTheRightSideOfLine(Node lineP1, Node lineP2, Node lineP3, Node testPoint) {1336 boolean pathBendToRight = angleIsClockwise(lineP1, lineP2, lineP3);1337 boolean rightOfSeg1 = angleIsClockwise(lineP1, lineP2, testPoint);1338 boolean rightOfSeg2 = angleIsClockwise(lineP2, lineP3, testPoint);1339 1340 if (pathBendToRight)1341 return rightOfSeg1 && rightOfSeg2;1342 else1343 return !(!rightOfSeg1 && !rightOfSeg2);1344 }1345 1346 /**1347 * This method tests if secondNode is clockwise to first node.1348 * @param commonNode starting point for both vectors1349 * @param firstNode first vector end node1350 * @param secondNode second vector end node1351 * @return true if first vector is clockwise before second vector.1352 */1353 1354 public static boolean angleIsClockwise(Node commonNode, Node firstNode, Node secondNode) {1355 double dy1 = (firstNode.getEastNorth().getY() - commonNode.getEastNorth().getY());1356 double dy2 = (secondNode.getEastNorth().getY() - commonNode.getEastNorth().getY());1357 double dx1 = (firstNode.getEastNorth().getX() - commonNode.getEastNorth().getX());1358 double dx2 = (secondNode.getEastNorth().getX() - commonNode.getEastNorth().getX());1359 1360 return dy1 * dx2 - dx1 * dy2 > 0;1361 }1362 1363 1088 1364 1089 /** … … 1375 1100 if (!outsideNodes.contains(insideNode)) 1376 1101 //simply test the one node 1377 return nodeInsidePolygon(insideNode, outside.getNodes());1102 return Geometry.nodeInsidePolygon(insideNode, outside.getNodes()); 1378 1103 } 1379 1104 … … 1381 1106 return false; 1382 1107 } 1383 1384 /**1385 * Tests if point is inside a polygon. The polygon can be self-intersecting. In such case the contains function works in xor-like manner.1386 * @param polygonNodes list of nodes from polygon path.1387 * @param point the point to test1388 * @return true if the point is inside polygon.1389 * FIXME: this should probably be moved to tools..1390 */1391 public static boolean nodeInsidePolygon(Node point, List<Node> polygonNodes) {1392 if (polygonNodes.size() < 3)1393 return false;1394 1395 boolean inside = false;1396 Node p1, p2;1397 1398 //iterate each side of the polygon, start with the last segment1399 Node oldPoint = polygonNodes.get(polygonNodes.size() - 1);1400 1401 for (Node newPoint : polygonNodes) {1402 //skip duplicate points1403 if (newPoint.equals(oldPoint)) {1404 continue;1405 }1406 1407 //order points so p1.lat <= p2.lat;1408 if (newPoint.getEastNorth().getY() > oldPoint.getEastNorth().getY()) {1409 p1 = oldPoint;1410 p2 = newPoint;1411 } else {1412 p1 = newPoint;1413 p2 = oldPoint;1414 }1415 1416 //test if the line is crossed and if so invert the inside flag.1417 if ((newPoint.getEastNorth().getY() < point.getEastNorth().getY()) == (point.getEastNorth().getY() <= oldPoint.getEastNorth().getY())1418 && (point.getEastNorth().getX() - p1.getEastNorth().getX()) * (p2.getEastNorth().getY() - p1.getEastNorth().getY())1419 < (p2.getEastNorth().getX() - p1.getEastNorth().getX()) * (point.getEastNorth().getY() - p1.getEastNorth().getY()))1420 {1421 inside = !inside;1422 }1423 1424 oldPoint = newPoint;1425 }1426 1427 return inside;1428 }1429 1430 1108 1431 1109 /** … … 1443 1121 return result; 1444 1122 } 1445 1446 1123 1447 1124 /** … … 1473 1150 } 1474 1151 1475 1476 1152 /** 1477 1153 * Joins a list of ways (using CombineWayAction and ReverseWayAction as specified in WayInPath) … … 1506 1182 return result.a; 1507 1183 } 1508 1509 1184 1510 1185 /** … … 1604 1279 } 1605 1280 1606 1607 1281 /** 1608 1282 * This method filters the list of relations that form the multipolygons. … … 1627 1301 return result; 1628 1302 } 1629 1630 1303 1631 1304 /** … … 1650 1323 } 1651 1324 1652 1653 1325 /** 1654 1326 * Removes a given OsmPrimitive from all relations … … 1685 1357 return result; 1686 1358 } 1687 1688 1359 1689 1360 /**
Note:
See TracChangeset
for help on using the changeset viewer.