| 92 | | // Fix #7341. Find the first way having all nodes in common to sort them in its nodes order |
| 93 | | List<Node> consideredNodes = Arrays.asList(n1, n2, n3); |
| 94 | | for (Way w : selectedWays) { |
| 95 | | final List<Node> nodes = w.getNodes(); |
| 96 | | if (nodes.containsAll(consideredNodes)) { |
| 97 | | Collections.sort(consideredNodes, new Comparator<Node>() { |
| 98 | | @Override |
| 99 | | public int compare(Node a, Node b) { |
| 100 | | return nodes.indexOf(a) - nodes.indexOf(b); |
| 101 | | } |
| 102 | | }); |
| 103 | | n1 = consideredNodes.get(0); |
| 104 | | n2 = consideredNodes.get(1); |
| 105 | | n3 = consideredNodes.get(2); |
| 106 | | break; |
| 107 | | } |
| 108 | | } |
| 109 | | |
| 110 | | for (Node n : consideredNodes) { |
| 111 | | targetWays.addAll(n.getParentWays()); |
| 112 | | } |
| 131 | | //// Create the new arc nodes. Insert anchor nodes at correct positions. |
| 132 | | List<Node> arcNodes = new ArrayList<>(points.size()); |
| 133 | | arcNodes.add(n1); |
| 134 | | int i = 1; |
| 135 | | for (EastNorth p : slice(points, 1, -2)) { |
| 136 | | if (p2Index.value != null && i == p2Index.value) { |
| 137 | | Node n2new = new Node(n2); |
| 138 | | n2new.setEastNorth(p); |
| 139 | | arcNodes.add(n2); // add the original n2, or else we can't find it in the target ways |
| 140 | | cmds.add(new ChangeCommand(n2, n2new)); |
| 141 | | } else { |
| 142 | | Node n = new Node(p); |
| 143 | | arcNodes.add(n); |
| 144 | | cmds.add(new AddCommand(ds, n)); |
| 145 | | } |
| 146 | | i++; |
| 147 | | } |
| 148 | | arcNodes.add(n3); |
| | 104 | // see #10777: calculate reasonable number of nodes for full circle (copy from CreateCircleAction) |
| | 105 | LatLon ll1 = ProjectionRegistry.getProjection().eastNorth2latlon(p1); |
| | 106 | LatLon ll2 = ProjectionRegistry.getProjection().eastNorth2latlon(center); |
| 157 | | private static void fuseArc(DataSet ds, Node[] anchorNodes, List<Node> arcNodes, Set<Way> targetWays, Collection<Command> cmds) { |
| 158 | | |
| 159 | | for (Way originalTw : targetWays) { |
| 160 | | Way tw = new Way(originalTw); |
| 161 | | boolean didChangeTw = false; |
| 162 | | /// Do one segment at the time (so ways only sharing one segment is fused too) |
| 163 | | for (int a = 0; a < 2; a++) { |
| 164 | | int anchorBi = arcNodes.indexOf(anchorNodes[a]); // TODO: optimize away |
| 165 | | int anchorEi = arcNodes.indexOf(anchorNodes[a + 1]); |
| 166 | | /// Find the anchor node indices in current target way |
| 167 | | int bi = -1, ei = -1; |
| 168 | | int i = -1; |
| 169 | | // Caution: nodes might appear multiple times. For now only handle simple closed ways |
| 170 | | for (Node n : tw.getNodes()) { |
| 171 | | i++; |
| 172 | | // We look for the first anchor node. The next should be directly to the left or right. |
| 173 | | // Exception when the way is closed |
| 174 | | if (Objects.equals(n, anchorNodes[a])) { |
| 175 | | bi = i; |
| 176 | | Node otherAnchor = anchorNodes[a + 1]; |
| 177 | | if (i > 0 && Objects.equals(tw.getNode(i - 1), otherAnchor)) { |
| 178 | | ei = i - 1; |
| 179 | | } else if (i < (tw.getNodesCount() - 1) && Objects.equals(tw.getNode(i + 1), otherAnchor)) { |
| 180 | | ei = i + 1; |
| 181 | | } else { |
| 182 | | continue; // this can happen with closed ways. Continue searching for the correct index |
| 183 | | } |
| 184 | | break; |
| 185 | | } |
| 186 | | } |
| 187 | | if (bi == -1 || ei == -1) { |
| 188 | | continue; // this segment is not part of the target way |
| 189 | | } |
| 190 | | didChangeTw = true; |
| 191 | | |
| 192 | | /// Insert the nodes of this segment |
| 193 | | // Direction of target way relative to the arc node order |
| 194 | | int twDirection = ei > bi ? 1 : 0; |
| 195 | | int anchorI = anchorBi + 1; // don't insert the anchor nodes again |
| 196 | | int twI = bi + (twDirection == 1 ? 1 : 0); // TODO: explain |
| 197 | | while (anchorI < anchorEi) { |
| 198 | | tw.addNode(twI, arcNodes.get(anchorI)); |
| 199 | | anchorI++; |
| 200 | | twI += twDirection; |
| 201 | | } |
| 202 | | } |
| 203 | | if (didChangeTw) |
| 204 | | cmds.add(new ChangeCommand(ds, originalTw, tw)); |
| | 120 | if (w == null) { |
| | 121 | w = new Way(); |
| | 122 | w.setNodes(anchorNodes); |
| | 123 | cmds.add(new AddCommand(ds, w)); |
| 208 | | /** |
| 209 | | * Return a list of coordinates lying an the circle arc determined by n1, n2 and n3. |
| 210 | | * The order of the list and which of the 3 possible arcs to construct are given by the order of n1, n2, n3 |
| 211 | | * @param p1 n1 |
| 212 | | * @param p2 n2 |
| 213 | | * @param p3 n3 |
| 214 | | * @param angleSeparation maximum angle separation between the arc points |
| 215 | | * @param includeAnchors include the anchor points in the list. The original objects will be used, not copies. |
| 216 | | * If {@code false}, p2 will be replaced by the closest arcpoint. |
| 217 | | * @param anchor2Index if non-null, it's value will be set to p2's index in the returned list. |
| 218 | | * @return list of coordinates lying an the circle arc determined by n1, n2 and n3 |
| 219 | | */ |
| 220 | | private static List<EastNorth> circleArcPoints(EastNorth p1, EastNorth p2, EastNorth p3, |
| 221 | | int angleSeparation, boolean includeAnchors, ReturnValue<Integer> anchor2Index) { |
| 222 | | |
| 223 | | // triangle: three single nodes needed or a way with three nodes |
| 224 | | |
| 225 | | // let's get some shorter names |
| 226 | | double x1 = p1.east(); |
| 227 | | double y1 = p1.north(); |
| 228 | | double x2 = p2.east(); |
| 229 | | double y2 = p2.north(); |
| 230 | | double x3 = p3.east(); |
| 231 | | double y3 = p3.north(); |
| 232 | | |
| 233 | | // calculate the center (xc,yc) |
| 234 | | double s = 0.5 * ((x2 - x3) * (x1 - x3) - (y2 - y3) * (y3 - y1)); |
| 235 | | double sUnder = (x1 - x2) * (y3 - y1) - (y2 - y1) * (x1 - x3); |
| 236 | | |
| 237 | | assert (sUnder != 0); |
| 238 | | |
| 239 | | s /= sUnder; |
| 240 | | |
| 241 | | double xc = 0.5 * (x1 + x2) + s * (y2 - y1); |
| 242 | | double yc = 0.5 * (y1 + y2) + s * (x1 - x2); |
| 243 | | |
| 244 | | // calculate the radius (r) |
| 245 | | double r = Math.sqrt(Math.pow(xc - x1, 2) + Math.pow(yc - y1, 2)); |
| 246 | | |
| 247 | | // The angles of the anchor points relative to the center |
| 248 | | double realA1 = calcang(xc, yc, x1, y1); |
| 249 | | double realA2 = calcang(xc, yc, x2, y2); |
| 250 | | double realA3 = calcang(xc, yc, x3, y3); |
| 251 | | |
| 252 | | double startAngle = realA1; |
| 253 | | // Transform the angles to get a consistent starting point |
| 254 | | double a2 = normalizeAngle(realA2 - startAngle); |
| 255 | | double a3 = normalizeAngle(realA3 - startAngle); |
| 256 | | int direction = a3 > a2 ? 1 : -1; |
| 257 | | |
| 258 | | double radialLength = 0; |
| 259 | | if (direction == 1) { // counter clockwise |
| 260 | | radialLength = a3; |
| 261 | | } else { // clockwise |
| 262 | | radialLength = Math.PI * 2 - a3; |
| 263 | | // make the angles consistent with the direction. |
| 264 | | a2 = (Math.PI * 2 - a2); |
| | 127 | if (!selectedWays.isEmpty()) { |
| | 128 | // Fix #7341. sort nodes in ways nodes order |
| | 129 | List<Node> consideredNodes = Arrays.asList(n1, n2, n3); |
| | 130 | Collections.sort(consideredNodes, (o1, o2) -> nodes.indexOf(o1) - nodes.indexOf(o2)); |
| | 131 | n1 = consideredNodes.get(0); |
| | 132 | n3 = consideredNodes.get(2); |
| 270 | | // Calculate the circle points in order |
| 271 | | double stepLength = radialLength / (numberOfNodesInArc-1); |
| 272 | | // Determine closest index to p2 |
| 273 | | |
| 274 | | int indexJustBeforeP2 = (int) Math.floor(a2 / stepLength); |
| 275 | | int closestIndexToP2 = indexJustBeforeP2; |
| 276 | | if ((a2 - indexJustBeforeP2 * stepLength) > ((indexJustBeforeP2 + 1) * stepLength - a2)) { |
| 277 | | closestIndexToP2 = indexJustBeforeP2 + 1; |
| | 135 | Set<Node> fixNodes = new HashSet<>(anchorNodes); |
| | 136 | if (!selectedWays.isEmpty()) { |
| | 137 | nodes.stream().filter(n -> n.getParentWays().size() > 1).forEach(fixNodes::add); |
| 287 | | double a = direction * stepLength; |
| 288 | | points.add(p1); |
| 289 | | if (indexJustBeforeP2 == 0 && includeAnchors) { |
| 290 | | points.add(p2); |
| | 145 | int pos1 = nodes.indexOf(n1); |
| | 146 | 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)); |
| 292 | | // i is ahead of the real index by one, since we need to be ahead in the angle calculation |
| 293 | | for (int i = 2; i < numberOfNodesInArc; i++) { |
| 294 | | double nextA = direction * (i * stepLength); |
| 295 | | double realAngle = a + startAngle; |
| 296 | | double x = xc + r * Math.cos(realAngle); |
| 297 | | double y = yc + r * Math.sin(realAngle); |
| 298 | | |
| 299 | | points.add(new EastNorth(x, y)); |
| 300 | | if (i - 1 == indexJustBeforeP2 && includeAnchors) { |
| 301 | | points.add(p2); |
| 302 | | } |
| 303 | | a = nextA; |
| | 158 | if (needsUndo) { |
| | 159 | // make sure we don't add the new nodes twice |
| | 160 | UndoRedoHandler.getInstance().undo(1); |
| 319 | | /** |
| 320 | | * Normalizes {@code angle} so it is between 0 and 2 PI |
| 321 | | * @param angle the angle |
| 322 | | * @return the normalized angle |
| 323 | | */ |
| 324 | | private static double normalizeAngle(double angle) { |
| 325 | | double PI2 = Math.PI * 2; |
| 326 | | if (angle < 0) { |
| 327 | | angle = angle + (Math.floor(-angle / PI2) + 1) * PI2; |
| 328 | | } else if (angle >= PI2) { |
| 329 | | angle = angle - Math.floor(angle / PI2) * PI2; |
| | 169 | // 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)); |
| 334 | | private static double calcang(double xc, double yc, double x, double y) { |
| 335 | | // calculate the angle from xc|yc to x|y |
| 336 | | if (xc == x && yc == y) |
| 337 | | return 0; // actually invalid, but we won't have this case in this context |
| 338 | | double yd = Math.abs(y - yc); |
| 339 | | if (yd == 0 && xc < x) |
| 340 | | return 0; |
| 341 | | if (yd == 0 && xc > x) |
| 342 | | return Math.PI; |
| 343 | | double xd = Math.abs(x - xc); |
| 344 | | double a = Math.atan2(xd, yd); |
| 345 | | if (y > yc) { |
| 346 | | a = Math.PI - a; |
| | 180 | // Search first fixed node |
| | 181 | int startPosition; |
| | 182 | for (startPosition = 0; startPosition < nodeCount; startPosition++) { |
| | 183 | if (fixNodes.contains(nodes.get(startPosition))) |
| | 184 | break; |
| 348 | | if (x < xc) { |
| 349 | | a = -a; |
| | 186 | int i = startPosition; // Start position for current arc |
| | 187 | 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))) |
| | 191 | 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) { |
| | 202 | delta += 2 * Math.PI; |
| | 203 | } else if (clockWise && delta > 0) { |
| | 204 | delta -= 2 * Math.PI; |
| | 205 | } |
| | 206 | // 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); |
| | 208 | 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; |
| | 222 | } |
| | 223 | i = j; // Update start point for next iteration |