Changeset 35440 in osm for applications/editors/josm/plugins/utilsplugin2/src
- Timestamp:
- 2020-05-13T11:34:47+02:00 (5 years ago)
- Location:
- applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/curves
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/curves/CircleArcMaker.java
r35438 r35440 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.plugins.utilsplugin2.curves; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 3 5 4 6 import java.util.ArrayList; … … 11 13 import java.util.List; 12 14 import java.util.Set; 15 import java.util.stream.Collectors; 13 16 14 17 import org.openstreetmap.josm.command.AddCommand; … … 23 26 import org.openstreetmap.josm.data.osm.DataSet; 24 27 import org.openstreetmap.josm.data.osm.Node; 28 import org.openstreetmap.josm.data.osm.Relation; 25 29 import org.openstreetmap.josm.data.osm.Way; 26 30 import org.openstreetmap.josm.data.projection.ProjectionRegistry; 27 31 import org.openstreetmap.josm.gui.MainApplication; 28 32 import org.openstreetmap.josm.tools.Geometry; 33 import org.openstreetmap.josm.tools.JosmRuntimeException; 29 34 30 35 /** … … 107 112 108 113 double radiusInMeters = ll1.greatCircleDistance(ll2); 114 if (radiusInMeters < 0.01) 115 throw new JosmRuntimeException(tr("Radius too small")); 109 116 110 117 int numberOfNodesInCircle = (int) Math.ceil(6.0 * Math.pow(radiusInMeters, 0.5)); … … 126 133 127 134 if (!selectedWays.isEmpty()) { 135 if (w.isClosed()) { 136 // see #19188 137 nodes.clear(); 138 nodes.addAll(findShortestPart(w, anchorNodes)); 139 } 128 140 // Fix #7341. sort nodes in ways nodes order 129 141 List<Node> consideredNodes = Arrays.asList(n1, n2, n3); 130 Collections.sort(consideredNodes,(o1, o2) -> nodes.indexOf(o1) - nodes.indexOf(o2));142 consideredNodes.sort((o1, o2) -> nodes.indexOf(o1) - nodes.indexOf(o2)); 131 143 n1 = consideredNodes.get(0); 144 n2 = consideredNodes.get(1); 132 145 n3 = consideredNodes.get(2); 133 } 134 135 Set<Node> fixNodes = new HashSet<>(anchorNodes); 136 if (!selectedWays.isEmpty()) { 137 nodes.stream().filter(n -> n.getParentWays().size() > 1 || n.isTagged()).forEach(fixNodes::add); 138 } 146 anchorNodes = Arrays.asList(n1, n2, n3); 147 } 148 149 150 List<Node> cwTest = new ArrayList<>(Arrays.asList(n1, n2, n3)); 151 if (cwTest.get(0) != cwTest.get(cwTest.size() - 1)) { 152 cwTest.add(cwTest.get(0)); 153 } 154 boolean clockwise = Geometry.isClockwise(cwTest); 155 139 156 boolean needsUndo = false; 140 157 if (!cmds.isEmpty()) { … … 145 162 int pos1 = nodes.indexOf(n1); 146 163 int pos3 = nodes.indexOf(n3); 147 List<Node> toModify = new ArrayList<>(nodes.subList(pos1, pos3 + 1)); 148 cmds.addAll(worker(toModify, fixNodes, center, radius, maxAngle)); 149 if (toModify.size() > pos3 + 1 - pos1) { 150 List<Node> changed = new ArrayList<>(); 151 changed.addAll(nodes.subList(0, pos1)); 152 changed.addAll(toModify); 153 changed.addAll(nodes.subList(pos3 + 1, nodes.size())); 154 Way wNew = new Way(w); 155 wNew.setNodes(changed); 156 cmds.add(new ChangeCommand(w, wNew)); 157 } 158 if (needsUndo) { 159 // make sure we don't add the new nodes twice 160 UndoRedoHandler.getInstance().undo(1); 164 Set<Node> fixNodes = new HashSet<>(anchorNodes); 165 if (!selectedWays.isEmpty()) { 166 for (int i = pos1 + 1; i < pos3; i++) { 167 Node n = nodes.get(i); 168 if (n.isTagged() || n.isReferredByWays(2) || n.referrers(Relation.class).count() > 0) { 169 fixNodes.add(n); 170 } 171 } 172 } 173 174 List<Node> orig = nodes.subList(pos1, pos3 + 1); 175 List<Node> arcNodes = new ArrayList<>(orig); 176 177 Set<Way> targetWays = new HashSet<>(); 178 if (!selectedWays.isEmpty()) { 179 orig.forEach(n -> targetWays.addAll(n.getParentWays())); 180 } else { 181 targetWays.add(w); 182 } 183 try { 184 List<Command> modCmds = worker(arcNodes, fixNodes, center, radius, maxAngle, clockwise); 185 cmds.addAll(modCmds); 186 if (!arcNodes.equals(orig)) { 187 fuseArc(ds, arcNodes, targetWays, cmds); 188 } 189 } finally { 190 if (needsUndo) { 191 // make sure we don't add the new nodes twice 192 UndoRedoHandler.getInstance().undo(1); 193 } 194 } 195 if (cmds.isEmpty()) { 196 throw new JosmRuntimeException(tr("Nothing to do")); 161 197 } 162 198 return cmds; 163 199 } 164 200 201 /** 202 * Try to find out which nodes should be moved when a closed way is modified. The positions of the anchor 203 * nodes might not give the right order. Rotate the nodes so that each of the selected nodes is first 204 * and check which variant produces the shortest part of the way. 205 * @param w the closed way to modify 206 * @param anchorNodes the (selected) anchor nodes 207 * @return the way nodes, possibly rotated 208 * @throws JosmRuntimeException if no usable rotation was found 209 */ 210 private static List<Node> findShortestPart(Way w, List<Node> anchorNodes) { 211 int bestRotate = 0; 212 double shortest = Double.MAX_VALUE; 213 final double wayLength = w.getLength(); 214 for (int i = 0; i < w.getNodesCount() - 1; i++) { 215 List<Node> nodes = rotate(w, i); 216 List<Integer> positions = anchorNodes.stream().map(nodes::indexOf).sorted().collect(Collectors.toList()); 217 double lenghth = getLength(nodes, positions.get(0), positions.get(2)); 218 if (lenghth < shortest) { 219 bestRotate = i; 220 shortest = lenghth; 221 } 222 } 223 if (shortest >= wayLength / 2) 224 throw new JosmRuntimeException(tr("Could not detect which part of the closed way should be changed")); 225 return rotate(w, bestRotate); 226 } 227 228 private static List<Node> rotate(Way w, int distance) { 229 List<Node> nodes = new ArrayList<>(w.getNodes()); 230 nodes.remove(nodes.size() - 1); // remove closing node 231 Collections.rotate(nodes, distance); 232 nodes.add(nodes.get(0)); 233 return nodes; 234 } 235 236 private static double getLength(List<Node> nodes, int pos1, int pos3) { 237 Way tmp = new Way(); 238 tmp.setNodes(nodes.subList(pos1, pos3+1)); 239 return tmp.getLength(); 240 } 241 165 242 // code partly taken from AlignInCircleAction 166 private static List<Command> worker(List<Node> nodes, Set<Node> fixNodes, EastNorth center, double radius, double maxAngle) { 243 private static List<Command> worker(List<Node> origNodes, Set<Node> origFixNodes, EastNorth center, double radius, 244 double maxAngle, boolean clockwise) { 167 245 List<Command> cmds = new LinkedList<>(); 168 169 246 // Move each node to that distance from the center. 170 // Nodes that are not "fix" will be adjust making regular arcs. 171 int nodeCount = nodes.size(); 172 173 List<Node> cwTest = new ArrayList<>(nodes); 174 if (cwTest.get(0) != cwTest.get(cwTest.size() - 1)) { 175 cwTest.add(cwTest.get(0)); 176 } 177 boolean clockWise = Geometry.isClockwise(cwTest); 247 // Nodes that are not "fix" will be adjusted making regular arcs. 248 List<Node> nodes = new ArrayList<>(origNodes); 249 Set<Node> fixNodes = new HashSet<>(origFixNodes); 178 250 double maxStep = Math.PI * 2 / (360.0 / maxAngle); 179 180 // Search first fixed node 181 int startPosition; 182 for (startPosition = 0; startPosition < nodeCount; startPosition++) { 183 if (fixNodes.contains(nodes.get(startPosition))) 184 break; 185 } 186 int i = startPosition; // Start position for current arc 251 double sumAbsDelta = 0; 252 int i = 0; // Start position for current arc 187 253 int j; // End position for current arc 188 while (i < nodeCount) { 189 for (j = i + 1; j < nodeCount; j++) { 190 if (fixNodes.contains(nodes.get(j))) 254 while (i < nodes.size()) { 255 Node first = nodes.get(i); 256 PolarCoor pcFirst = new PolarCoor(radius, PolarCoor.computeAngle(first.getEastNorth(), center), center); 257 for (j = i + 1; j < nodes.size(); j++) { 258 Node test = nodes.get(j); 259 if (fixNodes.contains(test)) { 191 260 break; 192 } 193 Node first = nodes.get(i); 194 195 PolarCoor pcFirst = new PolarCoor(radius, PolarCoor.computeAngle(first.getEastNorth(), center), center); 196 addMoveCommandIfNeeded(first, pcFirst, cmds); 197 if (j < nodeCount) { 198 double delta; 199 PolarCoor pcLast = new PolarCoor(nodes.get(j).getEastNorth(), center); 200 delta = pcLast.angle - pcFirst.angle; 201 if (!clockWise && delta < 0) { 261 } 262 } 263 264 if (j < nodes.size()) { 265 Node last = nodes.get(j); 266 PolarCoor pcLast = new PolarCoor(last.getEastNorth(), center); 267 double delta = pcLast.angle - pcFirst.angle; 268 if ((!clockwise && delta < 0 || clockwise && delta > 0) 269 && Math.signum(pcFirst.angle) == Math.signum(pcLast.angle)) { 270 // cannot project node onto circle arc, ignore that it is fixed 271 if (!last.isSelected() && fixNodes.remove(last)) { 272 continue; 273 } 274 if (!first.isSelected() && fixNodes.remove(first)) { 275 // try again with fewer fixed nodes 276 return worker(origNodes, fixNodes, center, radius, maxAngle, clockwise); 277 } 278 } 279 if (!clockwise && delta < 0) { 202 280 delta += 2 * Math.PI; 203 } else if (clock Wise && delta > 0) {281 } else if (clockwise && delta > 0) { 204 282 delta -= 2 * Math.PI; 205 283 } 284 sumAbsDelta += Math.abs(delta); 285 if (sumAbsDelta > 2 * Math.PI) { 286 // something went really wrong, we would add more than a full circle 287 throw new JosmRuntimeException("Would create a loop"); 288 } 289 206 290 // do we have enough nodes to produce a nice circle? 207 int numToAdd = Math.max((int) Math.ceil(Math.abs(delta / maxStep)), j - i) - (j-i); 291 int numToAdd = Math.max((int) Math.ceil(Math.abs(delta / maxStep)), j - i) - (j - i); 292 int added = 0; 208 293 double step = delta / (numToAdd + j - i); 209 for (int k = i + 1; k < j; k++) { 210 PolarCoor p = new PolarCoor(radius, pcFirst.angle + (k - i) * step, center); 211 addMoveCommandIfNeeded(nodes.get(k), p, cmds); 212 } 213 // add needed nodes 214 for (int k = j; k < j + numToAdd; k++) { 215 PolarCoor p = new PolarCoor(radius, pcFirst.angle + (k - i) * step, center); 216 Node nNew = new Node(p.toEastNorth()); 217 nodes.add(k, nNew); 218 cmds.add(new AddCommand(nodes.get(0).getDataSet(), nNew)); 219 } 220 j += numToAdd; 221 nodeCount += numToAdd; 294 295 // move existing nodes or add new nodes 296 List<Node> oldNodes = new ArrayList<>(nodes.subList(i, j)); 297 PolarCoor ref = pcFirst; 298 for (int k = i; k < j + numToAdd; k++) { 299 PolarCoor pc1 = new PolarCoor(radius, pcFirst.angle + (k - i) * step, center); 300 if (!oldNodes.isEmpty()) { 301 PolarCoor pc2 = new PolarCoor(oldNodes.get(0).getEastNorth(), center); 302 if (pc2.angle < ref.angle && ref.angle < 0 && step > 0 303 || pc2.angle > ref.angle && ref.angle > 0 && step < 0) { 304 // projected node would produce a loop 305 pc2 = ref; // 306 } 307 308 double delta2 = ref.angle - pc2.angle; 309 if (ref.angle < 0 && pc2.angle > 0) { 310 delta2 += 2 * Math.PI; 311 } else if (ref.angle > 0 && pc2.angle < 0) { 312 delta2 -= 2 * Math.PI; 313 } 314 if (added >= numToAdd || Math.abs(delta2) < Math.abs(step * 1.5)) { 315 // existing node is close enough 316 addMoveCommandIfNeeded(oldNodes.remove(0), pc1, cmds); 317 ref = pc1; 318 } 319 } 320 if (ref != pc1) { 321 ref = pc1; 322 Node nNew = new Node(pc1.toEastNorth()); 323 nodes.add(k, nNew); 324 cmds.add(new AddCommand(nodes.get(0).getDataSet(), nNew)); 325 ++added; 326 } 327 } 328 329 j += added; 222 330 } 223 331 i = j; // Update start point for next iteration 224 332 } 333 origNodes.clear(); 334 origNodes.addAll(nodes); 225 335 return cmds; 226 336 } … … 235 345 } 236 346 347 private static void fuseArc(DataSet ds, List<Node> arcNodes, Set<Way> targetWays, Collection<Command> cmds) { 348 // replace each segment of the target ways with the corresponding nodes of the new arc 349 for (Way originalTw : targetWays) { 350 Way tw = new Way(originalTw); 351 boolean twWasChanged = false; 352 for (int i = 0; i < arcNodes.size(); i++) { 353 Node arcNode1 = arcNodes.get(i); 354 // we don't want to match nodes which where added by worker 355 if (arcNode1.getDataSet() != ds || !arcNode1.getParentWays().contains(originalTw)) 356 continue; 357 358 boolean changed = false; 359 for (int j = i + 1; j < arcNodes.size() && !changed; j++) { 360 Node arcNode2 = arcNodes.get(j); 361 if (arcNode2.getDataSet() != ds || !arcNode2.getParentWays().contains(originalTw)) 362 continue; 363 changed = tryAddArc(tw, i, j, arcNodes); 364 twWasChanged |= changed; 365 } 366 } 367 if (twWasChanged) { 368 cmds.add(new ChangeCommand(ds, originalTw, tw)); 369 } 370 } 371 } 372 373 private static boolean tryAddArc(Way tw, int i, int j, List<Node> arcNodes) { 374 int pos1 = tw.getNodes().indexOf(arcNodes.get(i)); 375 int pos2 = tw.getNodes().indexOf(arcNodes.get(j)); 376 if (tw.isClosed()) { 377 if (pos1 - pos2 > 1 && pos2 == 0) { 378 pos2 = tw.getNodesCount() - 1; 379 } else if (pos2 - pos1 > 1 && pos1 == 0) { 380 pos1 = tw.getNodesCount() - 1; 381 } 382 } 383 if (pos2 + 1 == pos1) { 384 for (int k = i + 1; k < j; k++) { 385 tw.addNode(pos1, arcNodes.get(k)); 386 } 387 return true; 388 } else if (pos2 - 1 == pos1) { 389 for (int k = j - 1; k > i; k--) { 390 tw.addNode(pos2, arcNodes.get(k)); 391 } 392 return true; 393 } 394 return false; 395 } 237 396 } -
applications/editors/josm/plugins/utilsplugin2/src/org/openstreetmap/josm/plugins/utilsplugin2/curves/CurveAction.java
r35384 r35440 21 21 import org.openstreetmap.josm.data.osm.Way; 22 22 import org.openstreetmap.josm.gui.Notification; 23 import org.openstreetmap.josm.tools.JosmRuntimeException; 23 24 import org.openstreetmap.josm.tools.Shortcut; 24 25 … … 47 48 List<Way> selectedWays = new ArrayList<>(getLayerManager().getEditDataSet().getSelectedWays()); 48 49 49 Collection<Command> cmds = CircleArcMaker.doCircleArc(selectedNodes, selectedWays); 50 if (cmds == null || cmds.isEmpty()) { 51 new Notification(tr("Could not use selection to create a curve")).setIcon(JOptionPane.WARNING_MESSAGE).show(); 52 } else { 53 UndoRedoHandler.getInstance().add(new SequenceCommand("Create a curve", cmds)); 50 String msg = null; 51 try { 52 Collection<Command> cmds = CircleArcMaker.doCircleArc(selectedNodes, selectedWays); 53 if (cmds == null || cmds.isEmpty()) { 54 msg = tr("Could not use selection to create a curve"); 55 } else { 56 UndoRedoHandler.getInstance().add(new SequenceCommand("Create a curve", cmds)); 57 } 58 } catch (JosmRuntimeException ex) { 59 msg = tr("Could not use selection to create a curve: {0}", ex.getMessage()); 60 } 61 if (msg != null) { 62 new Notification(msg).setIcon(JOptionPane.WARNING_MESSAGE).show(); 54 63 } 55 64 }
Note:
See TracChangeset
for help on using the changeset viewer.