| | 51 | * InvalidSelection exception has to be raised when action can't be perform |
| | 52 | */ |
| | 53 | private class InvalidSelection extends Exception { |
| | 54 | |
| | 55 | /** |
| | 56 | * Create an InvalidSelection exception with default message |
| | 57 | */ |
| | 58 | public InvalidSelection() { |
| | 59 | super(tr("Please select at least three nodes.")); |
| | 60 | } |
| | 61 | |
| | 62 | /** |
| | 63 | * Create an InvalidSelection exception with specific message |
| | 64 | * @param msg Message that will be display to the user |
| | 65 | */ |
| | 66 | public InvalidSelection(String msg) { |
| | 67 | super(msg); |
| | 68 | } |
| | 69 | } |
| | 70 | |
| | 71 | /** |
| 102 | | private void showWarning() { |
| 103 | | showWarning(tr("Please select at least three nodes.")); |
| 104 | | } |
| 105 | | |
| 106 | | private void showWarning(String msg) { |
| 107 | | new Notification(msg) |
| 108 | | .setIcon(JOptionPane.INFORMATION_MESSAGE) |
| 109 | | .show(); |
| 110 | | } |
| 111 | | |
| 112 | | private static int indexWrap(int size, int i) { |
| 113 | | i = i % size; // -2 % 5 = -2, -7 % 5 = -2, -5 % 5 = 0 |
| 114 | | if (i < 0) { |
| 115 | | i = size + i; |
| 116 | | } |
| 117 | | return i; |
| 118 | | } |
| 119 | | // get the node in w at index i relative to refI |
| 120 | | private static Node getNodeRelative(Way w, int refI, int i) { |
| 121 | | int absI = indexWrap(w.getNodesCount(), refI + i); |
| 122 | | if(w.isClosed() && refI + i < 0) { |
| 123 | | absI--; // node duplicated in closed ways |
| 124 | | } |
| 125 | | return w.getNode(absI); |
| 126 | | } |
| 127 | | |
| 151 | | /// Only ways selected -> For each way align their nodes taking care of intersection |
| 152 | | if(selectedNodes.isEmpty() && !selectedWays.isEmpty()) { |
| 153 | | alignMultiWay(selectedWays); |
| 154 | | return; |
| 155 | | } |
| 156 | | /// More than 3 nodes selected -> align those nodes |
| 157 | | else if(selectedNodes.size() >= 3) { |
| 158 | | nodes.addAll(selectedNodes); |
| 159 | | // use the nodes furthest apart as anchors |
| 160 | | nodePairFurthestApart(nodes, anchors); |
| 161 | | } |
| 162 | | /// One node selected -> align that node to the relevant neighbors |
| 163 | | else if (selectedNodes.size() == 1) { |
| 164 | | Node n = selectedNodes.iterator().next(); |
| 165 | | |
| 166 | | Way w = null; |
| 167 | | if(selectedWays.size() == 1) { |
| 168 | | w = selectedWays.iterator().next(); |
| 169 | | if (!w.containsNode(n)) |
| 170 | | // warning |
| 171 | | return; |
| 172 | | } else { |
| 173 | | List<Way> refWays = OsmPrimitive.getFilteredList(n.getReferrers(), Way.class); |
| 174 | | if (refWays.size() == 1) { // node used in only one way |
| 175 | | w = refWays.iterator().next(); |
| 176 | | } |
| | 150 | /// Only ways selected -> For each way align their nodes taking care of intersection |
| | 151 | if(selectedNodes.isEmpty() && !selectedWays.isEmpty()) { |
| | 152 | cmd = alignMultiWay(selectedWays); |
| 178 | | if (w == null || w.getNodesCount() < 3) |
| 179 | | // warning, need at least 3 nodes |
| 180 | | return; |
| 181 | | |
| 182 | | // Find anchors |
| 183 | | int nodeI = w.getNodes().indexOf(n); |
| 184 | | // End-node in non-circular way selected: align this node with the two neighbors. |
| 185 | | if ((nodeI == 0 || nodeI == w.getNodesCount()-1) && !w.isClosed()) { |
| 186 | | int direction = nodeI == 0 ? 1 : -1; |
| 187 | | anchors[0] = w.getNode(nodeI + direction); |
| 188 | | anchors[1] = w.getNode(nodeI + direction*2); |
| 189 | | } else { |
| 190 | | // o---O---o |
| 191 | | anchors[0] = getNodeRelative(w, nodeI, 1); |
| 192 | | anchors[1] = getNodeRelative(w, nodeI, -1); |
| | 154 | /// Only 1 node selected -> align this node relative to referers way |
| | 155 | else if(selectedNodes.size() == 1) { |
| | 156 | Node selectedNode = selectedNodes.get(0); |
| | 157 | List<Way> involvedWays = null; |
| | 158 | if(selectedWays.isEmpty()) |
| | 159 | /// No selected way, all way containing this node are used |
| | 160 | involvedWays = OsmPrimitive.getFilteredList(selectedNode.getReferrers(), Way.class); |
| | 161 | else |
| | 162 | /// Selected way, use only these ways |
| | 163 | involvedWays = selectedWays; |
| | 164 | List<Line> lines = getInvolvedLines(selectedNode, involvedWays); |
| | 165 | if(lines.size() > 2 || lines.isEmpty()) |
| | 166 | throw new InvalidSelection(); |
| | 167 | cmd = alignSingleNode(selectedNodes.get(0), lines); |
| 202 | | |
| 203 | | Collection<Command> cmds = new ArrayList<Command>(nodes.size()); |
| 204 | | |
| 205 | | createAlignNodesCommands(anchors, nodes, cmds); |
| 206 | | |
| 207 | | // Do it! |
| 208 | | Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Line"), cmds)); |
| 209 | | Main.map.repaint(); |
| 210 | | } |
| 211 | | |
| 212 | | private void createAlignNodesCommands(Node[] anchors, Collection<Node> nodes, Collection<Command> cmds) { |
| 213 | | Node nodea = anchors[0]; |
| 214 | | Node nodeb = anchors[1]; |
| 215 | | |
| 216 | | // The anchors are aligned per definition |
| 217 | | nodes.remove(nodea); |
| 218 | | nodes.remove(nodeb); |
| 219 | | |
| 220 | | // Find out co-ords of A and B |
| 221 | | double ax = nodea.getEastNorth().east(); |
| 222 | | double ay = nodea.getEastNorth().north(); |
| 223 | | double bx = nodeb.getEastNorth().east(); |
| 224 | | double by = nodeb.getEastNorth().north(); |
| 225 | | |
| 226 | | // OK, for each node to move, work out where to move it! |
| 227 | | for (Node n : nodes) { |
| 228 | | // Get existing co-ords of node to move |
| 229 | | double nx = n.getEastNorth().east(); |
| 230 | | double ny = n.getEastNorth().north(); |
| 231 | | |
| 232 | | if (ax == bx) { |
| 233 | | // Special case if AB is vertical... |
| 234 | | nx = ax; |
| 235 | | } else if (ay == by) { |
| 236 | | // ...or horizontal |
| 237 | | ny = ay; |
| 238 | | } else { |
| 239 | | // Otherwise calculate position by solving y=mx+c |
| 240 | | double m1 = (by - ay) / (bx - ax); |
| 241 | | double c1 = ay - (ax * m1); |
| 242 | | double m2 = (-1) / m1; |
| 243 | | double c2 = n.getEastNorth().north() - (n.getEastNorth().east() * m2); |
| 244 | | |
| 245 | | nx = (c2 - c1) / (m1 - m2); |
| 246 | | ny = (m1 * nx) + c1; |
| 247 | | } |
| 248 | | double newX = nx - n.getEastNorth().east(); |
| 249 | | double newY = ny - n.getEastNorth().north(); |
| 250 | | // Add the command to move the node to its new position. |
| 251 | | cmds.add(new MoveCommand(n, newX, newY)); |
| | 182 | } catch (InvalidSelection except) { |
| | 183 | new Notification(except.getMessage()) |
| | 184 | .setIcon(JOptionPane.INFORMATION_MESSAGE) |
| | 185 | .show(); |
| | 190 | * Align nodes in case that only nodes are selected |
| | 191 | * @param nodes Nodes to be aligned |
| | 192 | * @return Command that perform action |
| | 193 | * @throws InvalidSelection |
| | 194 | */ |
| | 195 | private Command alignOnlyNodes(List<Node> nodes) throws InvalidSelection { |
| | 196 | Node[] anchors = new Node[2]; // oh, java I love you so much.. |
| | 197 | // use the nodes furthest apart as anchors |
| | 198 | nodePairFurthestApart(nodes, anchors); |
| | 199 | Collection<Command> cmds = new ArrayList<Command>(nodes.size()); |
| | 200 | Line line = new Line(anchors[0], anchors[1]); |
| | 201 | for(Node node: nodes) |
| | 202 | if(node != anchors[0] && node != anchors[1]) |
| | 203 | cmds.add(line.projectionCommand(node)); |
| | 204 | return new SequenceCommand(tr("Align Nodes in Line"), cmds); |
| | 205 | } |
| | 206 | |
| | 207 | /** |
| | 246 | * Get lines useful to do alignment of a single node |
| | 247 | * @param node Node to be aligned |
| | 248 | * @param refWays Ways where useful lines will be searched |
| | 249 | * @return List of useful lines |
| | 250 | * @throws InvalidSelection |
| | 251 | */ |
| | 252 | private List<Line> getInvolvedLines(Node node, List<Way> refWays) throws InvalidSelection { |
| | 253 | ArrayList<Line> lines = new ArrayList<Line>(); |
| | 254 | for(Way way: refWays) { |
| | 255 | List<Node> nodes = way.getNodes(); |
| | 256 | for(int i = 1; i < nodes.size()-1; i++) |
| | 257 | if(nodes.get(i) == node) |
| | 258 | lines.add(new Line(nodes.get(i-1), nodes.get(i+1))); |
| | 259 | } |
| | 260 | return lines; |
| | 261 | } |
| | 262 | |
| | 263 | /** |
| | 264 | * Align a single node relative to a set of lines #9081 |
| | 265 | * @param node Node to be aligned |
| | 266 | * @param lines Lines to align node on |
| | 267 | * @return Command that perform action |
| | 268 | * @throws InvalidSelection |
| | 269 | */ |
| | 270 | private Command alignSingleNode(Node node, List<Line> lines) throws InvalidSelection { |
| | 271 | if(lines.size() == 1) |
| | 272 | return lines.get(0).projectionCommand(node); |
| | 273 | else if(lines.size() == 2) |
| | 274 | return lines.get(0).intersectionCommand(node, lines.get(1)); |
| | 275 | throw new InvalidSelection(); |
| | 276 | } |
| | 277 | |
| | 278 | /** |
| 319 | | public Line(Way way) { |
| 320 | | xM = way.firstNode().getEastNorth().getX(); |
| 321 | | yM = way.firstNode().getEastNorth().getY(); |
| 322 | | double xB = way.lastNode().getEastNorth().getX(); |
| 323 | | double yB = way.lastNode().getEastNorth().getY(); |
| 324 | | a = yB - yM; |
| 325 | | b = xM - xB; |
| 326 | | double norm = Math.sqrt(a*a + b*b); |
| 327 | | if (norm == 0) { |
| 328 | | norm = 1; |
| 329 | | } |
| | 295 | private double norm; |
| | 296 | |
| | 297 | public Line(Node first, Node last) throws InvalidSelection { |
| | 298 | x1 = first.getEastNorth().getX(); |
| | 299 | y1 = first.getEastNorth().getY(); |
| | 300 | x2 = last.getEastNorth().getX(); |
| | 301 | y2 = last.getEastNorth().getY(); |
| | 302 | a = y2 - y1; |
| | 303 | b = x1 - x2; |
| | 304 | norm = Math.sqrt(a*a + b*b); |
| | 305 | if (norm == 0) |
| | 306 | // Nodes have same coordinates ! |
| | 307 | throw new InvalidSelection(); |