- Timestamp:
- 2020-12-07T11:52:15+01:00 (4 years ago)
- Location:
- trunk
- Files:
-
- 3 added
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/AlignInCircleAction.java
r17386 r17393 14 14 import java.util.List; 15 15 import java.util.Set; 16 import java.util.SortedMap; 17 import java.util.TreeMap; 16 18 import java.util.stream.Collectors; 17 19 … … 30 32 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 31 33 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay; 34 import org.openstreetmap.josm.data.validation.tests.CrossingWays; 32 35 import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay; 33 36 import org.openstreetmap.josm.gui.Notification; … … 43 46 * @author Teemu Koskinen 44 47 * @author Alain Delplanque 48 * @author Gerd Petermann 45 49 * 46 50 * @since 146 … … 107 111 try { 108 112 Command cmd = buildCommand(getLayerManager().getEditDataSet()); 109 UndoRedoHandler.getInstance().add(cmd); 113 if (cmd != null) 114 UndoRedoHandler.getInstance().add(cmd); 115 else { 116 new Notification(tr("nothing changed")) 117 .setIcon(JOptionPane.INFORMATION_MESSAGE) 118 .setDuration(Notification.TIME_SHORT) 119 .show(); 120 121 } 110 122 } catch (InvalidSelection except) { 111 123 Logging.debug(except); … … 115 127 .show(); 116 128 } 117 118 129 } 119 130 … … 140 151 * --> Align these nodes, all are fix 141 152 * @param ds data set in which the command operates 142 * @return the resulting command to execute to perform action 153 * @return the resulting command to execute to perform action, or null if nothing was changed 143 154 * @throws InvalidSelection if selection cannot be used 144 155 * @since 17386 … … 147 158 public static Command buildCommand(DataSet ds) throws InvalidSelection { 148 159 Collection<OsmPrimitive> sel = ds.getSelected(); 149 List<Node> nodes = new LinkedList<>();160 List<Node> selectedNodes = new LinkedList<>(); 150 161 // fixNodes: All nodes for which the angle relative to center should not be modified 151 162 Set<Node> fixNodes = new HashSet<>(); … … 156 167 for (OsmPrimitive osm : sel) { 157 168 if (osm instanceof Node) { 158 nodes.add((Node) osm);169 selectedNodes.add((Node) osm); 159 170 } else if (osm instanceof Way) { 160 171 ways.add((Way) osm); … … 162 173 } 163 174 164 if (ways.size() == 1 && !ways.get(0).isClosed()) { 175 // nodes on selected ways 176 List<Node> onWay = new ArrayList<>(); 177 if (!ways.isEmpty()) { 178 List<Node> potentialCenter = new ArrayList<>(); 179 for (Node n : selectedNodes) { 180 if (ways.stream().anyMatch(w -> w.containsNode(n))) { 181 onWay.add(n); 182 } else { 183 potentialCenter.add(n); 184 } 185 } 186 if (potentialCenter.size() == 1) { 187 // center is given 188 center = potentialCenter.get(0).getEastNorth(); 189 if (onWay.size() == 1) { 190 radius = center.distance(onWay.get(0).getEastNorth()); 191 } 192 } else if (potentialCenter.size() > 1) { 193 throw new InvalidSelection(tr("Please select only one node as center.")); 194 } 195 196 } 197 198 final List<Node> nodes; 199 if (ways.isEmpty()) { 200 nodes = sortByAngle(selectedNodes); 201 fixNodes.addAll(nodes); 202 } else if (ways.size() == 1 && !ways.get(0).isClosed()) { 165 203 // Case 1 166 204 Way w = ways.get(0); 167 if (SelfIntersectingWay.isSelfIntersecting(w)) {168 throw new InvalidSelection(tr("Self-intersecting way"));169 }170 171 205 fixNodes.add(w.firstNode()); 172 206 fixNodes.add(w.lastNode()); 173 fixNodes.addAll(nodes); 174 fixNodes.addAll(collectNodesWithExternReferrers(ways)); 207 fixNodes.addAll(onWay); 175 208 // Temporary closed way used to reorder nodes 176 209 Way closedWay = new Way(w); 177 closedWay.addNode(w.firstNode()); 178 nodes = collectNodesAnticlockwise(Collections.singletonList(closedWay)); 179 closedWay.setNodes(null); // see #19885 180 } else if (!ways.isEmpty() && checkWaysArePolygon(ways)) { 181 // Case 2 182 List<Node> inside = new ArrayList<>(); 183 List<Node> outside = new ArrayList<>(); 184 185 for (Node n: nodes) { 186 boolean isInside = ways.stream().anyMatch(w -> w.getNodes().contains(n)); 187 if (isInside) 188 inside.add(n); 189 else 190 outside.add(n); 191 } 192 193 if (outside.size() == 1 && inside.isEmpty()) { 194 center = outside.get(0).getEastNorth(); 195 } else if (outside.size() == 1 && inside.size() == 1) { 196 center = outside.get(0).getEastNorth(); 197 radius = center.distance(inside.get(0).getEastNorth()); 198 } else if (inside.size() == 2 && outside.isEmpty()) { 199 // 2 nodes inside, define diameter 200 EastNorth en0 = inside.get(0).getEastNorth(); 201 EastNorth en1 = inside.get(1).getEastNorth(); 202 center = new EastNorth((en0.east() + en1.east()) / 2, (en0.north() + en1.north()) / 2); 210 try { 211 closedWay.addNode(w.firstNode()); 212 nodes = collectNodesAnticlockwise(Collections.singletonList(closedWay)); 213 } finally { 214 closedWay.setNodes(null); // see #19885 215 } 216 } else if (Multipolygon.joinWays(ways).size() == 1) { 217 // Case 2: 218 if (onWay.size() == 2) { 219 // 2 way nodes define diameter 220 EastNorth en0 = onWay.get(0).getEastNorth(); 221 EastNorth en1 = onWay.get(1).getEastNorth(); 203 222 radius = en0.distance(en1) / 2; 204 } 205 206 fixNodes.addAll(inside); 207 fixNodes.addAll(collectNodesWithExternReferrers(ways)); 223 if (center == null) { 224 center = en0.getCenter(en1); 225 } 226 } 227 fixNodes.addAll(onWay); 208 228 nodes = collectNodesAnticlockwise(ways); 209 if (nodes.size() < 4) {210 throw new InvalidSelection(tr("Not enough nodes in selected ways."));211 }212 } else if (ways.isEmpty() && nodes.size() > 3) {213 // Case 3214 fixNodes.addAll(nodes);215 // No need to reorder nodes since all are fix216 229 } else { 217 if (ways.isEmpty() && nodes.size() <= 3)218 throw new InvalidSelection(tr("Please select at least four nodes."));219 230 throw new InvalidSelection(); 220 231 } 232 fixNodes.addAll(collectNodesWithExternReferrers(ways)); 221 233 222 234 // Check if one or more nodes are outside of download area … … 224 236 throw new InvalidSelection(tr("One or more nodes involved in this action is outside of the downloaded area.")); 225 237 238 226 239 if (center == null) { 227 // Compute the center of nodes 228 center = Geometry.getCenter(nodes); 240 if (nodes.size() < 4) { 241 throw new InvalidSelection(tr("Not enough nodes to calculate center.")); 242 } 243 if (validateGeometry(nodes)) { 244 // Compute the center of nodes 245 center = Geometry.getCenter(nodes); 246 } 229 247 if (center == null) { 230 throw new InvalidSelection(tr("Cannot determine center of selected nodes."));248 throw new InvalidSelection(tr("Cannot determine center of circle for this geometry.")); 231 249 } 232 250 } … … 282 300 } 283 301 if (cmds.isEmpty()) 284 throw new InvalidSelection(tr("nothing changed"));302 return null; 285 303 return new SequenceCommand(tr("Align Nodes in Circle"), cmds); 304 } 305 306 private static List<Node> sortByAngle(final List<Node> nodes) { 307 EastNorth sum = new EastNorth(0, 0); 308 for (Node n : nodes) { 309 EastNorth en = n.getEastNorth(); 310 sum = sum.add(en.east(), en.north()); 311 } 312 final EastNorth simpleCenter = new EastNorth(sum.east()/nodes.size(), sum.north()/nodes.size()); 313 314 SortedMap<Double, List<Node>> orderedMap = new TreeMap<>(); 315 for (Node n : nodes) { 316 double angle = new PolarCoor(n.getEastNorth(), simpleCenter).angle; 317 orderedMap.computeIfAbsent(angle, k-> new ArrayList<>()).add(n); 318 } 319 return orderedMap.values().stream().flatMap(List<Node>::stream).collect(Collectors.toList()); 320 } 321 322 private static boolean validateGeometry(List<Node> nodes) { 323 Way test = new Way(); 324 test.setNodes(nodes); 325 if (!test.isClosed()) { 326 test.addNode(test.firstNode()); 327 } 328 329 try { 330 if (CrossingWays.isSelfCrossing(test)) 331 return false; 332 return !SelfIntersectingWay.isSelfIntersecting(test); 333 } finally { 334 test.setNodes(null); // see #19855 335 } 286 336 } 287 337 … … 304 354 Collection<JoinedWay> rings = Multipolygon.joinWays(ways); 305 355 if (rings.size() != 1) 306 throw new InvalidSelection(); 356 throw new InvalidSelection(); // we should never get here 307 357 List<Node> nodes = new ArrayList<>(rings.iterator().next().getNodes()); 308 if (nodes.get(0) != nodes.get(nodes.size() -1))358 if (nodes.get(0) != nodes.get(nodes.size() - 1)) 309 359 throw new InvalidSelection(); 310 360 if (Geometry.isClockwise(nodes)) … … 324 374 updateEnabledStateOnModifiableSelection(selection); 325 375 } 326 327 /**328 * Determines if ways can be joined into a single polygon.329 * @param ways The ways collection to check330 * @return true if all ways can be joined into a single polygon331 */332 private static boolean checkWaysArePolygon(Collection<Way> ways) {333 if (Multipolygon.joinWays(ways).size() != 1)334 return false;335 // For each way, nodes strictly between first and last should't be reference by an other way336 for (Way way: ways) {337 for (Node node: way.getNodes()) {338 if (way.isFirstLastNode(node)) continue;339 if (ways.stream().filter(wayOther -> way != wayOther).anyMatch(wayOther -> node.getReferrers().contains(wayOther))) {340 return false;341 }342 }343 }344 return true;345 }346 347 376 } -
trunk/src/org/openstreetmap/josm/data/validation/tests/CrossingWays.java
r17348 r17393 27 27 import org.openstreetmap.josm.data.validation.util.ValUtil; 28 28 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 29 import org.openstreetmap.josm.tools.CheckParameterUtil; 29 30 import org.openstreetmap.josm.tools.Logging; 30 31 … … 469 470 } 470 471 472 /** 473 * Check if the given way is self crossing 474 * @param way the way to check 475 * @return {@code true} if one or more segments of the way are crossing 476 * @see SelfIntersectingWay 477 * @since xxx 478 */ 479 public static boolean isSelfCrossing(Way way) { 480 CheckParameterUtil.ensureParameterNotNull(way, "way"); 481 SelfCrossing test = new SelfCrossing(); 482 test.visit(way); 483 return !test.getErrors().isEmpty(); 484 } 471 485 } -
trunk/test/unit/org/openstreetmap/josm/actions/AlignInCircleActionTest.java
r17386 r17393 3 3 4 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 import static org.junit.jupiter.api.Assertions.assertFalse; 5 6 import static org.junit.jupiter.api.Assertions.assertNotNull; 6 import static org.junit.jupiter.api.Assertions.assertThrows; 7 import static org.junit.jupiter.api.Assertions.assertNull; 8 import static org.junit.jupiter.api.Assertions.assertTrue; 7 9 8 10 import java.nio.file.Files; … … 14 16 import org.junit.jupiter.api.extension.RegisterExtension; 15 17 import org.openstreetmap.josm.TestUtils; 18 import org.openstreetmap.josm.actions.AlignInCircleAction.InvalidSelection; 16 19 import org.openstreetmap.josm.command.Command; 17 20 import org.openstreetmap.josm.data.osm.DataSet; 18 21 import org.openstreetmap.josm.data.osm.Node; 22 import org.openstreetmap.josm.data.osm.OsmPrimitive; 19 23 import org.openstreetmap.josm.data.osm.Way; 20 24 import org.openstreetmap.josm.io.OsmReader; 21 25 import org.openstreetmap.josm.testutils.JOSMTestRules; 26 import org.opentest4j.AssertionFailedError; 22 27 23 28 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; … … 71 76 /** 72 77 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/20041">Bug #20041</a>. 73 * Don't create move commands when no node is visibly moved 78 * Don't create move commands when no node is visibly moved. 74 79 * @throws Exception if an error occurs 75 80 */ … … 88 93 if (roundabout != null) { 89 94 ds.setSelected(roundabout); 90 assert Throws(AlignInCircleAction.InvalidSelection.class, () ->AlignInCircleAction.buildCommand(ds));95 assertNull(AlignInCircleAction.buildCommand(ds)); 91 96 } 92 97 } … … 150 155 } 151 156 157 /** 158 * Various cases of selections in file 159 * @throws Exception if an error occurs 160 */ 161 @Test 162 void testSelectionEvaluation() throws Exception { 163 DataSet ds = OsmReader.parseDataSet( 164 Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "alignCircleCases.osm")), null); 165 166 for (int i = 0; i < 80; i++) { 167 final String selVal = Integer.toString(i); 168 Set<OsmPrimitive> sel = ds.allPrimitives().stream().filter(p -> p.hasTag("sel", selVal)) 169 .collect(Collectors.toSet()); 170 if (sel.isEmpty()) 171 continue; 172 ds.setSelected(sel); 173 boolean selValid = sel.stream().noneMatch(p -> p.hasKey("invalid-selection")); 174 try { 175 AlignInCircleAction.buildCommand(ds); 176 assertTrue(selValid, "sel=" + selVal + " is not valid?"); 177 } catch (InvalidSelection e) { 178 assertFalse(selValid, "sel=" + selVal + " is not invalid?"); 179 } catch (Exception e) { 180 throw new AssertionFailedError("test failed: sel=" + selVal,e); 181 } 182 } 183 } 152 184 }
Note:
See TracChangeset
for help on using the changeset viewer.