diff --git src/org/openstreetmap/josm/actions/JoinAreasAction.java src/org/openstreetmap/josm/actions/JoinAreasAction.java
index 3db02d2..9fa84d4 100644
|
|
public class JoinAreasAction extends JosmAction {
|
159 | 159 | |
160 | 160 | return nodes; |
161 | 161 | } |
| 162 | |
| 163 | /** |
| 164 | * Inverse inside and outside |
| 165 | */ |
| 166 | public void reverse() { |
| 167 | for(WayInPolygon way: ways) |
| 168 | way.insideToTheRight = !way.insideToTheRight; |
| 169 | Collections.reverse(ways); |
| 170 | } |
162 | 171 | } |
163 | 172 | |
164 | 173 | public static class AssembledMultipolygon { |
… |
… |
public class JoinAreasAction extends JosmAction {
|
193 | 202 | availableWays.removeAll(ways); |
194 | 203 | } |
195 | 204 | |
196 | | public boolean hasWays() { |
197 | | return !availableWays.isEmpty(); |
| 205 | public void removeWay(WayInPolygon way) { |
| 206 | availableWays.remove(way); |
198 | 207 | } |
199 | 208 | |
200 | | public WayInPolygon startNewWay(WayInPolygon way) { |
| 209 | public void setStartWay(WayInPolygon way) { |
201 | 210 | lastWay = way; |
202 | | lastWayReverse = !lastWay.insideToTheRight; |
203 | | |
204 | | return lastWay; |
| 211 | lastWayReverse = !way.insideToTheRight; |
205 | 212 | } |
206 | 213 | |
207 | 214 | public WayInPolygon startNewWay() { |
… |
… |
public class JoinAreasAction extends JosmAction {
|
215 | 222 | return lastWay; |
216 | 223 | } |
217 | 224 | |
218 | | |
219 | | public WayInPolygon advanceNextLeftmostWay() { |
220 | | return advanceNextWay(false); |
221 | | } |
222 | | |
223 | | public WayInPolygon advanceNextRightmostWay() { |
224 | | return advanceNextWay(true); |
225 | | } |
226 | | |
227 | | private WayInPolygon advanceNextWay(boolean rightmost) { |
228 | | |
| 225 | /** |
| 226 | * Get the next way creating a clockwise path, ensure it is the most right way. #7959 |
| 227 | * @return The next way. |
| 228 | */ |
| 229 | public WayInPolygon walk() { |
229 | 230 | Node headNode = !lastWayReverse ? lastWay.way.lastNode() : lastWay.way.firstNode(); |
230 | 231 | Node prevNode = !lastWayReverse ? lastWay.way.getNode(lastWay.way.getNodesCount() - 2) : lastWay.way.getNode(1); |
231 | 232 | |
| 233 | double headAngle = Math.atan2(headNode.getEastNorth().east() - prevNode.getEastNorth().east(), |
| 234 | headNode.getEastNorth().north() - prevNode.getEastNorth().north()); |
| 235 | double bestAngle = 0; |
| 236 | |
232 | 237 | //find best next way |
233 | 238 | WayInPolygon bestWay = null; |
234 | | Node bestWayNextNode = null; |
235 | 239 | boolean bestWayReverse = false; |
236 | 240 | |
237 | 241 | for (WayInPolygon way : availableWays) { |
238 | | if (way.way.firstNode().equals(headNode)) { |
239 | | //start adjacent to headNode |
240 | | Node nextNode = way.way.getNode(1); |
| 242 | Node nextNode; |
241 | 243 | |
242 | | if (nextNode.equals(prevNode)) |
243 | | { |
244 | | //this is the path we came from - ignore it. |
245 | | } |
246 | | else if (bestWay == null || (Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) { |
247 | | //the new way is better |
248 | | bestWay = way; |
249 | | bestWayReverse = false; |
250 | | bestWayNextNode = nextNode; |
251 | | } |
| 244 | // Check for a connected way |
| 245 | if (way.way.firstNode().equals(headNode) && way.insideToTheRight) { |
| 246 | nextNode = way.way.getNode(1); |
| 247 | } else if (way.way.lastNode().equals(headNode) && !way.insideToTheRight) { |
| 248 | nextNode = way.way.getNode(way.way.getNodesCount() - 2); |
| 249 | } else { |
| 250 | continue; |
252 | 251 | } |
253 | 252 | |
254 | | if (way.way.lastNode().equals(headNode)) { |
255 | | //end adjacent to headNode |
256 | | Node nextNode = way.way.getNode(way.way.getNodesCount() - 2); |
| 253 | if(nextNode == prevNode) { |
| 254 | // go back |
| 255 | lastWay = way; |
| 256 | lastWayReverse = !way.insideToTheRight; |
| 257 | return lastWay; |
| 258 | } |
257 | 259 | |
258 | | if (nextNode.equals(prevNode)) { |
259 | | //this is the path we came from - ignore it. |
260 | | } |
261 | | else if (bestWay == null || (Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) { |
262 | | //the new way is better |
263 | | bestWay = way; |
264 | | bestWayReverse = true; |
265 | | bestWayNextNode = nextNode; |
266 | | } |
| 260 | double angle = Math.atan2(nextNode.getEastNorth().east() - headNode.getEastNorth().east(), |
| 261 | nextNode.getEastNorth().north() - headNode.getEastNorth().north()) - headAngle; |
| 262 | if(angle > Math.PI) |
| 263 | angle -= 2*Math.PI; |
| 264 | if(angle <= -Math.PI) |
| 265 | angle += 2*Math.PI; |
| 266 | |
| 267 | // Now we have a valid candidate way, is it better than the previous one ? |
| 268 | if (bestWay == null || angle > bestAngle) { |
| 269 | //the new way is better |
| 270 | bestWay = way; |
| 271 | bestWayReverse = !way.insideToTheRight; |
| 272 | bestAngle = angle; |
267 | 273 | } |
268 | 274 | } |
269 | 275 | |
… |
… |
public class JoinAreasAction extends JosmAction {
|
272 | 278 | |
273 | 279 | return lastWay; |
274 | 280 | } |
275 | | |
276 | | public boolean isLastWayInsideToTheRight() { |
277 | | return lastWayReverse != lastWay.insideToTheRight; |
278 | | } |
279 | | |
280 | | public Node getLastWayStartNode() { |
281 | | return lastWayReverse ? lastWay.way.lastNode() : lastWay.way.firstNode(); |
282 | | } |
283 | | |
284 | | public Node getLastWayEndNode() { |
285 | | return lastWayReverse ? lastWay.way.firstNode() : lastWay.way.lastNode(); |
286 | | } |
287 | 281 | } |
288 | 282 | |
289 | 283 | /** |
… |
… |
public class JoinAreasAction extends JosmAction {
|
317 | 311 | public void actionPerformed(ActionEvent e) { |
318 | 312 | LinkedList<Way> ways = new LinkedList<Way>(Main.main.getCurrentDataSet().getSelectedWays()); |
319 | 313 | addedRelations.clear(); |
320 | | |
| 314 | |
321 | 315 | if (ways.isEmpty()) { |
322 | 316 | new Notification( |
323 | 317 | tr("Please select at least one closed way that should be joined.")) |
… |
… |
public class JoinAreasAction extends JosmAction {
|
380 | 374 | cmds.addAll(CreateMultipolygonAction.removeTagsFromWaysIfNeeded(r)); |
381 | 375 | } |
382 | 376 | commitCommands(tr("Move tags from ways to relations")); |
383 | | |
| 377 | |
384 | 378 | List<Way> allWays = new ArrayList<Way>(); |
385 | 379 | for (Multipolygon pol : result.polygons) { |
386 | 380 | allWays.add(pol.outerWay); |
… |
… |
public class JoinAreasAction extends JosmAction {
|
968 | 962 | public static List<AssembledPolygon> findBoundaryPolygons(Collection<WayInPolygon> multigonWays, List<Way> discardedResult) { |
969 | 963 | //first find all discardable ways, by getting outer shells. |
970 | 964 | //this will produce incorrect boundaries in some cases, but second pass will fix it. |
971 | | |
972 | 965 | List<WayInPolygon> discardedWays = new ArrayList<WayInPolygon>(); |
973 | | Set<WayInPolygon> processedWays = new HashSet<WayInPolygon>(); |
974 | | WayTraverser traverser = new WayTraverser(multigonWays); |
975 | | |
976 | | for (WayInPolygon startWay : multigonWays) { |
977 | | if (processedWays.contains(startWay)) { |
978 | | continue; |
979 | | } |
980 | 966 | |
981 | | traverser.startNewWay(startWay); |
982 | | |
983 | | List<WayInPolygon> boundary = new ArrayList<WayInPolygon>(); |
984 | | WayInPolygon lastWay = startWay; |
985 | | |
986 | | while (true) { |
987 | | boundary.add(lastWay); |
988 | | |
989 | | WayInPolygon bestWay = traverser.advanceNextLeftmostWay(); |
990 | | boolean wayInsideToTheRight = bestWay == null ? false : traverser.isLastWayInsideToTheRight(); |
| 967 | // In multigonWays collection, some way are just a point (i.e. way like nodeA-nodeA) |
| 968 | // This seems to appear when is apply over invalid way like #9911 test-case |
| 969 | // Remove all of these way to make the next work. |
| 970 | ArrayList<WayInPolygon> cleanMultigonWays = new ArrayList<WayInPolygon>(); |
| 971 | for(WayInPolygon way: multigonWays) |
| 972 | if(way.way.getNodesCount() == 2 && way.way.firstNode() == way.way.lastNode()) |
| 973 | discardedWays.add(way); |
| 974 | else |
| 975 | cleanMultigonWays.add(way); |
| 976 | |
| 977 | WayTraverser traverser = new WayTraverser(cleanMultigonWays); |
| 978 | List<AssembledPolygon> result = new ArrayList<AssembledPolygon>(); |
991 | 979 | |
992 | | if (bestWay == null || processedWays.contains(bestWay) || !wayInsideToTheRight) { |
993 | | //bad segment chain - proceed to discard it |
994 | | lastWay = null; |
995 | | break; |
996 | | } else if (boundary.contains(bestWay)) { |
997 | | //traversed way found - close the way |
998 | | lastWay = bestWay; |
| 980 | WayInPolygon startWay; |
| 981 | while((startWay = traverser.startNewWay()) != null) { |
| 982 | ArrayList<WayInPolygon> path = new ArrayList<WayInPolygon>(); |
| 983 | path.add(startWay); |
| 984 | while(true) { |
| 985 | WayInPolygon nextWay = traverser.walk(); |
| 986 | if(nextWay == null) |
| 987 | throw new RuntimeException("Join areas internal error."); |
| 988 | if(path.get(0) == nextWay) { |
| 989 | AssembledPolygon ring = new AssembledPolygon(path); |
| 990 | if(ring.getNodes().size() <= 2) { |
| 991 | // Invalid ring (2 nodes) -> remove |
| 992 | traverser.removeWays(path); |
| 993 | for(WayInPolygon way: path) |
| 994 | discardedResult.add(way.way); |
| 995 | } else { |
| 996 | // Close ring -> add |
| 997 | result.add(ring); |
| 998 | traverser.removeWays(path); |
| 999 | } |
999 | 1000 | break; |
1000 | | } else { |
1001 | | //proceed to next segment |
1002 | | lastWay = bestWay; |
1003 | 1001 | } |
1004 | | } |
1005 | | |
1006 | | if (lastWay != null) { |
1007 | | //way good |
1008 | | processedWays.addAll(boundary); |
1009 | | |
1010 | | //remove junk segments at the start |
1011 | | while (boundary.get(0) != lastWay) { |
1012 | | discardedWays.add(boundary.get(0)); |
1013 | | boundary.remove(0); |
| 1002 | if(path.contains(nextWay)) { |
| 1003 | // Inner loop -> remove |
| 1004 | int index = path.indexOf(nextWay); |
| 1005 | while(path.size() > index) { |
| 1006 | WayInPolygon currentWay = path.get(index); |
| 1007 | discardedResult.add(currentWay.way); |
| 1008 | traverser.removeWay(currentWay); |
| 1009 | path.remove(index); |
| 1010 | } |
| 1011 | traverser.setStartWay(path.get(index-1)); |
| 1012 | } else { |
| 1013 | path.add(nextWay); |
1014 | 1014 | } |
1015 | | } else { |
1016 | | //way bad |
1017 | | discardedWays.addAll(boundary); |
1018 | | processedWays.addAll(boundary); |
1019 | 1015 | } |
1020 | 1016 | } |
1021 | 1017 | |
1022 | | //now we have removed junk segments, collect the real result ways |
1023 | | |
1024 | | traverser.removeWays(discardedWays); |
1025 | | |
1026 | | List<AssembledPolygon> result = new ArrayList<AssembledPolygon>(); |
1027 | | |
1028 | | while (traverser.hasWays()) { |
1029 | | |
1030 | | WayInPolygon startWay = traverser.startNewWay(); |
1031 | | List<WayInPolygon> boundary = new ArrayList<WayInPolygon>(); |
1032 | | WayInPolygon curWay = startWay; |
1033 | | |
1034 | | do { |
1035 | | boundary.add(curWay); |
1036 | | curWay = traverser.advanceNextRightmostWay(); |
1037 | | |
1038 | | //should not happen |
1039 | | if (curWay == null || !traverser.isLastWayInsideToTheRight()) |
1040 | | throw new RuntimeException("Join areas internal error."); |
1041 | | |
1042 | | } while (curWay != startWay); |
1043 | | |
1044 | | //build result |
1045 | | traverser.removeWays(boundary); |
1046 | | result.add(new AssembledPolygon(boundary)); |
1047 | | } |
1048 | | |
1049 | | for (WayInPolygon way : discardedWays) { |
1050 | | discardedResult.add(way.way); |
1051 | | } |
1052 | | |
1053 | | //split inner polygons that have several touching parts. |
1054 | | result = fixTouchingPolygons(result); |
1055 | | |
1056 | | return result; |
| 1018 | return fixTouchingPolygons(result); |
1057 | 1019 | } |
1058 | 1020 | |
1059 | 1021 | /** |
1060 | 1022 | * This method checks if polygons have several touching parts and splits them in several polygons. |
1061 | 1023 | * @param polygons the polygons to process. |
1062 | 1024 | */ |
1063 | | public static List<AssembledPolygon> fixTouchingPolygons(List<AssembledPolygon> polygons) |
1064 | | { |
| 1025 | public static List<AssembledPolygon> fixTouchingPolygons(List<AssembledPolygon> polygons) { |
1065 | 1026 | List<AssembledPolygon> newPolygons = new ArrayList<AssembledPolygon>(); |
1066 | 1027 | |
1067 | | for (AssembledPolygon innerPart : polygons) { |
1068 | | WayTraverser traverser = new WayTraverser(innerPart.ways); |
1069 | | |
1070 | | while (traverser.hasWays()) { |
1071 | | |
1072 | | WayInPolygon startWay = traverser.startNewWay(); |
1073 | | List<WayInPolygon> boundary = new ArrayList<WayInPolygon>(); |
1074 | | WayInPolygon curWay = startWay; |
1075 | | |
1076 | | Node startNode = traverser.getLastWayStartNode(); |
1077 | | boundary.add(curWay); |
1078 | | |
1079 | | while (startNode != traverser.getLastWayEndNode()) { |
1080 | | curWay = traverser.advanceNextLeftmostWay(); |
1081 | | boundary.add(curWay); |
1082 | | |
1083 | | //should not happen |
1084 | | if (curWay == null || !traverser.isLastWayInsideToTheRight()) |
| 1028 | for (AssembledPolygon ring : polygons) { |
| 1029 | ring.reverse(); |
| 1030 | WayTraverser traverser = new WayTraverser(ring.ways); |
| 1031 | WayInPolygon startWay; |
| 1032 | |
| 1033 | while((startWay = traverser.startNewWay()) != null) { |
| 1034 | List<WayInPolygon> simpleRingWays = new ArrayList<WayInPolygon>(); |
| 1035 | simpleRingWays.add(startWay); |
| 1036 | WayInPolygon nextWay; |
| 1037 | while((nextWay = traverser.walk()) != startWay) { |
| 1038 | if(nextWay == null) |
1085 | 1039 | throw new RuntimeException("Join areas internal error."); |
| 1040 | simpleRingWays.add(nextWay); |
1086 | 1041 | } |
1087 | | |
1088 | | //build result |
1089 | | traverser.removeWays(boundary); |
1090 | | newPolygons.add(new AssembledPolygon(boundary)); |
| 1042 | traverser.removeWays(simpleRingWays); |
| 1043 | AssembledPolygon simpleRing = new AssembledPolygon(simpleRingWays); |
| 1044 | simpleRing.reverse(); |
| 1045 | newPolygons.add(simpleRing); |
1091 | 1046 | } |
1092 | 1047 | } |
1093 | 1048 | |