Ticket #5599: joinAreas_fixes.diff
File joinAreas_fixes.diff, 30.6 KB (added by , 15 years ago) |
---|
-
src/org/openstreetmap/josm/actions/JoinAreasAction.java
47 47 import org.openstreetmap.josm.tools.Pair; 48 48 import org.openstreetmap.josm.tools.Shortcut; 49 49 50 50 51 /** 51 52 * Join Areas (i.e. closed ways and multipolygons) 52 53 */ … … 401 402 } 402 403 403 404 //find intersection points 404 ArrayList<Node> nodes = Geometry.addIntersections(allStartingWays, true, cmds);405 Set<Node> nodes = Geometry.addIntersections(allStartingWays, true, cmds); 405 406 return nodes.size() > 0; 406 407 } 407 408 … … 437 438 } 438 439 439 440 //find intersection points 440 ArrayList<Node> nodes = Geometry.addIntersections(allStartingWays, false, cmds);441 Set<Node> nodes = Geometry.addIntersections(allStartingWays, false, cmds); 441 442 442 443 //no intersections, return. 443 444 if (nodes.isEmpty()) … … 473 474 //find polygons 474 475 List<AssembledMultipolygon> preparedPolygons = findPolygons(bounadries); 475 476 477 476 478 //assemble final polygons 477 479 List<Multipolygon> polygons = new ArrayList<Multipolygon>(); 480 Set<Relation> relationsToDelete = new LinkedHashSet<Relation>(); 481 478 482 for (AssembledMultipolygon pol : preparedPolygons) { 479 483 480 484 //create the new ways … … 484 488 RelationRole ownMultipolygonRelation = addOwnMultigonRelation(resultPol.innerWays, resultPol.outerWay); 485 489 486 490 //add back the original relations, merged with our new multipolygon relation 487 fixRelations(relations, resultPol.outerWay, ownMultipolygonRelation );491 fixRelations(relations, resultPol.outerWay, ownMultipolygonRelation, relationsToDelete); 488 492 489 493 //strip tags from inner ways 490 494 //TODO: preserve tags on existing inner ways … … 495 499 496 500 commitCommands(marktr("Assemble new polygons")); 497 501 502 for(Relation rel: relationsToDelete) { 503 cmds.add(new DeleteCommand(rel)); 504 } 505 506 commitCommands(marktr("Delete relations")); 507 498 508 // Delete the discarded inner ways 499 509 if (discardedWays.size() > 0) { 500 510 cmds.add(DeleteCommand.delete(Main.map.mapView.getEditLayer(), discardedWays, true)); … … 827 837 * Uses SplitWayAction.splitWay for the heavy lifting. 828 838 * @return list of split ways (or original ways if no splitting is done). 829 839 */ 830 private ArrayList<Way> splitWayOnNodes(Way way, Collection<Node> nodes) {840 private ArrayList<Way> splitWayOnNodes(Way way, Set<Node> nodes) { 831 841 832 842 ArrayList<Way> result = new ArrayList<Way>(); 833 843 List<List<Node>> chunks = buildNodeChunks(way, nodes); … … 1094 1104 */ 1095 1105 public static boolean wayInsideWay(AssembledPolygon inside, AssembledPolygon outside) { 1096 1106 Set<Node> outsideNodes = new HashSet<Node>(outside.getNodes()); 1107 List<Node> insideNodes = inside.getNodes(); 1097 1108 1098 for (Node insideNode : inside .getNodes()) {1109 for (Node insideNode : insideNodes) { 1099 1110 1100 1111 if (!outsideNodes.contains(insideNode)) 1101 1112 //simply test the one node … … 1363 1374 * members of both. This function depends on multigon relations to be valid already, it won't fix them. 1364 1375 * @param ArrayList<RelationRole> List of relations with roles the (original) ways were part of 1365 1376 * @param Way The newly created outer area/way 1377 * @param relationsToDelete - set of relations to delete. 1366 1378 */ 1367 private void fixRelations(ArrayList<RelationRole> rels, Way outer, RelationRole ownMultipol ) {1379 private void fixRelations(ArrayList<RelationRole> rels, Way outer, RelationRole ownMultipol, Set<Relation> relationsToDelete) { 1368 1380 ArrayList<RelationRole> multiouters = new ArrayList<RelationRole>(); 1369 1381 1370 1382 if (ownMultipol != null){ … … 1409 1421 newRel.put(key, r.rel.get(key)); 1410 1422 } 1411 1423 // Delete old relation 1412 cmds.add(new DeleteCommand(r.rel));1424 relationsToDelete.add(r.rel); 1413 1425 } 1414 1426 newRel.addMember(new RelationMember("outer", outer)); 1415 1427 cmds.add(new AddCommand(newRel)); -
src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java
40 40 import org.openstreetmap.josm.gui.layer.Layer; 41 41 import org.openstreetmap.josm.gui.layer.MapViewPaintable; 42 42 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 43 import org.openstreetmap.josm.tools.Geometry; 43 44 import org.openstreetmap.josm.tools.ImageProvider; 44 45 import org.openstreetmap.josm.tools.Shortcut; 45 46 … … 305 306 306 307 //find if the new points overlap existing segments (in case of 90 degree angles) 307 308 Node prevNode = getPreviousNode(selectedSegment.lowerIndex); 308 boolean nodeOverlapsSegment = prevNode != null && segmentsParralel(initialN1en, prevNode.getEastNorth(), initialN1en, newN1en);309 boolean nodeOverlapsSegment = prevNode != null && Geometry.segmentsParralel(initialN1en, prevNode.getEastNorth(), initialN1en, newN1en); 309 310 boolean hasOtherWays = this.hasNodeOtherWays(selectedSegment.getFirstNode(), selectedSegment.way); 310 311 311 312 if (nodeOverlapsSegment && !alwaysCreateNodes && !hasOtherWays) { … … 322 323 323 324 //find if the new points overlap existing segments (in case of 90 degree angles) 324 325 Node nextNode = getNextNode(selectedSegment.lowerIndex + 1); 325 nodeOverlapsSegment = nextNode != null && segmentsParralel(initialN2en, nextNode.getEastNorth(), initialN2en, newN2en);326 nodeOverlapsSegment = nextNode != null && Geometry.segmentsParralel(initialN2en, nextNode.getEastNorth(), initialN2en, newN2en); 326 327 hasOtherWays = hasNodeOtherWays(selectedSegment.getSecondNode(), selectedSegment.way); 327 328 328 329 if (nodeOverlapsSegment && !alwaysCreateNodes && !hasOtherWays) { … … 386 387 */ 387 388 private static EastNorth calculateSegmentOffset(EastNorth segmentP1, EastNorth segmentP2, EastNorth moveDirection, 388 389 EastNorth targetPos) { 389 EastNorth intersectionPoint = getLineLineIntersection(segmentP1, segmentP2, targetPos,390 EastNorth intersectionPoint = Geometry.getLineLineIntersection(segmentP1, segmentP2, targetPos, 390 391 new EastNorth(targetPos.getX() + moveDirection.getX(), targetPos.getY() + moveDirection.getY())); 391 392 392 393 if (intersectionPoint == null) … … 394 395 else 395 396 //return distance form base to target position 396 397 return new EastNorth(targetPos.getX() - intersectionPoint.getX(), 397 398 targetPos.getY() - intersectionPoint.getY()); 398 399 } 399 400 400 /**401 * Finds the intersection of two lines of infinite length.402 * @return EastNorth null if no intersection was found, the coordinates of the intersection otherwise403 */404 public static EastNorth getLineLineIntersection(EastNorth p1, EastNorth p2, EastNorth p3, EastNorth p4) {405 // Convert line from (point, point) form to ax+by=c406 double a1 = p2.getY() - p1.getY();407 double b1 = p1.getX() - p2.getX();408 double c1 = p2.getX() * p1.getY() - p1.getX() * p2.getY();409 401 410 double a2 = p4.getY() - p3.getY();411 double b2 = p3.getX() - p4.getX();412 double c2 = p4.getX() * p3.getY() - p3.getX() * p4.getY();413 414 // Solve the equations415 double det = a1 * b2 - a2 * b1;416 if (det == 0)417 return null; // Lines are parallel418 419 return new EastNorth((b1 * c2 - b2 * c1) / det, (a2 * c1 - a1 * c2) / det);420 }421 422 private static boolean segmentsParralel(EastNorth p1, EastNorth p2, EastNorth p3, EastNorth p4) {423 424 // Convert line from (point, point) form to ax+by=c425 double a1 = p2.getY() - p1.getY();426 double b1 = p1.getX() - p2.getX();427 428 double a2 = p4.getY() - p3.getY();429 double b2 = p3.getX() - p4.getX();430 431 // Solve the equations432 double det = a1 * b2 - a2 * b1;433 return Math.abs(det) < 1e-13;434 }435 436 402 /** 437 403 * Gets a node from selected way before given index. 438 404 * @param index index of current node -
src/org/openstreetmap/josm/data/osm/NodePositionComparator.java
11 11 public class NodePositionComparator implements Comparator<Node> { 12 12 @Override 13 13 public int compare(Node n1, Node n2) { 14 15 if (n1.getCoor().equalsEpsilon(n2.getCoor())) 16 return 0; 17 14 18 double dLat = n1.getCoor().lat() - n2.getCoor().lat(); 15 19 if (dLat > 0) 16 20 return 1; -
src/org/openstreetmap/josm/tools/Geometry.java
4 4 import java.awt.geom.Line2D; 5 5 import java.util.ArrayList; 6 6 import java.util.Comparator; 7 import java.util.HashSet; 7 8 import java.util.LinkedHashSet; 8 9 import java.util.List; 9 10 import java.util.Set; 11 10 12 import org.openstreetmap.josm.Main; 11 13 import org.openstreetmap.josm.command.AddCommand; 12 14 import org.openstreetmap.josm.command.ChangeCommand; 13 15 import org.openstreetmap.josm.command.Command; 14 16 import org.openstreetmap.josm.data.coor.EastNorth; 15 import org.openstreetmap.josm.data. coor.LatLon;17 import org.openstreetmap.josm.data.osm.BBox; 16 18 import org.openstreetmap.josm.data.osm.Node; 17 19 import org.openstreetmap.josm.data.osm.NodePositionComparator; 18 20 import org.openstreetmap.josm.data.osm.Way; … … 23 25 * @author viesturs 24 26 */ 25 27 public class Geometry { 28 public enum PolygonIntersection {FIRST_INSIDE_SECOND, SECOND_INSIDE_FIRST, OUTSIDE, CROSSING} 29 30 31 26 32 /** 27 33 * Will find all intersection and add nodes there for list of given ways. Handles self-intersections too. 28 34 * And make commands to add the intersection points to ways. … … 30 36 * @return ArrayList<Node> List of new nodes 31 37 * Prerequisite: no two nodes have the same coordinates. 32 38 */ 33 public static ArrayList<Node> addIntersections(List<Way> ways, boolean test, List<Command> cmds) { 34 //TODO: this is a bit slow - O( (number of nodes)^2 + numberOfIntersections * numberOfNodes ) 39 public static Set<Node> addIntersections(List<Way> ways, boolean test, List<Command> cmds) { 35 40 36 41 //stupid java, cannot instantiate array of generic classes.. 37 42 @SuppressWarnings("unchecked") 38 43 ArrayList<Node>[] newNodes = new ArrayList[ways.size()]; 44 BBox[] wayBounds = new BBox[ways.size()]; 39 45 boolean[] changedWays = new boolean[ways.size()]; 40 46 41 47 Set<Node> intersectionNodes = new LinkedHashSet<Node>(); 42 48 49 //copy node arrays for local usage. 43 50 for (int pos = 0; pos < ways.size(); pos ++) { 44 51 newNodes[pos] = new ArrayList<Node>(ways.get(pos).getNodes()); 52 wayBounds[pos] = getNodesBounds(newNodes[pos]); 45 53 changedWays[pos] = false; 46 54 } 47 55 48 //iterate over all segment pairs and introduce the intersections 49 56 //iterate over all way pairs and introduce the intersections 50 57 Comparator<Node> coordsComparator = new NodePositionComparator(); 51 58 52 int seg1Way = 0;53 int seg1Pos = -1;59 WayLoop: for (int seg1Way = 0; seg1Way < ways.size(); seg1Way ++) { 60 for (int seg2Way = seg1Way; seg2Way < ways.size(); seg2Way ++) { 54 61 55 while (true) { 56 //advance to next segment 57 seg1Pos++; 58 if (seg1Pos > newNodes[seg1Way].size() - 2) { 59 seg1Way++; 60 seg1Pos = 0; 61 62 if (seg1Way == ways.size()) { //finished 63 break; 62 //do not waste time on bounds that do not intersect 63 if (!wayBounds[seg1Way].intersects(wayBounds[seg2Way])) { 64 continue; 64 65 } 65 }66 66 67 ArrayList<Node> way1Nodes = newNodes[seg1Way]; 68 ArrayList<Node> way2Nodes = newNodes[seg2Way]; 67 69 68 //iterate over secondary segment 70 //iterate over primary segmemt 71 for (int seg1Pos = 0; seg1Pos + 1 < way1Nodes.size(); seg1Pos ++) { 69 72 70 int seg2Way = seg1Way;71 int seg2Pos = seg1Pos + 1;//skip the adjacent segment73 //iterate over secondary segment 74 int seg2Start = seg1Way != seg2Way ? 0: seg1Pos + 2;//skip the adjacent segment 72 75 73 while (true) {76 for (int seg2Pos = seg2Start; seg2Pos + 1< way2Nodes.size(); seg2Pos ++) { 74 77 75 //advance to next segment76 seg2Pos++;77 if (seg2Pos > newNodes[seg2Way].size() - 2) {78 seg2Way++;79 seg2Pos = 0;78 //need to get them again every time, because other segments may be changed 79 Node seg1Node1 = way1Nodes.get(seg1Pos); 80 Node seg1Node2 = way1Nodes.get(seg1Pos + 1); 81 Node seg2Node1 = way2Nodes.get(seg2Pos); 82 Node seg2Node2 = way2Nodes.get(seg2Pos + 1); 80 83 81 if (seg2Way == ways.size()) { //finished82 break;83 }84 }84 int commonCount = 0; 85 //test if we have common nodes to add. 86 if (seg1Node1 == seg2Node1 || seg1Node1 == seg2Node2) { 87 commonCount ++; 85 88 86 //need to get them again every time, because other segments may be changed 87 Node seg1Node1 = newNodes[seg1Way].get(seg1Pos); 88 Node seg1Node2 = newNodes[seg1Way].get(seg1Pos + 1); 89 Node seg2Node1 = newNodes[seg2Way].get(seg2Pos); 90 Node seg2Node2 = newNodes[seg2Way].get(seg2Pos + 1); 89 if (seg1Way == seg2Way && 90 seg1Pos == 0 && 91 seg2Pos == way2Nodes.size() -2) { 92 //do not add - this is first and last segment of the same way. 93 } else { 94 intersectionNodes.add(seg1Node1); 95 } 96 } 91 97 92 int commonCount = 0; 93 //test if we have common nodes to add. 94 if (seg1Node1 == seg2Node1 || seg1Node1 == seg2Node2) { 95 commonCount ++; 98 if (seg1Node2 == seg2Node1 || seg1Node2 == seg2Node2) { 99 commonCount ++; 96 100 97 if (seg1Way == seg2Way && 98 seg1Pos == 0 && 99 seg2Pos == newNodes[seg2Way].size() -2) { 100 //do not add - this is first and last segment of the same way. 101 } else { 102 intersectionNodes.add(seg1Node1); 103 } 104 } 101 intersectionNodes.add(seg1Node2); 102 } 105 103 106 if (seg1Node2 == seg2Node1 || seg1Node2 == seg2Node2) { 107 commonCount ++; 104 //no common nodes - find intersection 105 if (commonCount == 0) { 106 EastNorth intersection = getSegmentSegmentIntersection( 107 seg1Node1.getEastNorth(), seg1Node2.getEastNorth(), 108 seg2Node1.getEastNorth(), seg2Node2.getEastNorth()); 108 109 109 intersectionNodes.add(seg1Node2); 110 } 110 if (intersection != null) { 111 if (test) { 112 intersectionNodes.add(seg2Node1); 113 return intersectionNodes; 114 } 111 115 112 //no common nodes - find intersection 113 if (commonCount == 0) { 114 LatLon intersection = getLineLineIntersection( 115 seg1Node1.getEastNorth().east(), seg1Node1.getEastNorth().north(), 116 seg1Node2.getEastNorth().east(), seg1Node2.getEastNorth().north(), 117 seg2Node1.getEastNorth().east(), seg2Node1.getEastNorth().north(), 118 seg2Node2.getEastNorth().east(), seg2Node2.getEastNorth().north()); 116 Node newNode = new Node(Main.proj.eastNorth2latlon(intersection)); 117 Node intNode = newNode; 118 boolean insertInSeg1 = false; 119 boolean insertInSeg2 = false; 119 120 120 if (intersection != null) { 121 if (test) { 122 intersectionNodes.add(seg2Node1); 123 return new ArrayList<Node>(intersectionNodes); 124 } 121 //find if the intersection point is at end point of one of the segments, if so use that point 125 122 126 Node newNode = new Node(intersection); 127 Node intNode = newNode; 128 boolean insertInSeg1 = false; 129 boolean insertInSeg2 = false; 123 //segment 1 124 if (coordsComparator.compare(newNode, seg1Node1) == 0) { 125 intNode = seg1Node1; 126 } else if (coordsComparator.compare(newNode, seg1Node2) == 0) { 127 intNode = seg1Node2; 128 } else { 129 insertInSeg1 = true; 130 } 130 131 131 //find if the intersection point is at end point of one of the segments, if so use that point 132 //segment 2 133 if (coordsComparator.compare(newNode, seg2Node1) == 0) { 134 intNode = seg2Node1; 135 } else if (coordsComparator.compare(newNode, seg2Node2) == 0) { 136 intNode = seg2Node2; 137 } else { 138 insertInSeg2 = true; 139 } 132 140 133 //segment 1 134 if (coordsComparator.compare(newNode, seg1Node1) == 0) { 135 intNode = seg1Node1; 136 } else if (coordsComparator.compare(newNode, seg1Node2) == 0) { 137 intNode = seg1Node2; 138 } else { 139 insertInSeg1 = true; 140 } 141 if (insertInSeg1) { 142 way1Nodes.add(seg1Pos +1, intNode); 143 changedWays[seg1Way] = true; 141 144 142 //segment 2 143 if (coordsComparator.compare(newNode, seg2Node1) == 0) { 144 intNode = seg2Node1; 145 } else if (coordsComparator.compare(newNode, seg2Node2) == 0) { 146 intNode = seg2Node2; 147 } else { 148 insertInSeg2 = true; 149 } 145 //fix seg2 position, as indexes have changed, seg2Pos is always bigger than seg1Pos on the same segment. 146 if (seg2Way == seg1Way) { 147 seg2Pos ++; 148 } 149 } 150 150 151 if (insertInSeg1) {152 newNodes[seg1Way].add(seg1Pos +1, intNode);153 changedWays[seg1Way] = true;151 if (insertInSeg2) { 152 way2Nodes.add(seg2Pos +1, intNode); 153 changedWays[seg2Way] = true; 154 154 155 //fix seg2 position, as indexes have changed, seg2Pos is always bigger than seg1Pos on the same segment. 156 if (seg2Way == seg1Way) { 157 seg2Pos ++; 158 } 159 } 155 //Do not need to compare again to already split segment 156 seg2Pos ++; 157 } 160 158 161 if (insertInSeg2) { 162 newNodes[seg2Way].add(seg2Pos +1, intNode); 163 changedWays[seg2Way] = true; 159 intersectionNodes.add(intNode); 164 160 165 //Do not need to compare again to already split segment 166 seg2Pos ++; 161 if (intNode == newNode) { 162 cmds.add(new AddCommand(intNode)); 163 } 164 } 167 165 } 168 169 intersectionNodes.add(intNode); 170 171 if (intNode == newNode) { 172 cmds.add(new AddCommand(intNode)); 173 } 166 else if (test && intersectionNodes.size() > 0) 167 return intersectionNodes; 174 168 } 175 169 } 176 else if (test && intersectionNodes.size() > 0)177 return new ArrayList<Node>(intersectionNodes);178 170 } 179 171 } 180 172 173 181 174 for (int pos = 0; pos < ways.size(); pos ++) { 182 175 if (changedWays[pos] == false) { 183 176 continue; … … 190 183 cmds.add(new ChangeCommand(way, newWay)); 191 184 } 192 185 193 return new ArrayList<Node>(intersectionNodes);186 return intersectionNodes; 194 187 } 195 188 196 /**197 * Finds the intersection of two lines198 * @return LatLon null if no intersection was found, the LatLon coordinates of the intersection otherwise199 */200 static private LatLon getLineLineIntersection(201 double x1, double y1, double x2, double y2,202 double x3, double y3, double x4, double y4) {203 189 204 if (!Line2D.linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) return null;205 190 206 // Convert line from (point, point) form to ax+by=c 207 double a1 = y2 - y1; 208 double b1 = x1 - x2; 209 double c1 = x2*y1 - x1*y2; 191 private static BBox getNodesBounds(ArrayList<Node> nodes) { 210 192 211 double a2 = y4 - y3; 212 double b2 = x3 - x4; 213 double c2 = x4*y3 - x3*y4; 193 BBox bounds = new BBox(nodes.get(0)); 214 194 215 // Solve the equations216 double det = a1*b2 - a2*b1;217 if (det == 0) return null; // Lines are parallel195 for(Node n: nodes) { 196 bounds.add(n.getCoor()); 197 } 218 198 219 return Main.proj.eastNorth2latlon(new EastNorth( 220 (b1*c2 - b2*c1)/det, 221 (a2*c1 -a1*c2)/det 222 )); 199 return bounds; 223 200 } 224 201 202 225 203 /** 226 204 * Tests if given point is to the right side of path consisting of 3 points. 227 205 * @param lineP1 first point in path … … 258 236 } 259 237 260 238 /** 239 * Finds the intersection of two line segments 240 * @return EastNorth null if no intersection was found, the EastNorth coordinates of the intersection otherwise 241 */ 242 static public EastNorth getSegmentSegmentIntersection( 243 EastNorth p1, EastNorth p2, 244 EastNorth p3, EastNorth p4) { 245 double x1 = p1.getX(); 246 double y1 = p1.getY(); 247 double x2 = p2.getX(); 248 double y2 = p2.getY(); 249 double x3 = p3.getX(); 250 double y3 = p3.getY(); 251 double x4 = p4.getX(); 252 double y4 = p4.getY(); 253 254 //TODO: do this locally. 255 if (!Line2D.linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) return null; 256 257 // Convert line from (point, point) form to ax+by=c 258 double a1 = y2 - y1; 259 double b1 = x1 - x2; 260 double c1 = x2*y1 - x1*y2; 261 262 double a2 = y4 - y3; 263 double b2 = x3 - x4; 264 double c2 = x4*y3 - x3*y4; 265 266 // Solve the equations 267 double det = a1*b2 - a2*b1; 268 if (det == 0) return null; // Lines are parallel 269 270 double x = (b1*c2 - b2*c1)/det; 271 double y = (a2*c1 -a1*c2)/det; 272 273 return new EastNorth(x, y); 274 } 275 276 /** 277 * Finds the intersection of two lines of infinite length. 278 * @return EastNorth null if no intersection was found, the coordinates of the intersection otherwise 279 */ 280 public static EastNorth getLineLineIntersection(EastNorth p1, EastNorth p2, EastNorth p3, EastNorth p4) { 281 282 // Convert line from (point, point) form to ax+by=c 283 double a1 = p2.getY() - p1.getY(); 284 double b1 = p1.getX() - p2.getX(); 285 double c1 = p2.getX() * p1.getY() - p1.getX() * p2.getY(); 286 287 double a2 = p4.getY() - p3.getY(); 288 double b2 = p3.getX() - p4.getX(); 289 double c2 = p4.getX() * p3.getY() - p3.getX() * p4.getY(); 290 291 // Solve the equations 292 double det = a1 * b2 - a2 * b1; 293 if (det == 0) 294 return null; // Lines are parallel 295 296 return new EastNorth((b1 * c2 - b2 * c1) / det, (a2 * c1 - a1 * c2) / det); 297 } 298 299 public static boolean segmentsParralel(EastNorth p1, EastNorth p2, EastNorth p3, EastNorth p4) { 300 301 // Convert line from (point, point) form to ax+by=c 302 double a1 = p2.getY() - p1.getY(); 303 double b1 = p1.getX() - p2.getX(); 304 305 double a2 = p4.getY() - p3.getY(); 306 double b2 = p3.getX() - p4.getX(); 307 308 // Solve the equations 309 double det = a1 * b2 - a2 * b1; 310 return Math.abs(det) < 1e-13; 311 } 312 313 /** 314 * Calculates closest point to a line segment. 315 * @param segmentP1 316 * @param segmentP2 317 * @param point 318 * @return segmentP1 if it is the closest point, segmentP2 if it is the closest point, 319 * a new point if closest point is between segmentP1 and segmentP2. 320 */ 321 public static EastNorth closestPointToSegment(EastNorth segmentP1, EastNorth segmentP2, EastNorth point) { 322 323 double ldx = segmentP2.getX() - segmentP1.getX(); 324 double ldy = segmentP2.getY() - segmentP1.getY(); 325 326 if (ldx == 0 && ldy == 0) //segment zero length 327 return segmentP1; 328 329 double pdx = point.getX() - segmentP1.getX(); 330 double pdy = point.getY() - segmentP1.getY(); 331 332 double offset = (pdx * ldx + pdy * ldy) / (ldx * ldx + ldy * ldy); 333 334 if (offset <= 0) 335 return segmentP1; 336 else if (offset >= 1) 337 return segmentP2; 338 else 339 return new EastNorth(segmentP1.getX() + ldx * offset, segmentP1.getY() + ldy * offset); 340 341 } 342 343 /** 344 * This method tests if secondNode is clockwise to first node. 345 * @param commonNode starting point for both vectors 346 * @param firstNode first vector end node 347 * @param secondNode second vector end node 348 * @return true if first vector is clockwise before second vector. 349 */ 350 351 public static boolean angleIsClockwise(EastNorth commonNode, EastNorth firstNode, EastNorth secondNode) { 352 double dy1 = (firstNode.getY() - commonNode.getY()); 353 double dy2 = (secondNode.getY() - commonNode.getY()); 354 double dx1 = (firstNode.getX() - commonNode.getX()); 355 double dx2 = (secondNode.getX() - commonNode.getX()); 356 357 return dy1 * dx2 - dx1 * dy2 > 0; 358 } 359 360 361 /** 362 * Tests if two polygons intersect. 363 * @param first 364 * @param second 365 * @return intersection kind 366 * TODO: test segments, not only points 367 * TODO: is O(N*M), should use sweep for better performance. 368 */ 369 public static PolygonIntersection polygonIntersection(List<Node> first, List<Node> second) { 370 Set<Node> firstSet = new HashSet<Node>(first); 371 Set<Node> secondSet = new HashSet<Node>(second); 372 373 int nodesInsideSecond = 0; 374 int nodesOutsideSecond = 0; 375 int nodesInsideFirst = 0; 376 int nodesOutsideFirst = 0; 377 378 for (Node insideNode : first) { 379 if (secondSet.contains(insideNode)) { 380 continue; 381 //ignore touching nodes. 382 } 383 384 if (nodeInsidePolygon(insideNode, second)) { 385 nodesInsideSecond ++; 386 } 387 else { 388 nodesOutsideSecond ++; 389 } 390 } 391 392 for (Node insideNode : second) { 393 if (firstSet.contains(insideNode)) { 394 continue; 395 //ignore touching nodes. 396 } 397 398 if (nodeInsidePolygon(insideNode, first)) { 399 nodesInsideFirst ++; 400 } 401 else { 402 nodesOutsideFirst ++; 403 } 404 } 405 406 if (nodesInsideFirst == 0) { 407 if (nodesInsideSecond == 0){ 408 if (nodesOutsideFirst + nodesInsideSecond > 0) 409 return PolygonIntersection.OUTSIDE; 410 else 411 //all nodes common 412 return PolygonIntersection.CROSSING; 413 } else 414 return PolygonIntersection.FIRST_INSIDE_SECOND; 415 } 416 else 417 { 418 if (nodesInsideSecond == 0) 419 return PolygonIntersection.SECOND_INSIDE_FIRST; 420 else 421 return PolygonIntersection.CROSSING; 422 } 423 } 424 425 426 /** 261 427 * Tests if point is inside a polygon. The polygon can be self-intersecting. In such case the contains function works in xor-like manner. 262 428 * @param polygonNodes list of nodes from polygon path. 263 429 * @param point the point to test 264 430 * @return true if the point is inside polygon. 265 431 */ 266 432 public static boolean nodeInsidePolygon(Node point, List<Node> polygonNodes) { 267 if (polygonNodes.size() < 3)433 if (polygonNodes.size() < 2) 268 434 return false; 269 435 270 436 boolean inside = false;