Changeset 15574 in josm
- Timestamp:
- 2019-12-09T09:47:20+01:00 (6 years ago)
- Location:
- trunk
- Files:
-
- 4 added
- 3 edited
-
src/org/openstreetmap/josm/actions/CombineWayAction.java (modified) (7 diffs)
-
src/org/openstreetmap/josm/data/osm/NodeGraph.java (modified) (5 diffs)
-
test/data/regress/18367 (added)
-
test/data/regress/18367/nocombine.osm (added)
-
test/data/regress/18367/silent-revert.osm (added)
-
test/data/regress/18367/split-and-reverse.osm (added)
-
test/unit/org/openstreetmap/josm/actions/CombineWayActionTest.java (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java
r15555 r15574 32 32 import org.openstreetmap.josm.data.osm.TagCollection; 33 33 import org.openstreetmap.josm.data.osm.Way; 34 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 35 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay; 34 36 import org.openstreetmap.josm.data.preferences.BooleanProperty; 37 import org.openstreetmap.josm.data.validation.Test; 38 import org.openstreetmap.josm.data.validation.tests.OverlappingWays; 39 import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay; 35 40 import org.openstreetmap.josm.gui.ExtendedDialog; 36 41 import org.openstreetmap.josm.gui.MainApplication; … … 123 128 124 129 // try to build a new way which includes all the combined ways 125 NodeGraph graph = NodeGraph.createNearlyUndirectedGraphFromNodeWays(ways); 126 List<Node> path = graph.buildSpanningPathNoRemove(); 127 if (path == null) { 130 List<Node> path = tryJoin(ways); 131 if (path.isEmpty()) { 128 132 warnCombiningImpossible(); 129 133 return null; … … 137 141 List<Way> reversedWays = new LinkedList<>(); 138 142 List<Way> unreversedWays = new LinkedList<>(); 139 for (Way w: ways) { 140 // Treat zero or one-node ways as unreversed as Combine action action is a good way to fix them (see #8971) 141 if (w.getNodesCount() < 2 || (path.indexOf(w.getNode(0)) + 1) == path.lastIndexOf(w.getNode(1))) { 142 unreversedWays.add(w); 143 } else { 144 reversedWays.add(w); 145 } 146 } 143 detectReversedWays(ways, path, reversedWays, unreversedWays); 147 144 // reverse path if all ways have been reversed 148 145 if (unreversedWays.isEmpty()) { … … 164 161 } 165 162 // if there are still reversed ways with direction-dependent tags, reverse their tags 166 if (!reversedWays.isEmpty() && PROP_REVERSE_WAY.get()) { 163 if (!reversedWays.isEmpty() && Boolean.TRUE.equals(PROP_REVERSE_WAY.get())) { 167 164 List<Way> unreversedTagWays = new ArrayList<>(ways); 168 165 unreversedTagWays.removeAll(reversedWays); … … 211 208 212 209 return new Pair<>(targetWay, sequenceCommand); 210 } 211 212 protected static void detectReversedWays(Collection<Way> ways, List<Node> path, List<Way> reversedWays, 213 List<Way> unreversedWays) { 214 for (Way w: ways) { 215 // Treat zero or one-node ways as unreversed as Combine action action is a good way to fix them (see #8971) 216 if (w.getNodesCount() < 2) { 217 unreversedWays.add(w); 218 } else { 219 boolean foundStartSegment = false; 220 int last = path.lastIndexOf(w.getNode(0)); 221 222 for (int i = path.indexOf(w.getNode(0)); i <= last; i++) { 223 if (path.get(i) == w.getNode(0) && i + 1 < path.size() && w.getNode(1) == path.get(i + 1)) { 224 foundStartSegment = true; 225 break; 226 } 227 } 228 if (foundStartSegment) { 229 unreversedWays.add(w); 230 } else { 231 reversedWays.add(w); 232 } 233 } 234 } 235 } 236 237 protected static List<Node> tryJoin(Collection<Way> ways) { 238 List<Node> path = joinWithMultipolygonCode(ways); 239 if (path.isEmpty()) { 240 NodeGraph graph = NodeGraph.createNearlyUndirectedGraphFromNodeWays(ways); 241 path = graph.buildSpanningPathNoRemove(); 242 } 243 return path; 244 } 245 246 /** 247 * Use {@link Multipolygon#joinWays(Collection)} to join ways. 248 * @param ways the ways 249 * @return List of nodes of the combined ways or null if ways could not be combined to a single way. 250 * Result may contain overlapping segments. 251 */ 252 private static List<Node> joinWithMultipolygonCode(Collection<Way> ways) { 253 // sort so that old unclosed ways appear first 254 LinkedList<Way> toJoin = new LinkedList<>(ways); 255 toJoin.sort((o1, o2) -> { 256 int d = Boolean.compare(o1.isNew(), o2.isNew()); 257 if (d == 0) 258 d = Boolean.compare(o1.isClosed(), o2.isClosed()); 259 return d; 260 }); 261 Collection<JoinedWay> list = Multipolygon.joinWays(toJoin); 262 if (list.size() == 1) { 263 // ways form a single line string 264 return new ArrayList<>(list.iterator().next().getNodes()); 265 } 266 return Collections.emptyList(); 213 267 } 214 268 … … 238 292 if (combineResult == null) 239 293 return; 294 240 295 final Way selectedWay = combineResult.a; 241 296 UndoRedoHandler.getInstance().add(combineResult.b); 297 Test test = new OverlappingWays(); 298 test.startTest(null); 299 test.visit(combineResult.a); 300 test.endTest(); 301 if (test.getErrors().isEmpty()) { 302 test = new SelfIntersectingWay(); 303 test.startTest(null); 304 test.visit(combineResult.a); 305 test.endTest(); 306 } 307 if (!test.getErrors().isEmpty()) { 308 new Notification(test.getErrors().get(0).getMessage()) 309 .setIcon(JOptionPane.WARNING_MESSAGE) 310 .setDuration(Notification.TIME_SHORT) 311 .show(); 312 } 242 313 if (selectedWay != null) { 243 314 GuiHelper.runInEDT(() -> ds.setSelected(selectedWay)); … … 262 333 setEnabled(numWays >= 2); 263 334 } 335 264 336 } -
trunk/src/org/openstreetmap/josm/data/osm/NodeGraph.java
r15559 r15574 12 12 import java.util.LinkedHashMap; 13 13 import java.util.LinkedHashSet; 14 import java.util.LinkedList;15 14 import java.util.List; 16 15 import java.util.Map; … … 18 17 import java.util.Optional; 19 18 import java.util.Set; 20 import java.util.Stack;21 19 import java.util.TreeMap; 22 20 … … 249 247 } 250 248 251 protected List<Node> buildPathFromNodePairs( Stack<NodePair> path) {252 List<Node> ret = new LinkedList<>();253 for (NodePair pair: path) { 249 protected List<Node> buildPathFromNodePairs(Deque<NodePair> path) { 250 List<Node> ret = new ArrayList<>(path.size() + 1); 251 for (NodePair pair : path) { 254 252 ret.add(pair.getA()); 255 253 } 256 ret.add(path.peek().getB()); 254 ret.add(path.peekLast().getB()); 257 255 return ret; 258 256 } … … 264 262 * 265 263 * @param startNode the start node 266 * @return the spanning path; null,if no path is found264 * @return the spanning path; empty list if no path is found 267 265 */ 268 266 protected List<Node> buildSpanningPath(Node startNode) { 269 267 if (startNode != null) { 270 // do not simply replace `Stack` by `ArrayDeque` because of different iteration behaviour, see #11957 271 Stack<NodePair> path = new Stack<>(); 268 Deque<NodePair> path = new ArrayDeque<>(); 272 269 Set<NodePair> dupCheck = new HashSet<>(); 273 Stack<NodePair> nextPairs = newStack<>();270 Deque<NodePair> nextPairs = new ArrayDeque<>(); 274 271 nextPairs.addAll(getOutboundPairs(startNode)); 275 272 while (!nextPairs.isEmpty()) { 276 NodePair cur = nextPairs. pop();273 NodePair cur = nextPairs.removeLast(); 277 274 if (!dupCheck.contains(cur) && !dupCheck.contains(cur.swap())) { 278 while (!path.isEmpty() && !path.peek().isPredecessorOf(cur)) { 279 dupCheck.remove(path. pop());275 while (!path.isEmpty() && !path.peekLast().isPredecessorOf(cur)) { 276 dupCheck.remove(path.removeLast()); 280 277 } 281 path. push(cur);278 path.addLast(cur); 282 279 dupCheck.add(cur); 283 if (isSpanningWay(path)) return buildPathFromNodePairs(path); 284 nextPairs.addAll(getOutboundPairs(path.peek())); 280 if (isSpanningWay(path)) 281 return buildPathFromNodePairs(path); 282 nextPairs.addAll(getOutboundPairs(path.peekLast())); 285 283 } 286 284 } … … 324 322 * any duplicated edge was removed. 325 323 * 326 * @return the path; null,if no pathwas foundor duplicated edges were found327 * @since 155 55324 * @return List of nodes that build the path; an empty list if no path or duplicated edges were found 325 * @since 15573 (return value not null) 328 326 */ 329 327 public List<Node> buildSpanningPathNoRemove() { 330 if (edges.size() != addedEdges) 331 return null; 332 return buildSpanningPath(); 328 List<Node> path = null; 329 if (edges.size() == addedEdges) 330 path = buildSpanningPath(); 331 return path == null ? Collections.emptyList() : path; 333 332 } 334 333 -
trunk/test/unit/org/openstreetmap/josm/actions/CombineWayActionTest.java
r15558 r15574 3 3 4 4 import static org.junit.Assert.assertEquals; 5 import static org.junit.Assert.assertNull; 6 import static org.junit.Assert.assertTrue; 5 import static org.junit.Assert.assertFalse; 7 6 8 7 import java.io.IOException; 9 8 import java.io.InputStream; 10 9 import java.util.ArrayList; 10 import java.util.Collection; 11 11 import java.util.Collections; 12 12 import java.util.HashSet; 13 import java.util.LinkedList; 13 14 import java.util.List; 14 15 import java.util.Set; … … 19 20 import org.openstreetmap.josm.data.osm.DataSet; 20 21 import org.openstreetmap.josm.data.osm.Node; 21 import org.openstreetmap.josm.data.osm.NodeGraph;22 22 import org.openstreetmap.josm.data.osm.NodePair; 23 23 import org.openstreetmap.josm.data.osm.Way; … … 50 50 try (InputStream is = TestUtils.getRegressionDataStream(11957, "data.osm")) { 51 51 DataSet ds = OsmReader.parseDataSet(is, null); 52 NodeGraph graph = NodeGraph.createNearlyUndirectedGraphFromNodeWays(ds.getWays()); 53 List<Node> path = graph.buildSpanningPathNoRemove(); 52 List<Node> path = CombineWayAction.tryJoin(ds.getWays()); 54 53 assertEquals(10, path.size()); 55 54 Set<Long> firstAndLastObtained = new HashSet<>(); … … 72 71 try (InputStream is = TestUtils.getRegressionDataStream(18385, "data.osm")) { 73 72 DataSet ds = OsmReader.parseDataSet(is, null); 74 NodeGraph graph = NodeGraph.createNearlyUndirectedGraphFromNodeWays(ds.getWays()); 75 List<Node> path = graph.buildSpanningPathNoRemove(); 76 assertNull(path); 73 List<Node> path = CombineWayAction.tryJoin(ds.getWays()); 74 assertFalse(path.isEmpty()); 77 75 } 78 76 } … … 91 89 if (!selection.get(0).isNew()) 92 90 Collections.reverse(selection); 93 NodeGraph graph = NodeGraph.createNearlyUndirectedGraphFromNodeWays(selection); 94 List<Node> path = graph.buildSpanningPathNoRemove(); 95 assertTrue(path != null); 91 double expectedLen = getOriginalLength(selection); 92 List<Node> path = CombineWayAction.tryJoin(selection); 93 assertFalse(path.isEmpty()); 94 Way combined = new Way(0); 95 combined.setNodes(path); 96 assertEquals(expectedLen, combined.getLength(), 0.01); 96 97 } 97 98 } 99 100 /** 101 * Non-regression test for bug #18367 (Lines cannot be combined when they share an overlapping segment) 102 * @throws IOException if any I/O error occurs 103 * @throws IllegalDataException if OSM parsing fails 104 */ 105 @Test 106 public void testTicket18367() throws IOException, IllegalDataException { 107 try (InputStream is = TestUtils.getRegressionDataStream(18367, "nocombine.osm")) { 108 DataSet ds = OsmReader.parseDataSet(is, null); 109 ArrayList<Way> selection = new ArrayList<>(ds.getWays()); 110 assertEquals(2, selection.size()); 111 double expectedLen = getOriginalLength(selection); 112 List<Node> path = CombineWayAction.tryJoin(selection); 113 assertFalse(path.isEmpty()); 114 Way combined = new Way(0); 115 combined.setNodes(path); 116 assertEquals(expectedLen, combined.getLength(), 1e-7); 117 } 118 } 119 120 121 /** 122 * Non-regression test for bug #18367 123 * @throws IOException if any I/O error occurs 124 * @throws IllegalDataException if OSM parsing fails 125 */ 126 @Test 127 public void testTicket18367NeedsSplit() throws IOException, IllegalDataException { 128 try (InputStream is = TestUtils.getRegressionDataStream(18367, "split-and-reverse.osm")) { 129 DataSet ds = OsmReader.parseDataSet(is, null); 130 ArrayList<Way> selection = new ArrayList<>(ds.getWays()); 131 assertEquals(2, selection.size()); 132 double expectedLen = getOriginalLength(selection); 133 List<Node> path = CombineWayAction.tryJoin(selection); 134 assertFalse(path.isEmpty()); 135 Way combined = new Way(0); 136 combined.setNodes(path); 137 assertEquals(expectedLen, combined.getLength(), 1e-7); 138 List<Way> reversedWays = new LinkedList<>(); 139 List<Way> unreversedWays = new LinkedList<>(); 140 CombineWayAction.detectReversedWays(selection, path, reversedWays, unreversedWays); 141 assertFalse(reversedWays.isEmpty()); 142 } 143 } 144 145 146 /** 147 * Test for bad reverse way detection. See #18367 148 * @throws IOException if any I/O error occurs 149 * @throws IllegalDataException if OSM parsing fails 150 */ 151 @Test 152 public void testDetectReversedWays() throws IOException, IllegalDataException { 153 try (InputStream is = TestUtils.getRegressionDataStream(18367, "silent-revert.osm")) { 154 DataSet ds = OsmReader.parseDataSet(is, null); 155 ArrayList<Way> selection = new ArrayList<>(ds.getWays()); 156 assertEquals(2, selection.size()); 157 // make sure that short way is first 158 if (selection.get(0).getNodesCount() != 2) 159 Collections.reverse(selection); 160 double expectedLen = getOriginalLength(selection); 161 List<Node> path = CombineWayAction.tryJoin(selection); 162 assertFalse(path.isEmpty()); 163 Way combined = new Way(0); 164 combined.setNodes(path); 165 assertEquals(expectedLen, combined.getLength(), 1e-7); 166 List<Way> reversedWays = new LinkedList<>(); 167 List<Way> unreversedWays = new LinkedList<>(); 168 CombineWayAction.detectReversedWays(selection, path, reversedWays, unreversedWays); 169 assertFalse(reversedWays.contains(selection.get(0))); 170 } 171 } 172 98 173 99 174 /** … … 107 182 .verify(); 108 183 } 184 185 private static double getOriginalLength(Collection<Way> ways) { 186 double len = 0; 187 for (Way w : ways) { 188 len += w.getLength(); 189 } 190 return len; 191 } 192 109 193 }
Note:
See TracChangeset
for help on using the changeset viewer.
