Changeset 13990 in osm for applications/editors/josm/plugins/terracer/src
- Timestamp:
- 2009-03-06T19:55:38+01:00 (16 years ago)
- Location:
- applications/editors/josm/plugins/terracer/src/terracer
- Files:
-
- 1 added
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
applications/editors/josm/plugins/terracer/src/terracer/TerracerAction.java
r13561 r13990 9 9 10 10 import static org.openstreetmap.josm.tools.I18n.tr; 11 11 import static org.openstreetmap.josm.tools.I18n.trn; 12 13 import java.awt.BorderLayout; 14 import java.awt.Choice; 15 import java.awt.Component; 16 import java.awt.GridBagLayout; 12 17 import java.awt.event.ActionEvent; 18 import java.awt.event.ItemEvent; 19 import java.awt.event.ItemListener; 13 20 import java.awt.event.KeyEvent; 21 import java.util.ArrayList; 22 import java.util.Arrays; 14 23 import java.util.Collection; 24 import java.util.Collections; 25 import java.util.HashMap; 15 26 import java.util.LinkedList; 16 27 import java.util.List; 28 import java.util.Map; 29 import java.util.TreeMap; 30 import java.util.TreeSet; 31 32 import javax.swing.JComponent; 33 import javax.swing.JFormattedTextField; 34 import javax.swing.JLabel; 17 35 import javax.swing.JOptionPane; 36 import javax.swing.JPanel; 37 import javax.swing.JSpinner; 38 import javax.swing.JTextField; 39 import javax.swing.SpinnerNumberModel; 40 import javax.swing.SpringLayout; 41 import javax.swing.event.ChangeEvent; 42 import javax.swing.event.ChangeListener; 18 43 19 44 import org.openstreetmap.josm.Main; … … 25 50 import org.openstreetmap.josm.data.osm.Node; 26 51 import org.openstreetmap.josm.data.osm.OsmPrimitive; 52 import org.openstreetmap.josm.data.osm.Relation; 53 import org.openstreetmap.josm.data.osm.RelationMember; 27 54 import org.openstreetmap.josm.data.osm.Way; 55 import org.openstreetmap.josm.gui.tagging.TaggingPreset.Item; 56 import org.openstreetmap.josm.tools.AutoCompleteComboBox; 57 import org.openstreetmap.josm.tools.GBC; 58 import org.openstreetmap.josm.tools.Pair; 28 59 import org.openstreetmap.josm.tools.Shortcut; 29 60 … … 40 71 public final class TerracerAction extends JosmAction { 41 72 73 // smsms1 asked for the last value to be remembered to make it easier to do 74 // repeated terraces. this is the easiest, but not necessarily nicest, way. 75 private static String lastSelectedValue = ""; 76 42 77 public TerracerAction() { 43 78 super(tr("Terrace a building"), … … 65 100 Way way = (Way)prim; 66 101 67 if ((way.nodes.size() == 5) &&102 if ((way.nodes.size() >= 5) && 68 103 way.isClosed()) { 69 104 // first ask the user how many buildings to terrace into 70 String answer = 71 JOptionPane.showInputDialog( 72 tr("How many buildings are in the terrace?")); 73 74 // if the answer was null then the user clicked "Cancel" 75 if (answer != null) { 76 int nb = Integer.parseInt(answer); 77 terraceBuilding(way, nb); 105 HouseNumberDialog dialog = new HouseNumberDialog(); 106 final JOptionPane optionPane = new JOptionPane(dialog, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION); 107 108 String title = trn("Change {0} object", "Change {0} objects", sel.size(), sel.size()); 109 if(sel.size() == 0) 110 title = tr("Nothing selected!"); 111 112 optionPane.createDialog(Main.parent, title).setVisible(true); 113 Object answerObj = optionPane.getValue(); 114 if (answerObj != null && 115 answerObj != JOptionPane.UNINITIALIZED_VALUE && 116 (answerObj instanceof Integer && 117 (Integer)answerObj == JOptionPane.OK_OPTION)) { 118 119 // call out to the method which does the actual 120 // terracing. 121 terraceBuilding(way, 122 dialog.numberFrom(), 123 dialog.numberTo(), 124 dialog.stepSize(), 125 dialog.streetName()); 126 78 127 } 79 128 } else { … … 89 138 if (badSelect) { 90 139 JOptionPane.showMessageDialog(Main.parent, 91 tr("Select a single, closed way of four nodes."));140 tr("Select a single, closed way of at least four nodes.")); 92 141 } 93 142 } … … 102 151 * 103 152 * @param w The closed, quadrilateral way to terrace. 104 * @param nb The number of buildings to terrace into.105 */106 private void terraceBuilding(Way w, int nb) {153 */ 154 private void terraceBuilding(Way w, int from, int to, int step, String streetName) { 155 final int nb = 1 + (to - from) / step; 107 156 108 157 // now find which is the longest side connecting the first node 109 Node[] nodes = w.nodes.toArray(new Node[] {}); 110 double side1 = nodes[0].coor.greatCircleDistance(nodes[1].coor); 111 double side2 = nodes[0].coor.greatCircleDistance(nodes[3].coor); 112 158 Pair<Way,Way> interp = findFrontAndBack(w); 159 160 final double frontLength = wayLength(interp.a); 161 final double backLength = wayLength(interp.b); 162 113 163 // new nodes array to hold all intermediate nodes 114 164 Node[][] new_nodes = new Node[2][nb + 1]; 115 116 if (side1 > side2) { 117 new_nodes[0][0] = nodes[0]; 118 new_nodes[0][nb] = nodes[1]; 119 new_nodes[1][0] = nodes[3]; 120 new_nodes[1][nb] = nodes[2]; 121 } else { 122 new_nodes[0][0] = nodes[0]; 123 new_nodes[0][nb] = nodes[3]; 124 new_nodes[1][0] = nodes[1]; 125 new_nodes[1][nb] = nodes[2]; 126 } 127 128 Collection<Command> commands = new LinkedList<Command>(); 129 Collection<Way> ways = new LinkedList<Way>(); 130 131 // create intermediate nodes by interpolating. 132 for (int i = 1; i < nb; ++i) { 133 new_nodes[0][i] = interpolateNode(new_nodes[0][0], new_nodes[0][nb], 134 (double)(i)/(double)(nb)); 135 new_nodes[1][i] = interpolateNode(new_nodes[1][0], new_nodes[1][nb], 136 (double)(i)/(double)(nb)); 165 166 Collection<Command> commands = new LinkedList<Command>(); 167 Collection<Way> ways = new LinkedList<Way>(); 168 169 // create intermediate nodes by interpolating. 170 for (int i = 0; i <= nb; ++i) { 171 new_nodes[0][i] = interpolateAlong(interp.a, frontLength * (double)(i) / (double)(nb)); 172 new_nodes[1][i] = interpolateAlong(interp.b, backLength * (double)(i) / (double)(nb)); 137 173 commands.add(new AddCommand(new_nodes[0][i])); 138 174 commands.add(new AddCommand(new_nodes[1][i])); 139 175 } 140 176 141 // assemble new quadrilateral, closed ways 142 for (int i = 0; i < nb; ++i) { 143 Way terr = new Way(); 144 // Using Way.nodes.add rather than Way.addNode because the latter doesn't 145 // exist in older versions of JOSM. 146 terr.nodes.add(new_nodes[0][i]); 147 terr.nodes.add(new_nodes[0][i+1]); 148 terr.nodes.add(new_nodes[1][i+1]); 149 terr.nodes.add(new_nodes[1][i]); 150 terr.nodes.add(new_nodes[0][i]); 151 ways.add(terr); 152 commands.add(new AddCommand(terr)); 153 } 154 155 Main.main.undoRedo.add(new SequenceCommand(tr("Terrace"), commands)); 156 Main.ds.setSelected(ways); 177 // create a new relation for addressing 178 Relation relatedStreet = new Relation(); 179 relatedStreet.put("type", "relatedStreet"); 180 if (streetName != null) { 181 relatedStreet.put("name", streetName); 182 } 183 // note that we don't actually add the street member to the relation, as 184 // the name isn't unambiguous and it could cause confusion if the editor were 185 // to automatically select one which wasn't the one the user intended. 186 187 // assemble new quadrilateral, closed ways 188 for (int i = 0; i < nb; ++i) { 189 Way terr = new Way(); 190 // Using Way.nodes.add rather than Way.addNode because the latter doesn't 191 // exist in older versions of JOSM. 192 terr.nodes.add(new_nodes[0][i]); 193 terr.nodes.add(new_nodes[0][i+1]); 194 terr.nodes.add(new_nodes[1][i+1]); 195 terr.nodes.add(new_nodes[1][i]); 196 terr.nodes.add(new_nodes[0][i]); 197 terr.put("addr:housenumber", "" + (from + i * step)); 198 terr.put("building", "yes"); 199 if (streetName != null) { 200 terr.put("addr:street", streetName); 201 } 202 relatedStreet.members.add(new RelationMember("house", terr)); 203 ways.add(terr); 204 commands.add(new AddCommand(terr)); 205 } 206 207 commands.add(new AddCommand(relatedStreet)); 208 209 Main.main.undoRedo.add(new SequenceCommand(tr("Terrace"), commands)); 210 Main.ds.setSelected(ways); 211 } 212 213 /** 214 * Creates a node at a certain distance along a way, as calculated by the 215 * great circle distance. 216 * 217 * Note that this really isn't an efficient way to do this and leads to 218 * O(N^2) running time for the main algorithm, but its simple and easy 219 * to understand, and probably won't matter for reasonable-sized ways. 220 * 221 * @param w The way to interpolate. 222 * @param l The length at which to place the node. 223 * @return A node at a distance l along w from the first point. 224 */ 225 private Node interpolateAlong(Way w, double l) { 226 Node n = null; 227 for (Pair<Node,Node> p : w.getNodePairs(false)) { 228 final double seg_length = p.a.coor.greatCircleDistance(p.b.coor); 229 if (l <= seg_length) { 230 n = interpolateNode(p.a, p.b, l / seg_length); 231 break; 232 } else { 233 l -= seg_length; 234 } 235 } 236 if (n == null) { 237 // sometimes there is a small overshoot due to numerical roundoff, so we just 238 // set these cases to be equal to the last node. its not pretty, but it works ;-) 239 n = w.nodes.get(w.nodes.size() - 1); 240 } 241 return n; 242 } 243 244 /** 245 * Calculates the great circle length of a way by summing the great circle 246 * distance of each pair of nodes. 247 * 248 * @param w The way to calculate length of. 249 * @return The length of the way. 250 */ 251 private double wayLength(Way w) { 252 double length = 0.0; 253 for (Pair<Node,Node> p : w.getNodePairs(false)) { 254 length += p.a.coor.greatCircleDistance(p.b.coor); 255 } 256 return length; 257 } 258 259 /** 260 * Given a way, try and find a definite front and back by looking at the 261 * segments to find the "sides". Sides are assumed to be single segments 262 * which cannot be contiguous. 263 * 264 * @param w The way to analyse. 265 * @return A pair of ways (front, back) pointing in the same directions. 266 */ 267 private Pair<Way, Way> findFrontAndBack(Way w) { 268 // calculate the "side-ness" score for each segment of the way 269 double[] sideness = calculateSideness(w); 270 271 // find the largest two sidenesses which are not contiguous 272 int[] indexes = sortedIndexes(sideness); 273 int side1 = indexes[0]; 274 int side2 = indexes[1]; 275 // if side2 is contiguous with side1 then look further down the 276 // list. we know there are at least 4 sides, as anything smaller 277 // than a quadrilateral would have been rejected at an earlier 278 // stage. 279 if (Math.abs(side1 - side2) < 2) { 280 side2 = indexes[2]; 281 } 282 if (Math.abs(side1 - side2) < 2) { 283 side2 = indexes[3]; 284 } 285 286 // swap side1 and side2 into sorted order. 287 if (side1 > side2) { 288 // i can't believe i have to write swap() myself - surely java standard 289 // library has this somewhere??!!?ONE! 290 int tmp = side2; 291 side2 = side1; 292 side1 = tmp; 293 } 294 295 Way front = new Way(); 296 Way back = new Way(); 297 for (int i = side2 + 1; i < w.nodes.size() - 1; ++i) { 298 front.nodes.add(w.nodes.get(i)); 299 } 300 for (int i = 0; i <= side1; ++i) { 301 front.nodes.add(w.nodes.get(i)); 302 } 303 // add the back in reverse order so that the front and back ways point 304 // in the same direction. 305 for (int i = side2; i > side1; --i) { 306 back.nodes.add(w.nodes.get(i)); 307 } 308 309 return new Pair<Way, Way>(front, back); 310 } 311 312 /** 313 * Given an array of doubles (but this could made generic very easily) sort 314 * into order and return the array of indexes such that, for a returned array 315 * x, a[x[i]] is sorted for ascending index i. 316 * 317 * This isn't efficient at all, but should be fine for the small arrays we're 318 * expecting. If this gets slow - replace it with some more efficient algorithm. 319 * 320 * @param a The array to sort. 321 * @return An array of indexes, the same size as the input, such that a[x[i]] 322 * is in sorted order. 323 */ 324 private int[] sortedIndexes(final double[] a) { 325 class SortWithIndex implements Comparable<SortWithIndex> { 326 public double x; 327 public int i; 328 public SortWithIndex(double a, int b) { 329 x = a; i = b; 330 } 331 public int compareTo(SortWithIndex o) { 332 return Double.compare(x, o.x); 333 }; 334 } 335 336 final int length = a.length; 337 ArrayList<SortWithIndex> sortable = new ArrayList<SortWithIndex>(length); 338 for (int i = 0; i < length; ++i) { 339 sortable.add(new SortWithIndex(a[i], i)); 340 } 341 Collections.sort(sortable); 342 343 int[] indexes = new int[length]; 344 for (int i = 0; i < length; ++i) { 345 indexes[i] = sortable.get(i).i; 346 } 347 348 return indexes; 349 } 350 351 /** 352 * Calculate "sideness" metric for each segment in a way. 353 */ 354 private double[] calculateSideness(Way w) { 355 final int length = w.nodes.size() - 1; 356 double[] sideness = new double[length]; 357 358 sideness[0] = calculateSideness( 359 w.nodes.get(length - 1), w.nodes.get(0), 360 w.nodes.get(1), w.nodes.get(2)); 361 for (int i = 1; i < length - 1; ++i) { 362 sideness[i] = calculateSideness( 363 w.nodes.get(i-1), w.nodes.get(i), 364 w.nodes.get(i+1), w.nodes.get(i+2)); 365 } 366 sideness[length-1] = calculateSideness( 367 w.nodes.get(length - 2), w.nodes.get(length - 1), 368 w.nodes.get(length), w.nodes.get(1)); 369 370 return sideness; 371 } 372 373 /** 374 * Calculate sideness of a single segment given the nodes which make up that 375 * segment and its previous and next segments in order. Sideness is calculated 376 * for the segment b-c. 377 */ 378 private double calculateSideness(Node a, Node b, Node c, Node d) { 379 final double ndx = b.coor.getX() - a.coor.getX(); 380 final double pdx = d.coor.getX() - c.coor.getX(); 381 final double ndy = b.coor.getY() - a.coor.getY(); 382 final double pdy = d.coor.getY() - c.coor.getY(); 383 384 return (ndx * pdx + ndy * pdy) / 385 Math.sqrt((ndx * ndx + ndy * ndy) * (pdx * pdx + pdy * pdy)); 386 } 387 388 /** 389 * Dialog box to allow users to input housenumbers in a nice way. 390 */ 391 class HouseNumberDialog extends JPanel { 392 private SpinnerNumberModel lo, hi; 393 private JSpinner clo, chi; 394 private Choice step; 395 private AutoCompleteComboBox street; 396 397 public HouseNumberDialog() { 398 super(new GridBagLayout()); 399 lo = new SpinnerNumberModel(1, 1, 1, 1); 400 hi = new SpinnerNumberModel(1, 1, null, 1); 401 step = new Choice(); 402 step.add(tr("All")); 403 step.add(tr("Even")); 404 step.add(tr("Odd")); 405 clo = new JSpinner(lo); 406 chi = new JSpinner(hi); 407 408 lo.addChangeListener(new ChangeListener() { 409 public void stateChanged(ChangeEvent e) { 410 hi.setMinimum((Integer)lo.getNumber()); 411 } 412 }); 413 hi.addChangeListener(new ChangeListener() { 414 public void stateChanged(ChangeEvent e) { 415 lo.setMaximum((Integer)hi.getNumber()); 416 } 417 }); 418 step.addItemListener(new ItemListener() { 419 public void itemStateChanged(ItemEvent e) { 420 if (step.getSelectedItem() == tr("All")) { 421 hi.setStepSize(1); 422 lo.setStepSize(1); 423 } else { 424 int odd_or_even = 0; 425 int min = 0; 426 427 if (step.getSelectedItem() == tr("Even")) { 428 odd_or_even = 0; 429 min = 2; 430 } else { 431 odd_or_even = 1; 432 min = 1; 433 } 434 435 if ((lo.getNumber().intValue() & 1) != odd_or_even) { 436 int nextval = lo.getNumber().intValue() - 1; 437 lo.setValue((nextval > min) ? nextval : min); 438 } 439 if ((hi.getNumber().intValue() & 1) != odd_or_even) { 440 int nextval = hi.getNumber().intValue() - 1; 441 hi.setValue((nextval > min) ? nextval : min); 442 } 443 lo.setMinimum(min); 444 hi.setStepSize(2); 445 lo.setStepSize(2); 446 } 447 } 448 }); 449 450 final TreeSet<String> names = createAutoCompletionInfo(); 451 452 street = new AutoCompleteComboBox(); 453 street.setPossibleItems(names); 454 street.setEditable(true); 455 street.setSelectedItem(null); 456 457 JFormattedTextField x; 458 x = ((JSpinner.DefaultEditor)clo.getEditor()).getTextField(); 459 x.setColumns(5); 460 x = ((JSpinner.DefaultEditor)chi.getEditor()).getTextField(); 461 x.setColumns(5); 462 addLabelled("From number: ", clo); 463 addLabelled("To number: ", chi); 464 addLabelled("Interpolation: ", step); 465 addLabelled("Street name (Optional): ", street); 466 } 467 468 private void addLabelled(String str, Component c) { 469 JLabel label = new JLabel(str); 470 add(label, GBC.std()); 471 label.setLabelFor(c); 472 add(c, GBC.eol()); 473 } 474 475 public int numberFrom() { 476 return lo.getNumber().intValue(); 477 } 478 479 public int numberTo() { 480 return hi.getNumber().intValue(); 481 } 482 483 public int stepSize() { 484 return (step.getSelectedItem() == tr("All")) ? 1 : 2; 485 } 486 487 public String streetName() { 488 Object selected = street.getSelectedItem(); 489 if (selected == null) { 490 return null; 491 } else { 492 String name = selected.toString(); 493 if (name.isEmpty()) { 494 return null; 495 } else { 496 return name; 497 } 498 } 499 } 500 } 501 502 /** 503 * Generates a list of all visible names of highways in order to do 504 * autocompletion on the road name. 505 */ 506 private TreeSet<String> createAutoCompletionInfo() { 507 final TreeSet<String> names = new TreeSet<String>(); 508 for (OsmPrimitive osm : Main.ds.allNonDeletedPrimitives()) { 509 if (osm.keys != null && 510 osm.keys.containsKey("highway") && 511 osm.keys.containsKey("name")) { 512 names.add(osm.keys.get("name")); 513 } 514 } 515 return names; 157 516 } 158 517 … … 167 526 */ 168 527 private Node interpolateNode(Node a, Node b, double f) { 169 // this isn't quite right - we should probably be interpolating 528 Node n = new Node(interpolateLatLon(a, b, f)); 529 return n; 530 } 531 532 /** 533 * Calculates the interpolated position between the argument nodes. Interpolates 534 * linearly in Lat/Lon coordinates. 535 * 536 * @param a First node, at which f=0. 537 * @param b Last node, at which f=1. 538 * @param f Fractional position between first and last nodes. 539 * @return The interpolated position. 540 */ 541 private LatLon interpolateLatLon(Node a, Node b, double f) { 542 // this isn't quite right - we should probably be interpolating 170 543 // screen coordinates rather than lat/lon, but it doesn't seem to 171 544 // make a great deal of difference at the scale of most terraces. 172 Node n = new Node(new LatLon( 173 a.coor.lat() * (1.0 - f) + b.coor.lat() * f, 174 a.coor.lon() * (1.0 - f) + b.coor.lon() * f 175 )); 176 return n; 545 return new LatLon(a.coor.lat() * (1.0 - f) + b.coor.lat() * f, 546 a.coor.lon() * (1.0 - f) + b.coor.lon() * f); 177 547 } 178 548 } -
applications/editors/josm/plugins/terracer/src/terracer/TerracerPlugin.java
r13553 r13990 20 20 public TerracerPlugin() { 21 21 MainMenu.add(Main.main.menu.toolsMenu, new TerracerAction()); 22 MainMenu.add(Main.main.menu.toolsMenu, new ReverseTerraceAction()); 22 23 } 23 24 }
Note:
See TracChangeset
for help on using the changeset viewer.