### Eclipse Workspace Patch 1.0
#P josm
diff --git src/org/openstreetmap/josm/actions/AlignInLineAction.java src/org/openstreetmap/josm/actions/AlignInLineAction.java
index 9d85c4f..0feb334 100644
|
|
|
|
| 93 | 93 | // Get ways passing though all selected nodes. |
| 94 | 94 | Set<Way> waysRef = null; |
| 95 | 95 | for (Node n: nodes) { |
| 96 | | Collection<Way> ref = n.getParentWays(); |
| 97 | | if (waysRef == null) |
| 98 | | waysRef = new HashSet<>(ref); |
| 99 | | else |
| 100 | | waysRef.retainAll(ref); |
| | 96 | final Collection<Way> ref = n.getParentWays(); |
| | 97 | if (waysRef == null) { |
| | 98 | waysRef = new HashSet<>(ref); |
| | 99 | } else { |
| | 100 | waysRef.retainAll(ref); |
| | 101 | } |
| 101 | 102 | } |
| 102 | 103 | |
| 103 | 104 | if (waysRef == null) { |
| … |
… |
|
| 105 | 106 | } |
| 106 | 107 | |
| 107 | 108 | // Nodes belongs to multiple ways, return most distant nodes. |
| 108 | | if (waysRef.size() != 1) |
| 109 | | return nodeFurthestAppart(nodes); |
| | 109 | if (waysRef.size() != 1) { |
| | 110 | return nodeFurthestAppart(nodes); |
| | 111 | } |
| 110 | 112 | |
| 111 | 113 | // All nodes are part of the same way. See #9605. |
| 112 | | Way way = waysRef.iterator().next(); |
| | 114 | final Way way = waysRef.iterator().next(); |
| 113 | 115 | |
| 114 | 116 | if (way.isClosed()) { |
| 115 | 117 | // Align these nodes on the line passing through the most distant nodes. |
| … |
… |
|
| 121 | 123 | |
| 122 | 124 | // The way is open, align nodes on the line passing through the extremity nodes (most distant in the way |
| 123 | 125 | // sequence). See #9605#comment:3. |
| 124 | | Set<Node> remainNodes = new HashSet<>(nodes); |
| | 126 | final Set<Node> remainNodes = new HashSet<>(nodes); |
| 125 | 127 | for (Node n : way.getNodes()) { |
| 126 | | if (!remainNodes.contains(n)) |
| 127 | | continue; |
| 128 | | if (nodea == null) |
| 129 | | nodea = n; |
| | 128 | if (!remainNodes.contains(n)) { |
| | 129 | continue; |
| | 130 | } |
| | 131 | if (nodea == null) { |
| | 132 | nodea = n; |
| | 133 | } |
| 130 | 134 | if (remainNodes.size() == 1) { |
| 131 | 135 | nodeb = remainNodes.iterator().next(); |
| 132 | 136 | break; |
| … |
… |
|
| 146 | 150 | private static Node[] nodeFurthestAppart(List<Node> nodes) { |
| 147 | 151 | Node node1 = null, node2 = null; |
| 148 | 152 | double minSqDistance = 0; |
| 149 | | int nb; |
| | 153 | final int nb; |
| 150 | 154 | |
| 151 | 155 | nb = nodes.size(); |
| 152 | 156 | for (int i = 0; i < nb - 1; i++) { |
| 153 | | Node n = nodes.get(i); |
| | 157 | final Node n = nodes.get(i); |
| 154 | 158 | for (int j = i + 1; j < nb; j++) { |
| 155 | | Node m = nodes.get(j); |
| 156 | | double sqDist = n.getEastNorth().distanceSq(m.getEastNorth()); |
| | 159 | final Node m = nodes.get(j); |
| | 160 | final double sqDist = n.getEastNorth().distanceSq(m.getEastNorth()); |
| 157 | 161 | if (sqDist > minSqDistance) { |
| 158 | 162 | node1 = n; |
| 159 | 163 | node2 = m; |
| … |
… |
|
| 170 | 174 | */ |
| 171 | 175 | @Override |
| 172 | 176 | public void actionPerformed(ActionEvent e) { |
| 173 | | if (!isEnabled()) |
| 174 | | return; |
| | 177 | if (!isEnabled()) { |
| | 178 | return; |
| | 179 | } |
| 175 | 180 | |
| 176 | 181 | try { |
| 177 | | Command cmd = buildCommand(getLayerManager().getEditDataSet()); |
| | 182 | final Command cmd = buildCommand(getLayerManager().getEditDataSet()); |
| 178 | 183 | if (cmd != null) { |
| 179 | 184 | UndoRedoHandler.getInstance().add(cmd); |
| 180 | 185 | } |
| … |
… |
|
| 194 | 199 | * @since 13108 |
| 195 | 200 | */ |
| 196 | 201 | public Command buildCommand(DataSet ds) throws InvalidSelection { |
| 197 | | List<Node> selectedNodes = new ArrayList<>(ds.getSelectedNodes()); |
| 198 | | List<Way> selectedWays = new ArrayList<>(ds.getSelectedWays()); |
| | 202 | final List<Node> selectedNodes = new ArrayList<>(ds.getSelectedNodes()); |
| | 203 | final List<Way> selectedWays = new ArrayList<>(ds.getSelectedWays()); |
| 199 | 204 | selectedWays.removeIf(w -> w.isIncomplete() || w.isEmpty()); |
| 200 | 205 | |
| 201 | 206 | // Decide what to align based on selection: |
| … |
… |
|
| 204 | 209 | return alignMultiWay(selectedWays); |
| 205 | 210 | } else if (selectedNodes.size() == 1) { |
| 206 | 211 | // Only 1 node selected -> align this node relative to referrers way |
| 207 | | Node selectedNode = selectedNodes.get(0); |
| 208 | | List<Way> involvedWays; |
| 209 | | if (selectedWays.isEmpty()) |
| 210 | | // No selected way, all way containing this node are used |
| | 212 | final Node selectedNode = selectedNodes.get(0); |
| | 213 | final List<Way> involvedWays; |
| | 214 | if (selectedWays.isEmpty()) { |
| | 215 | // No selected way, all way containing this node are used |
| 211 | 216 | involvedWays = selectedNode.getParentWays(); |
| 212 | | else |
| 213 | | // Selected way, use only these ways |
| | 217 | } else { |
| | 218 | // Selected way, use only these ways |
| 214 | 219 | involvedWays = selectedWays; |
| 215 | | List<Line> lines = getInvolvedLines(selectedNode, involvedWays); |
| 216 | | if (lines.size() > 2 || lines.isEmpty()) |
| 217 | | throw new InvalidSelection(); |
| | 220 | } |
| | 221 | final List<Line> lines = getInvolvedLines(selectedNode, involvedWays); |
| | 222 | if (lines.size() > 2 || lines.isEmpty()) { |
| | 223 | throw new InvalidSelection(); |
| | 224 | } |
| 218 | 225 | return alignSingleNode(selectedNodes.get(0), lines); |
| 219 | 226 | } else if (selectedNodes.size() >= 3) { |
| 220 | 227 | // More than 3 nodes and way(s) selected -> align selected nodes. Don't care of way(s). |
| … |
… |
|
| 234 | 241 | */ |
| 235 | 242 | private static Command alignOnlyNodes(List<Node> nodes) throws InvalidSelection { |
| 236 | 243 | // Choose nodes used as anchor points for projection. |
| 237 | | Node[] anchors = nodePairFurthestApart(nodes); |
| 238 | | Line line = new Line(anchors[0], anchors[1]); |
| 239 | | Collection<Command> cmds = nodes.stream() |
| | 244 | final Node[] anchors = nodePairFurthestApart(nodes); |
| | 245 | final Line line = new Line(anchors[0], anchors[1]); |
| | 246 | final Collection<Command> cmds = nodes.stream() |
| 240 | 247 | .filter(node -> node != anchors[0] && node != anchors[1]) |
| 241 | 248 | .map(line::projectionCommand) |
| 242 | 249 | .collect(Collectors.toList()); |
| … |
… |
|
| 251 | 258 | */ |
| 252 | 259 | private static Command alignMultiWay(Collection<Way> ways) throws InvalidSelection { |
| 253 | 260 | // Collect all nodes and compute line equation |
| 254 | | Set<Node> nodes = new HashSet<>(); |
| 255 | | Map<Way, Line> lines = new HashMap<>(); |
| | 261 | final Set<Node> nodes = new HashSet<>(); |
| | 262 | final Map<Way, Line> lines = new HashMap<>(); |
| 256 | 263 | for (Way w: ways) { |
| 257 | | if (w.isClosed()) |
| 258 | | throw new InvalidSelection(tr("Can not align a polygon. Abort.")); |
| | 264 | if (w.isClosed()) { |
| | 265 | throw new InvalidSelection(tr("Can not align a polygon. Abort.")); |
| | 266 | } |
| 259 | 267 | if (!w.isEmpty()) { |
| 260 | 268 | nodes.addAll(w.getNodes()); |
| 261 | 269 | lines.put(w, new Line(w)); |
| … |
… |
|
| 264 | 272 | if (nodes.isEmpty()) { |
| 265 | 273 | throw new InvalidSelection(tr("Intersection of three or more ways can not be solved. Abort.")); |
| 266 | 274 | } |
| 267 | | Collection<Command> cmds = new ArrayList<>(nodes.size()); |
| 268 | | List<Way> referrers = new ArrayList<>(ways.size()); |
| | 275 | final Collection<Command> cmds = new ArrayList<>(nodes.size()); |
| | 276 | final List<Way> referrers = new ArrayList<>(ways.size()); |
| 269 | 277 | for (Node n: nodes) { |
| 270 | 278 | referrers.clear(); |
| 271 | 279 | for (OsmPrimitive o: n.getReferrers()) { |
| 272 | | if (ways.contains(o)) |
| 273 | | referrers.add((Way) o); |
| | 280 | if (ways.contains(o)) { |
| | 281 | referrers.add((Way) o); |
| | 282 | } |
| 274 | 283 | } |
| 275 | 284 | if (referrers.size() == 1) { |
| 276 | | Way way = referrers.get(0); |
| 277 | | if (way.isFirstLastNode(n)) continue; |
| | 285 | final Way way = referrers.get(0); |
| | 286 | if (way.isFirstLastNode(n)) { |
| | 287 | continue; |
| | 288 | } |
| 278 | 289 | cmds.add(lines.get(way).projectionCommand(n)); |
| 279 | 290 | } else if (referrers.size() == 2) { |
| 280 | 291 | cmds.add(lines.get(referrers.get(0)).intersectionCommand(n, lines.get(referrers.get(1)))); |
| 281 | | } else |
| 282 | | throw new InvalidSelection(tr("Intersection of three or more ways can not be solved. Abort.")); |
| | 292 | } else { |
| | 293 | throw new InvalidSelection(tr("Intersection of three or more ways can not be solved. Abort.")); |
| | 294 | } |
| 283 | 295 | } |
| 284 | 296 | return cmds.isEmpty() ? null : new SequenceCommand(tr("Align Nodes in Line"), cmds); |
| 285 | 297 | } |
| … |
… |
|
| 292 | 304 | * @throws InvalidSelection if a node got more than 4 neighbours (self-crossing way) |
| 293 | 305 | */ |
| 294 | 306 | private static List<Line> getInvolvedLines(Node node, List<Way> refWays) throws InvalidSelection { |
| 295 | | List<Line> lines = new ArrayList<>(); |
| 296 | | List<Node> neighbors = new ArrayList<>(); |
| | 307 | final List<Line> lines = new ArrayList<>(); |
| | 308 | final List<Node> neighbors = new ArrayList<>(); |
| 297 | 309 | for (Way way: refWays) { |
| 298 | | List<Node> nodes = way.getNodes(); |
| | 310 | final List<Node> nodes = way.getNodes(); |
| 299 | 311 | neighbors.clear(); |
| 300 | 312 | for (int i = 1; i < nodes.size()-1; i++) { |
| 301 | 313 | if (nodes.get(i) == node) { |
| … |
… |
|
| 303 | 315 | neighbors.add(nodes.get(i+1)); |
| 304 | 316 | } |
| 305 | 317 | } |
| 306 | | if (neighbors.isEmpty()) |
| 307 | | continue; |
| 308 | | else if (neighbors.size() == 2) |
| 309 | | // Non self crossing |
| | 318 | if (neighbors.isEmpty()) { |
| | 319 | continue; |
| | 320 | } else if (neighbors.size() == 2) { |
| | 321 | // Non self crossing |
| 310 | 322 | lines.add(new Line(neighbors.get(0), neighbors.get(1))); |
| 311 | | else if (neighbors.size() == 4) { |
| | 323 | } else if (neighbors.size() == 4) { |
| 312 | 324 | // Self crossing, have to make 2 lines with 4 neighbors |
| 313 | 325 | // see #9081 comment 6 |
| 314 | | EastNorth c = node.getEastNorth(); |
| 315 | | double[] angle = IntStream.range(0, 4) |
| | 326 | final EastNorth c = node.getEastNorth(); |
| | 327 | final double[] angle = IntStream.range(0, 4) |
| 316 | 328 | .mapToDouble(i -> PolarCoor.computeAngle(neighbors.get(i).getEastNorth(), c)).toArray(); |
| 317 | | double[] deltaAngle = new double[3]; |
| | 329 | final double[] deltaAngle = new double[3]; |
| 318 | 330 | for (int i = 0; i < 3; i++) { |
| 319 | 331 | deltaAngle[i] = angle[i+1] - angle[0]; |
| 320 | | if (deltaAngle[i] < 0) |
| 321 | | deltaAngle[i] += 2*Math.PI; |
| | 332 | if (deltaAngle[i] < 0) { |
| | 333 | deltaAngle[i] += 2*Math.PI; |
| | 334 | } |
| 322 | 335 | } |
| 323 | 336 | int nb = 0; |
| 324 | | if (deltaAngle[1] < deltaAngle[0]) nb++; |
| 325 | | if (deltaAngle[2] < deltaAngle[0]) nb++; |
| | 337 | if (deltaAngle[1] < deltaAngle[0]) { |
| | 338 | nb++; |
| | 339 | } |
| | 340 | if (deltaAngle[2] < deltaAngle[0]) { |
| | 341 | nb++; |
| | 342 | } |
| 326 | 343 | if (nb == 1) { |
| 327 | 344 | // Align along [neighbors[0], neighbors[1]] and [neighbors[0], neighbors[2]] |
| 328 | 345 | lines.add(new Line(neighbors.get(0), neighbors.get(1))); |
| … |
… |
|
| 332 | 349 | lines.add(new Line(neighbors.get(0), neighbors.get(2))); |
| 333 | 350 | lines.add(new Line(neighbors.get(1), neighbors.get(3))); |
| 334 | 351 | } |
| 335 | | } else |
| 336 | | throw new InvalidSelection("cannot treat more than 4 neighbours, got "+neighbors.size()); |
| | 352 | } else { |
| | 353 | throw new InvalidSelection("cannot treat more than 4 neighbours, got "+neighbors.size()); |
| | 354 | } |
| 337 | 355 | } |
| 338 | 356 | return lines; |
| 339 | 357 | } |
| … |
… |
|
| 346 | 364 | * @throws InvalidSelection if more than 2 lines |
| 347 | 365 | */ |
| 348 | 366 | private static Command alignSingleNode(Node node, List<Line> lines) throws InvalidSelection { |
| 349 | | if (lines.size() == 1) |
| 350 | | return lines.get(0).projectionCommand(node); |
| 351 | | else if (lines.size() == 2) |
| 352 | | return lines.get(0).intersectionCommand(node, lines.get(1)); |
| | 367 | if (lines.size() == 1) { |
| | 368 | return lines.get(0).projectionCommand(node); |
| | 369 | } else if (lines.size() == 2) { |
| | 370 | return lines.get(0).intersectionCommand(node, lines.get(1)); |
| | 371 | } |
| 353 | 372 | throw new InvalidSelection(); |
| 354 | 373 | } |
| 355 | 374 | |
| … |
… |
|
| 378 | 397 | * @throws InvalidSelection if nodes have same coordinates |
| 379 | 398 | */ |
| 380 | 399 | Line(Node first, Node last) throws InvalidSelection { |
| 381 | | xM = first.getEastNorth().getX(); |
| 382 | | yM = first.getEastNorth().getY(); |
| 383 | | double xB = last.getEastNorth().getX(); |
| 384 | | double yB = last.getEastNorth().getY(); |
| 385 | | a = yB - yM; |
| 386 | | b = xM - xB; |
| 387 | | double norm = Math.sqrt(a*a + b*b); |
| 388 | | if (norm == 0) |
| 389 | | throw new InvalidSelection("Nodes have same coordinates!"); |
| 390 | | a /= norm; |
| 391 | | b /= norm; |
| 392 | | c = -(a*xM + b*yM); |
| | 400 | final var coordFirst = first.getEastNorth(); |
| | 401 | xM = coordFirst.getX(); |
| | 402 | yM = coordFirst.getY(); |
| | 403 | |
| | 404 | final var coordLast = last.getEastNorth(); |
| | 405 | final double xB = coordLast.getX(); |
| | 406 | final double yB = coordLast.getY(); |
| | 407 | |
| | 408 | |
| | 409 | this.a = yB - yM; |
| | 410 | this.b = xM - xB; |
| | 411 | final double norm = Math.hypot(this.a, this.b); |
| | 412 | if (norm == 0) { |
| | 413 | throw new InvalidSelection("Nodes have same coordinates!"); |
| | 414 | } |
| | 415 | this.a /= norm; |
| | 416 | this.b /= norm; |
| | 417 | this.c = -(this.a * this.xM + this.b * this.yM); |
| 393 | 418 | } |
| 394 | 419 | |
| 395 | 420 | /** |
| … |
… |
|
| 407 | 432 | * @return The command that do the projection of this node |
| 408 | 433 | */ |
| 409 | 434 | public Command projectionCommand(Node n) { |
| 410 | | double s = (xM - n.getEastNorth().getX()) * a + (yM - n.getEastNorth().getY()) * b; |
| | 435 | final double s = (xM - n.getEastNorth().getX()) * a + (yM - n.getEastNorth().getY()) * b; |
| 411 | 436 | return new MoveCommand(n, a*s, b*s); |
| 412 | 437 | } |
| 413 | 438 | |
| … |
… |
|
| 419 | 444 | * @throws InvalidSelection if two parallels ways found |
| 420 | 445 | */ |
| 421 | 446 | public Command intersectionCommand(Node n, Line other) throws InvalidSelection { |
| 422 | | double d = this.a * other.b - other.a * this.b; |
| 423 | | if (Math.abs(d) < 10e-6) |
| 424 | | // parallels lines |
| 425 | | throw new InvalidSelection(tr("Two parallels ways found. Abort.")); |
| 426 | | double x = (this.b * other.c - other.b * this.c) / d; |
| 427 | | double y = (other.a * this.c - this.a * other.c) / d; |
| 428 | | return new MoveCommand(n, x - n.getEastNorth().getX(), y - n.getEastNorth().getY()); |
| | 447 | final double d = this.a * other.b - other.a * this.b; |
| | 448 | if (Math.abs(d) < 10e-6) { |
| | 449 | // parallels lines |
| | 450 | throw new InvalidSelection(tr("Two parallel ways found. Abort.")); |
| | 451 | } |
| | 452 | final double x = (this.b * other.c - other.b * this.c) / d; |
| | 453 | final double y = (other.a * this.c - this.a * other.c) / d; |
| | 454 | |
| | 455 | final var eastNorth = n.getEastNorth(); |
| | 456 | return new MoveCommand(n, x - eastNorth.getX(), y - eastNorth.getY()); |
| 429 | 457 | } |
| 430 | 458 | } |
| 431 | 459 | |
| 432 | 460 | @Override |
| 433 | 461 | protected void updateEnabledState() { |
| 434 | | DataSet ds = getLayerManager().getEditDataSet(); |
| | 462 | final DataSet ds = getLayerManager().getEditDataSet(); |
| 435 | 463 | setEnabled(ds != null && !ds.selectionEmpty()); |
| 436 | 464 | } |
| 437 | 465 | |