Changeset 17386 in josm
- Timestamp:
- 2020-12-02T16:43:36+01:00 (4 years ago)
- Location:
- trunk
- Files:
-
- 6 added
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/AlignInCircleAction.java
r17188 r17386 28 28 import org.openstreetmap.josm.data.osm.OsmPrimitive; 29 29 import org.openstreetmap.josm.data.osm.Way; 30 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 31 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay; 32 import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay; 30 33 import org.openstreetmap.josm.gui.Notification; 31 34 import org.openstreetmap.josm.tools.Geometry; 35 import org.openstreetmap.josm.tools.Logging; 32 36 import org.openstreetmap.josm.tools.Shortcut; 33 37 … … 55 59 56 60 /** 57 * Create a {@link MoveCommand} to move a node to a PolarCoor. 61 * InvalidSelection exception has to be raised when action can't be performed 62 */ 63 public static class InvalidSelection extends Exception { 64 65 /** 66 * Create an InvalidSelection exception with default message 67 */ 68 InvalidSelection() { 69 super(tr("Selection could not be used to align in circle.")); 70 } 71 72 /** 73 * Create an InvalidSelection exception with specific message 74 * @param msg Message that will be displayed to the user 75 */ 76 InvalidSelection(String msg) { 77 super(msg); 78 } 79 } 80 81 /** 82 * Add a {@link MoveCommand} to move a node to a PolarCoor if there is a significant move. 58 83 * @param n Node to move 59 84 * @param coor polar coordinate where to move the node 60 * @ return new MoveCommand61 * @since 1 310762 */ 63 public static MoveCommand createMoveCommand(Node n, PolarCoor coor) {85 * @param cmds list of commands 86 * @since 17386 87 */ 88 public static void addMoveCommandIfNeeded(Node n, PolarCoor coor, List<Command> cmds) { 64 89 EastNorth en = coor.toEastNorth(); 65 return new MoveCommand(n, en.east() - n.getEastNorth().east(), en.north() - n.getEastNorth().north()); 90 double deltaEast = en.east() - n.getEastNorth().east(); 91 double deltaNorth = en.north() - n.getEastNorth().north(); 92 93 if (Math.abs(deltaEast) > 5e-6 || Math.abs(deltaNorth) > 5e-6) { 94 cmds.add(new MoveCommand(n, deltaEast, deltaNorth)); 95 } 66 96 } 67 97 … … 69 99 * Perform AlignInCircle action. 70 100 * 101 */ 102 @Override 103 public void actionPerformed(ActionEvent e) { 104 if (!isEnabled()) 105 return; 106 107 try { 108 Command cmd = buildCommand(getLayerManager().getEditDataSet()); 109 UndoRedoHandler.getInstance().add(cmd); 110 } catch (InvalidSelection except) { 111 Logging.debug(except); 112 new Notification(except.getMessage()) 113 .setIcon(JOptionPane.INFORMATION_MESSAGE) 114 .setDuration(Notification.TIME_SHORT) 115 .show(); 116 } 117 118 } 119 120 /** 121 * Builds "align in circle" command depending on the selected objects. 71 122 * A fixed node is a node for which it is forbidden to change the angle relative to center of the circle. 72 123 * All other nodes are uniformly distributed. 73 * 124 * <p> 74 125 * Case 1: One unclosed way. 75 126 * --> allow action, and align selected way nodes 76 127 * If nodes contained by this way are selected, there are fix. 77 128 * If nodes outside from the way are selected there are ignored. 78 * 129 * <p> 79 130 * Case 2: One or more ways are selected and can be joined into a polygon 80 131 * --> allow action, and align selected ways nodes … … 85 136 * In all cases, selected nodes are fix, nodes with more than one referrers are fix 86 137 * (first referrer is the selected way) 87 * 138 * <p> 88 139 * Case 3: Only nodes are selected 89 140 * --> Align these nodes, all are fix 90 */ 91 @Override 92 public void actionPerformed(ActionEvent e) { 93 if (!isEnabled()) 94 return; 95 96 Collection<OsmPrimitive> sel = getLayerManager().getEditDataSet().getSelected(); 141 * @param ds data set in which the command operates 142 * @return the resulting command to execute to perform action 143 * @throws InvalidSelection if selection cannot be used 144 * @since 17386 145 * 146 */ 147 public static Command buildCommand(DataSet ds) throws InvalidSelection { 148 Collection<OsmPrimitive> sel = ds.getSelected(); 97 149 List<Node> nodes = new LinkedList<>(); 98 150 // fixNodes: All nodes for which the angle relative to center should not be modified … … 113 165 // Case 1 114 166 Way w = ways.get(0); 167 if (SelfIntersectingWay.isSelfIntersecting(w)) { 168 throw new InvalidSelection(tr("Self-intersecting way")); 169 } 170 115 171 fixNodes.add(w.firstNode()); 116 172 fixNodes.add(w.lastNode()); … … 121 177 closedWay.addNode(w.firstNode()); 122 178 nodes = collectNodesAnticlockwise(Collections.singletonList(closedWay)); 123 closedWay.setNodes(null); 179 closedWay.setNodes(null); // see #19885 124 180 } else if (!ways.isEmpty() && checkWaysArePolygon(ways)) { 125 181 // Case 2 … … 152 208 nodes = collectNodesAnticlockwise(ways); 153 209 if (nodes.size() < 4) { 154 new Notification( 155 tr("Not enough nodes in selected ways.")) 156 .setIcon(JOptionPane.INFORMATION_MESSAGE) 157 .setDuration(Notification.TIME_SHORT) 158 .show(); 159 return; 210 throw new InvalidSelection(tr("Not enough nodes in selected ways.")); 160 211 } 161 212 } else if (ways.isEmpty() && nodes.size() > 3) { … … 164 215 // No need to reorder nodes since all are fix 165 216 } else { 166 // Invalid action167 new Notification(168 tr("Please select at least four nodes."))169 .setIcon(JOptionPane.INFORMATION_MESSAGE)170 .setDuration(Notification.TIME_SHORT) 171 .show();172 return;173 }217 if (ways.isEmpty() && nodes.size() <= 3) 218 throw new InvalidSelection(tr("Please select at least four nodes.")); 219 throw new InvalidSelection(); 220 } 221 222 // Check if one or more nodes are outside of download area 223 if (nodes.stream().anyMatch(Node::isOutsideDownloadArea)) 224 throw new InvalidSelection(tr("One or more nodes involved in this action is outside of the downloaded area.")); 174 225 175 226 if (center == null) { … … 177 228 center = Geometry.getCenter(nodes); 178 229 if (center == null) { 179 new Notification(tr("Cannot determine center of selected nodes.")) 180 .setIcon(JOptionPane.INFORMATION_MESSAGE) 181 .setDuration(Notification.TIME_SHORT) 182 .show(); 183 return; 230 throw new InvalidSelection(tr("Cannot determine center of selected nodes.")); 184 231 } 185 232 } … … 195 242 } 196 243 197 if (!actionAllowed(nodes)) return; 198 199 Collection<Command> cmds = new LinkedList<>(); 244 List<Command> cmds = new LinkedList<>(); 200 245 201 246 // Move each node to that distance from the center. … … 217 262 Node first = nodes.get(i % nodeCount); 218 263 PolarCoor pcFirst = new PolarCoor(radius, PolarCoor.computeAngle(first.getEastNorth(), center), center); 219 cmds.add(createMoveCommand(first, pcFirst));264 addMoveCommandIfNeeded(first, pcFirst, cmds); 220 265 if (j > i + 1) { 221 266 double delta; … … 231 276 for (int k = i+1; k < j; k++) { 232 277 PolarCoor p = new PolarCoor(radius, pcFirst.angle + (k-i)*delta, center); 233 cmds.add(createMoveCommand(nodes.get(k % nodeCount), p));278 addMoveCommandIfNeeded(nodes.get(k % nodeCount), p, cmds); 234 279 } 235 280 } 236 281 i = j; // Update start point for next iteration 237 282 } 238 239 UndoRedoHandler.getInstance().add(new SequenceCommand(tr("Align Nodes in Circle"), cmds)); 283 if (cmds.isEmpty()) 284 throw new InvalidSelection(tr("nothing changed")); 285 return new SequenceCommand(tr("Align Nodes in Circle"), cmds); 240 286 } 241 287 … … 253 299 * @param ways List of ways to be joined 254 300 * @return Nodes anticlockwise ordered 255 */ 256 private static List<Node> collectNodesAnticlockwise(List<Way> ways) { 257 List<Node> nodes = new ArrayList<>(); 258 Node firstNode = ways.get(0).firstNode(); 259 Node lastNode = null; 260 Way lastWay = null; 261 while (firstNode != lastNode) { 262 if (lastNode == null) lastNode = firstNode; 263 for (Way way: ways) { 264 if (way == lastWay) continue; 265 if (way.firstNode() == lastNode) { 266 List<Node> wayNodes = way.getNodes(); 267 for (int i = 0; i < wayNodes.size() - 1; i++) { 268 nodes.add(wayNodes.get(i)); 269 } 270 lastNode = way.lastNode(); 271 lastWay = way; 272 break; 273 } 274 if (way.lastNode() == lastNode) { 275 List<Node> wayNodes = way.getNodes(); 276 for (int i = wayNodes.size() - 1; i > 0; i--) { 277 nodes.add(wayNodes.get(i)); 278 } 279 lastNode = way.firstNode(); 280 lastWay = way; 281 break; 282 } 283 } 284 } 285 // Check if nodes are in anticlockwise order 286 int nc = nodes.size(); 287 double area = 0; 288 for (int i = 0; i < nc; i++) { 289 EastNorth p1 = nodes.get(i).getEastNorth(); 290 EastNorth p2 = nodes.get((i+1) % nc).getEastNorth(); 291 area += p1.east()*p2.north() - p2.east()*p1.north(); 292 } 293 if (area < 0) 301 * @throws InvalidSelection if selection cannot be used 302 */ 303 private static List<Node> collectNodesAnticlockwise(List<Way> ways) throws InvalidSelection { 304 Collection<JoinedWay> rings = Multipolygon.joinWays(ways); 305 if (rings.size() != 1) 306 throw new InvalidSelection(); 307 List<Node> nodes = new ArrayList<>(rings.iterator().next().getNodes()); 308 if (nodes.get(0) != nodes.get(nodes.size()-1)) 309 throw new InvalidSelection(); 310 if (Geometry.isClockwise(nodes)) 294 311 Collections.reverse(nodes); 312 nodes.remove(nodes.size() - 1); 295 313 return nodes; 296 }297 298 /**299 * Check if one or more nodes are outside of download area300 * @param nodes Nodes to check301 * @return true if action can be done302 */303 private static boolean actionAllowed(Collection<Node> nodes) {304 boolean outside = nodes.stream().anyMatch(Node::isOutsideDownloadArea);305 if (outside)306 new Notification(307 tr("One or more nodes involved in this action is outside of the downloaded area."))308 .setIcon(JOptionPane.WARNING_MESSAGE)309 .setDuration(Notification.TIME_SHORT)310 .show();311 return true;312 314 } 313 315 … … 324 326 325 327 /** 326 * Determines if ways can be joined into a polygon. 328 * Determines if ways can be joined into a single polygon. 327 329 * @param ways The ways collection to check 328 * @return true if all ways can be joined into a polygon 330 * @return true if all ways can be joined into a single polygon 329 331 */ 330 332 private static boolean checkWaysArePolygon(Collection<Way> ways) { 333 if (Multipolygon.joinWays(ways).size() != 1) 334 return false; 331 335 // For each way, nodes strictly between first and last should't be reference by an other way 332 336 for (Way way: ways) { … … 338 342 } 339 343 } 340 // Test if ways can be joined 341 Way currentWay = null; 342 Node startNode = null, endNode = null; 343 int used = 0; 344 while (true) { 345 Way nextWay = null; 346 for (Way w: ways) { 347 if (w.isClosed()) return ways.size() == 1; 348 if (w == currentWay) continue; 349 if (currentWay == null) { 350 nextWay = w; 351 startNode = w.firstNode(); 352 endNode = w.lastNode(); 353 break; 354 } 355 if (w.firstNode() == endNode) { 356 nextWay = w; 357 endNode = w.lastNode(); 358 break; 359 } 360 if (w.lastNode() == endNode) { 361 nextWay = w; 362 endNode = w.firstNode(); 363 break; 364 } 365 } 366 if (nextWay == null) return false; 367 used += 1; 368 currentWay = nextWay; 369 if (endNode == startNode) return used == ways.size(); 370 } 371 } 344 return true; 345 } 346 372 347 } -
trunk/src/org/openstreetmap/josm/data/validation/tests/SelfIntersectingWay.java
r11441 r17386 12 12 import org.openstreetmap.josm.data.validation.Test; 13 13 import org.openstreetmap.josm.data.validation.TestError; 14 import org.openstreetmap.josm.data.validation.tests.CrossingWays.SelfCrossing; 14 15 15 16 /** … … 62 63 } 63 64 } 65 66 /** 67 * Check if the given way is self-intersecting 68 * @param way the way to check 69 * @return {@code true} if way contains some nodes more than once 70 * @see SelfCrossing 71 * @since 17386 72 */ 73 public static boolean isSelfIntersecting(Way way) { 74 SelfIntersectingWay test = new SelfIntersectingWay(); 75 test.visit(way); 76 return !test.errors.isEmpty(); 77 } 64 78 }
Note:
See TracChangeset
for help on using the changeset viewer.