Ticket #5729: JOSM-terracer.patch
File JOSM-terracer.patch, 53.6 KB (added by , 13 years ago) |
---|
-
src/terracer/HouseNumberInputDialog.java
15 15 import java.awt.Dimension; 16 16 import java.awt.GridBagConstraints; 17 17 import java.awt.GridBagLayout; 18 import java.util.ArrayList; 19 import java.util.Iterator; 18 20 import java.awt.event.ActionEvent; 19 21 import java.util.TreeSet; 20 22 … … 26 28 import javax.swing.JTextField; 27 29 28 30 import org.openstreetmap.josm.Main; 31 import org.openstreetmap.josm.data.osm.Node; 29 32 import org.openstreetmap.josm.data.osm.OsmPrimitive; 30 33 import org.openstreetmap.josm.data.osm.Way; 31 34 import org.openstreetmap.josm.gui.ExtendedDialog; … … 54 57 final static String INTERPOLATION = "plugins.terracer.interpolation"; 55 58 56 59 final private Way street; 60 final private String streetName; 57 61 final private boolean relationExists; 62 final ArrayList<Node> housenumbers; 58 63 59 64 protected static final String DEFAULT_MESSAGE = tr("Enter housenumbers or amount of segments"); 60 65 private static final long serialVersionUID = 1L; … … 64 69 JTextField lo; 65 70 private JLabel hiLabel; 66 71 JTextField hi; 72 private JLabel numbersLabel; 73 JTextField numbers; 67 74 private JLabel streetLabel; 68 75 AutoCompletingComboBox streetComboBox; 69 76 private JLabel segmentsLabel; … … 79 86 /** 80 87 * @param street If street is not null, we assume, the name of the street to be fixed 81 88 * and just show a label. If street is null, we show a ComboBox/InputField. 89 * @param streetName the name of the street, derived from either the 90 * street line or the house numbers which are guaranteed to have the 91 * same name attached (may be null) 82 92 * @param relationExists If the buildings can be added to an existing relation or not. 93 * @param housenumbers a list of house numbers in this outline (may be empty) 83 94 */ 84 public HouseNumberInputDialog(HouseNumberInputHandler handler, Way street, boolean relationExists) {95 public HouseNumberInputDialog(HouseNumberInputHandler handler, Way street, String streetName, boolean relationExists, ArrayList<Node> housenumbers) { 85 96 super(Main.parent, 86 97 tr("Terrace a house"), 87 98 new String[] { tr("OK"), tr("Cancel")}, … … 89 100 ); 90 101 this.inputHandler = handler; 91 102 this.street = street; 103 this.streetName = streetName; 92 104 this.relationExists = relationExists; 105 this.housenumbers = housenumbers; 93 106 handler.dialog = this; 94 107 JPanel content = getInputPanel(); 95 108 setContent(content); … … 159 172 loLabel.setToolTipText(tr("Lowest housenumber of the terraced house")); 160 173 hiLabel = new JLabel(); 161 174 hiLabel.setText(tr("Highest Number")); 175 numbersLabel = new JLabel(); 176 numbersLabel.setText(tr("List of Numbers")); 177 loLabel.setPreferredSize(new Dimension(111, 16)); 162 178 final String txt = relationExists ? tr("add to existing associatedStreet relation") : tr("create an associatedStreet relation"); 163 179 164 180 handleRelationCheckBox = new JCheckBox(txt, Main.pref.getBoolean(HANDLE_RELATION, true)); … … 174 190 inputPanel.add(getLo(), GBC.eol().fill(GBC.HORIZONTAL).insets(5,3,0,0)); 175 191 inputPanel.add(hiLabel, GBC.std().insets(3,3,0,0)); 176 192 inputPanel.add(getHi(), GBC.eol().fill(GBC.HORIZONTAL).insets(5,3,0,0)); 193 inputPanel.add(numbersLabel, GBC.std().insets(3,3,0,0)); 194 inputPanel.add(getNumbers(), GBC.eol().fill(GBC.HORIZONTAL).insets(5,3,0,0)); 177 195 inputPanel.add(interpolationLabel, GBC.std().insets(3,3,0,0)); 178 196 inputPanel.add(getInterpolation(), GBC.eol().insets(5,3,0,0)); 179 197 inputPanel.add(segmentsLabel, GBC.std().insets(3,3,0,0)); 180 198 inputPanel.add(getSegments(), GBC.eol().fill(GBC.HORIZONTAL).insets(5,3,0,0)); 181 if (street == null) {199 if (streetName == null) { 182 200 inputPanel.add(streetLabel, GBC.std().insets(3,3,0,0)); 183 201 inputPanel.add(getStreet(), GBC.eol().insets(5,3,0,0)); 184 202 } else { 185 inputPanel.add(new JLabel(tr("Street name: ")+"\""+street .get("name")+"\""), GBC.eol().insets(3,3,0,0));203 inputPanel.add(new JLabel(tr("Street name: ")+"\""+streetName+"\""), GBC.eol().insets(3,3,0,0)); 186 204 } 187 205 inputPanel.add(handleRelationCheckBox, GBC.eol().insets(3,3,0,0)); 188 206 inputPanel.add(deleteOutlineCheckBox, GBC.eol().insets(3,3,0,0)); 207 208 if (numbers.isVisible()) 209 { 210 loLabel.setVisible(false); 211 lo.setVisible(false); 212 lo.setEnabled(false); 213 hiLabel.setVisible(false); 214 hi.setVisible(false); 215 hi.setEnabled(false); 216 interpolationLabel.setVisible(false); 217 interpolation.setVisible(false); 218 interpolation.setEnabled(false); 219 segments.setText(String.valueOf(housenumbers.size())); 220 segments.setEditable(false); 221 } 189 222 } 190 223 return inputPanel; 191 224 } … … 223 256 } 224 257 return hi; 225 258 } 259 260 /** 261 * This method initializes numbers 262 * 263 * @return javax.swing.JTextField 264 */ 265 private JTextField getNumbers() { 266 if (numbers == null) { 267 numbers = new JTextField(); 268 269 Iterator<Node> it = housenumbers.iterator(); 270 StringBuilder s = new StringBuilder(256); 271 if (it.hasNext()) { 272 s.append(it.next().get("addr:housenumber")); 273 while (it.hasNext()) 274 s.append(';').append(it.next().get("addr:housenumber")); 275 } 276 else { 277 numbersLabel.setVisible(false); 278 numbers.setVisible(false); 279 } 280 281 numbers.setText(s.toString()); 282 numbers.setEditable(false); 283 } 284 return numbers; 285 } 226 286 227 287 /** 228 288 * This method initializes street -
src/terracer/TerracerAction.java
15 15 import java.util.ArrayList; 16 16 import java.util.Collection; 17 17 import java.util.Collections; 18 import java.util.Comparator; 18 19 import java.util.Iterator; 19 20 import java.util.LinkedList; 20 21 import java.util.List; 22 import java.util.Map; 23 import java.util.Set; 24 import java.util.Map.Entry; 25 import java.util.regex.Matcher; 26 import java.util.regex.Pattern; 21 27 22 28 import javax.swing.JOptionPane; 23 29 … … 30 36 import org.openstreetmap.josm.command.DeleteCommand; 31 37 import org.openstreetmap.josm.command.SequenceCommand; 32 38 import org.openstreetmap.josm.data.coor.LatLon; 39 import org.openstreetmap.josm.data.osm.DataSet; 33 40 import org.openstreetmap.josm.data.osm.Node; 34 41 import org.openstreetmap.josm.data.osm.OsmPrimitive; 35 42 import org.openstreetmap.josm.data.osm.Relation; … … 40 47 import org.openstreetmap.josm.tools.Shortcut; 41 48 42 49 /** 43 * Terraces a quadrilateral, closed way into a series of quadrilateral, 44 * closed ways. If two ways are selected and one of them can be identified as45 * a street (highway=*, name=*) then the given street will be added46 * to the'associatedStreet' relation.47 * 48 * 49 * At present it only works on quadrilaterals, but there is no reason 50 * why it couldn't be extended to work with other shapes too. The51 * algorithm employed isnaive, but it works in the simple case.52 * 50 * Terraces a quadrilateral, closed way into a series of quadrilateral, closed 51 * ways. If two ways are selected and one of them can be identified as a street 52 * (highway=*, name=*) then the given street will be added to the 53 * 'associatedStreet' relation. 54 * 55 * 56 * At present it only works on quadrilaterals, but there is no reason why it 57 * couldn't be extended to work with other shapes too. The algorithm employed is 58 * naive, but it works in the simple case. 59 * 53 60 * @author zere 54 61 */ 55 62 public final class TerracerAction extends JosmAction { … … 77 84 .getSelected(); 78 85 Way outline = null; 79 86 Way street = null; 87 String streetname = null; 88 ArrayList<Node> housenumbers = new ArrayList<Node>(); 80 89 81 90 class InvalidUserInputException extends Exception { 82 91 InvalidUserInputException(String message) { 83 92 super(message); 84 93 } 94 85 95 InvalidUserInputException() { 86 96 super(); 87 97 } 88 98 } 89 99 90 100 try { 91 if (sel.size() == 2) { 92 Iterator<OsmPrimitive> it = sel.iterator(); 93 OsmPrimitive prim1 = it.next(); 94 OsmPrimitive prim2 = it.next(); 95 if (!(prim1 instanceof Way && prim2 instanceof Way)) 96 throw new InvalidUserInputException(); 97 Way way1 = (Way) prim1; 98 Way way2 = (Way) prim2; 99 if (way2.get("highway") != null) { 100 street = way2; 101 outline = way1; 102 } else if (way1.get("highway") != null) { 103 street = way1; 104 outline = way2; 105 } else 106 throw new InvalidUserInputException(); 107 if (street.get("name") == null) 108 throw new InvalidUserInputException(); 109 110 } else if (sel.size() == 1) { 101 if (sel.size() == 1) { 111 102 OsmPrimitive prim = sel.iterator().next(); 112 103 113 104 if (!(prim instanceof Way)) 114 105 throw new InvalidUserInputException(); 115 106 116 outline = (Way)prim; 107 outline = (Way) prim; 108 } else if (sel.size() > 1) { 109 List<Way> ways = OsmPrimitive.getFilteredList(sel, Way.class); 110 Iterator<Way> wit = ways.iterator(); 111 while (wit.hasNext()) { 112 Way way = wit.next(); 113 String type = way.get("building"); 114 if (type != null) { 115 if (outline == null) 116 outline = way; 117 else 118 // already have a building 119 throw new InvalidUserInputException(); 120 } else if ((type = way.get("highway")) != null) { 121 if (street == null) 122 street = way; 123 else 124 // already have a street 125 throw new InvalidUserInputException(); 126 127 if ((streetname = street.get("name")) == null) 128 throw new InvalidUserInputException(); 129 } else 130 throw new InvalidUserInputException(); 131 } 132 133 if (outline == null) 134 throw new InvalidUserInputException(); 135 136 List<Node> nodes = OsmPrimitive 137 .getFilteredList(sel, Node.class); 138 Iterator<Node> nit = nodes.iterator(); 139 // Actually this should test if the selected address nodes lies 140 // within 141 // within the selected outline. Any ideas how to do this? 142 while (nit.hasNext()) { 143 Node node = nit.next(); 144 if (node.get("addr:housenumber") != null) { 145 String nodesstreetname = node.get("addr:street"); 146 // if a node has a street name if must be equal 147 // to the one of the other address nodes 148 if (nodesstreetname != null) { 149 if (streetname == null) 150 streetname = nodesstreetname; 151 else if (!nodesstreetname.equals(streetname)) 152 throw new InvalidUserInputException(); 153 } 154 // if a node has a street name it must be 155 // equal to the one of the street line 156 if (nodesstreetname != null && street != null 157 && !nodesstreetname.equals(street.get("name"))) 158 throw new InvalidUserInputException(); 159 160 housenumbers.add(node); 161 } else { 162 // A given node might not be an address node but then 163 // it should be part of the building or the street line. 164 if (!outline.containsNode(node) 165 && !(street != null && street 166 .containsNode(node))) 167 throw new InvalidUserInputException(); 168 } 169 } 170 171 Collections.sort(housenumbers, new HousenumberNodeComparator()); 117 172 } else 118 173 throw new InvalidUserInputException(); 119 174 … … 128 183 return; 129 184 } 130 185 131 // If we have a street, try to find a associatedStreet relation that could be reused. 186 // If we have a street, try to find an associatedStreet relation that 187 // could be reused. 132 188 Relation associatedStreet = null; 133 189 if (street != null) { 134 outer:for (OsmPrimitive osm : Main.main.getCurrentDataSet().allNonDeletedPrimitives()) { 135 if (!(osm instanceof Relation)) continue; 190 outer: for (OsmPrimitive osm : Main.main.getCurrentDataSet() 191 .allNonDeletedPrimitives()) { 192 if (!(osm instanceof Relation)) 193 continue; 136 194 Relation rel = (Relation) osm; 137 if ("associatedStreet".equals(rel.get("type")) && street.get("name").equals(rel.get("name"))) { 195 if ("associatedStreet".equals(rel.get("type")) 196 && street.get("name").equals(rel.get("name"))) { 138 197 List<RelationMember> members = rel.getMembers(); 139 198 for (RelationMember m : members) { 140 if ("street".equals(m.getRole()) && m.isWay() && m.getMember().equals(street)) { 199 if ("street".equals(m.getRole()) && m.isWay() 200 && m.getMember().equals(street)) { 141 201 associatedStreet = rel; 142 202 break outer; 143 203 } … … 146 206 } 147 207 } 148 208 149 String title = trn("Change {0} object", "Change {0} objects", sel.size(), sel.size()); 150 // show input dialog. 151 new HouseNumberInputHandler(this, outline, street, associatedStreet, title); 209 if (housenumbers.size() == 1) { 210 // Special case of one outline and one address node. 211 // Don't open the dialogue, just copy the node keys 212 // to the outline, set building just in case it isn't there 213 // and remove the node. 214 Collection<Command> commands = new LinkedList<Command>(); 215 Way newOutline = new Way(outline); 216 for (Entry<String, String> entry : housenumbers.get(0).getKeys() 217 .entrySet()) { 218 newOutline.put(entry.getKey(), entry.getValue()); 219 } 220 newOutline.put("building", "yes"); 221 commands.add(new ChangeCommand(outline, newOutline)); 222 commands.add(DeleteCommand.delete(Main.main.getEditLayer(), 223 housenumbers, true, true)); 224 Main.main.undoRedo 225 .add(new SequenceCommand(tr("Terrace"), commands)); 226 Main.main.getCurrentDataSet().setSelected(newOutline); 227 } else { 228 String title = trn("Change {0} object", "Change {0} objects", sel 229 .size(), sel.size()); 230 // show input dialog. 231 new HouseNumberInputHandler(this, outline, street, streetname, 232 associatedStreet, housenumbers, title); 233 } 152 234 } 153 235 154 236 public Integer getNumber(String number) { … … 160 242 } 161 243 162 244 /** 245 * Sorts the house number nodes according their numbers only 246 * 247 * @param house 248 * number nodes 249 */ 250 class HousenumberNodeComparator implements Comparator<Node> { 251 private final Pattern pat = Pattern.compile("^([0-9]+)"); 252 253 /* 254 * (non-Javadoc) 255 * 256 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) 257 */ 258 @Override 259 public int compare(Node node1, Node node2) { 260 // It's necessary to strip off trailing non-numbers so we can 261 // compare the numbers itself numerically since string comparison 262 // doesn't work for numbers with different number of digits, 263 // e.g. 9 is higher than 11 264 String node1String = node1.get("addr:housenumber"); 265 String node2String = node2.get("addr:housenumber"); 266 Matcher mat = pat.matcher(node1String); 267 if (mat.find()) { 268 Integer node1Int = Integer.valueOf(mat.group(1)); 269 mat = pat.matcher(node2String); 270 if (mat.find()) { 271 Integer node2Int = Integer.valueOf(mat.group(1)); 272 273 return node1Int.compareTo(node2Int); 274 } 275 } 276 277 return node1String.compareTo(node2String); 278 } 279 } 280 281 /** 163 282 * Terraces a single, closed, quadrilateral way. 164 * 283 * 165 284 * Any node must be adjacent to both a short and long edge, we naively 166 285 * choose the longest edge and its opposite and interpolate along them 167 286 * linearly to produce new nodes. Those nodes are then assembled into 168 287 * closed, quadrilateral ways and left in the selection. 169 * 170 * @param outline The closed, quadrilateral way to terrace. 171 * @param street The street, the buildings belong to (may be null) 288 * 289 * @param outline 290 * The closed, quadrilateral way to terrace. 291 * @param street 292 * The street, the buildings belong to (may be null) 172 293 * @param associatedStreet 173 294 * @param From 174 295 * @param To 175 * @param streetName the name of a street (may be null). Used if not null and street is null. 176 * @param handleRelations If the user likes to add a relation or extend an existing relation 177 * @param deleteOutline If the outline way should be deleted, when done 296 * @param streetName 297 * the name of a street (may be null). Used if not null and 298 * street is null. 299 * @param handleRelations 300 * If the user likes to add a relation or extend an existing 301 * relation 302 * @param streetName 303 * the name of the street, derived from the street line or the 304 * house numbers (may be null) 305 * @param housenumbers 306 * @param deleteOutline 307 * If the outline way should be deleted, when done 178 308 */ 179 public void terraceBuilding(Way outline, 180 Way street, 181 Relation associatedStreet, 182 Integer segments, 183 String From, 184 String To, 185 int step, 186 String streetName, 187 boolean handleRelations, 188 boolean deleteOutline) { 309 public void terraceBuilding(Way outline, Way street, 310 Relation associatedStreet, Integer segments, String From, 311 String To, int step, ArrayList<Node> housenumbers, 312 String streetName, boolean handleRelations, boolean deleteOutline) { 189 313 final int nb; 190 191 Integer to, from; 192 to = getNumber(To); 193 from = getNumber(From); 194 if (to != null && from != null) { 195 nb = 1 + (to.intValue() - from.intValue()) / step; 196 } else if (segments != null) { 197 nb = segments.intValue(); 314 Integer to = null, from = null; 315 if (housenumbers.isEmpty()) { 316 to = getNumber(To); 317 from = getNumber(From); 318 if (to != null && from != null) { 319 nb = 1 + (to.intValue() - from.intValue()) / step; 320 } else if (segments != null) { 321 nb = segments.intValue(); 322 } else { 323 // if we get here, there is is a bug in the input validation. 324 throw new TerracerRuntimeException( 325 "Could not determine segments from parameters, this is a bug. " 326 + "Parameters were: segments " + segments 327 + " from " + from + " to " + to + " step " 328 + step); 329 } 198 330 } else { 199 // if we get here, there is is a bug in the input validation. 200 throw new TerracerRuntimeException( 201 "Could not determine segments from parameters, this is a bug. " 202 + "Parameters were: segments " + segments 203 + " from " + from + " to " + to + " step " + step); 331 nb = housenumbers.size(); 204 332 } 205 333 334 // now find which is the longest side connecting the first node 335 Pair<Way, Way> interp = findFrontAndBack(outline); 336 337 final double frontLength = wayLength(interp.a); 338 final double backLength = wayLength(interp.b); 339 206 340 // new nodes array to hold all intermediate nodes 207 341 Node[][] new_nodes = new Node[2][nb + 1]; 208 342 209 343 this.commands = new LinkedList<Command>(); 210 344 Collection<Way> ways = new LinkedList<Way>(); 211 345 212 // Should this building be terraced (i.e. is there more then one section?)213 346 if (nb > 1) { 214 347 // create intermediate nodes by interpolating. 215 216 // now find which is the longest side connecting the first node217 Pair<Way, Way> interp = findFrontAndBack(outline);218 219 final double frontLength = wayLength(interp.a);220 final double backLength = wayLength(interp.b);221 222 348 for (int i = 0; i <= nb; ++i) { 223 new_nodes[0][i] = interpolateAlong(interp.a, frontLength * i / nb); 224 new_nodes[1][i] = interpolateAlong(interp.b, backLength * i / nb); 349 new_nodes[0][i] = interpolateAlong(interp.a, frontLength * i 350 / nb); 351 new_nodes[1][i] = interpolateAlong(interp.b, backLength * i 352 / nb); 225 353 this.commands.add(new AddCommand(new_nodes[0][i])); 226 354 this.commands.add(new AddCommand(new_nodes[1][i])); 227 355 } … … 229 357 // assemble new quadrilateral, closed ways 230 358 for (int i = 0; i < nb; ++i) { 231 359 Way terr = new Way(); 232 // Using Way.nodes.add rather than Way.addNode because the latter233 // doesn't234 // exist in older versions of JOSM.235 360 terr.addNode(new_nodes[0][i]); 236 361 terr.addNode(new_nodes[0][i + 1]); 237 362 terr.addNode(new_nodes[1][i + 1]); … … 242 367 TagCollection.from(outline).applyTo(terr); 243 368 244 369 String number = null; 245 if (from != null) { 246 number = Integer.toString(from + i * step); 370 Set<Entry<String, String>> additionalKeys = null; 371 if (housenumbers.isEmpty()) { 372 if (from != null) { 373 // only, if the user has specified house numbers 374 number = Integer.toString(from + i * step); 375 } 376 } else { 377 number = housenumbers.get(i).get("addr:housenumber"); 378 additionalKeys = housenumbers.get(i).getKeys().entrySet(); 247 379 } 248 terr = addressBuilding(terr, street, streetName, number);249 380 381 terr = addressBuilding(terr, street, streetName, number, 382 additionalKeys); 383 250 384 ways.add(terr); 251 385 this.commands.add(new AddCommand(terr)); 252 386 } 253 387 254 388 if (deleteOutline) { 255 this.commands.add(DeleteCommand.delete(Main.main.getEditLayer(), Collections.singleton(outline), true, true)); 389 this.commands.add(DeleteCommand.delete( 390 Main.main.getEditLayer(), Collections 391 .singleton(outline), true, true)); 256 392 } 257 393 } else { 258 // Single section, just add the address details394 // Single building, just add the address details 259 395 Way newOutline; 260 newOutline = addressBuilding(outline, street, streetName, From); 396 newOutline = addressBuilding(outline, street, streetName, From, 397 null); 261 398 ways.add(newOutline); 262 399 this.commands.add(new ChangeCommand(outline, newOutline)); 263 400 } 264 401 265 402 if (handleRelations) { // create a new relation or merge with existing 266 if (associatedStreet == null) { 403 if (associatedStreet == null) { // create a new relation 267 404 associatedStreet = new Relation(); 268 405 associatedStreet.put("type", "associatedStreet"); 269 406 if (street != null) { // a street was part of the selection 270 407 associatedStreet.put("name", street.get("name")); 271 associatedStreet.addMember(new RelationMember("street", street)); 408 associatedStreet.addMember(new RelationMember("street", 409 street)); 272 410 } else { 273 411 associatedStreet.put("name", streetName); 274 412 } … … 276 414 associatedStreet.addMember(new RelationMember("house", w)); 277 415 } 278 416 this.commands.add(new AddCommand(associatedStreet)); 279 } 280 else { // relation exists already - add new members 417 } else { // relation exists already - add new members 281 418 Relation newAssociatedStreet = new Relation(associatedStreet); 282 419 for (Way w : ways) { 283 newAssociatedStreet.addMember(new RelationMember("house", w)); 420 newAssociatedStreet 421 .addMember(new RelationMember("house", w)); 284 422 } 285 this.commands.add(new ChangeCommand(associatedStreet, newAssociatedStreet)); 423 this.commands.add(new ChangeCommand(associatedStreet, 424 newAssociatedStreet)); 286 425 } 287 426 } 427 428 // Remove the address node since their tags have been incorporated into 429 // the terraces. 430 // Or should removing them also be an option? 431 if (!housenumbers.isEmpty()) 432 commands.add(DeleteCommand.delete(Main.main.getEditLayer(), 433 housenumbers, true, true)); 434 288 435 Main.main.undoRedo.add(new SequenceCommand(tr("Terrace"), commands)); 289 436 if (nb > 1) { 290 437 // Select the new building outlines (for quick reversing) 291 438 Main.main.getCurrentDataSet().setSelected(ways); 292 439 } else if (street != null) { 293 // Select the way (for quick selection of a new house (with the same way)) 440 // Select the way (for quick selection of a new house (with the same 441 // way)) 294 442 Main.main.getCurrentDataSet().setSelected(street); 295 443 } 296 444 } 297 445 298 446 /** 299 447 * Adds address details to a single building 300 * 301 * @param outline The closed, quadrilateral way to add the address to. 302 * @param street The street, the buildings belong to (may be null) 303 * @param streetName the name of a street (may be null). Used if not null and street is null. 304 * @param number The house number 448 * 449 * @param outline 450 * The closed, quadrilateral way to add the address to. 451 * @param street 452 * The street, the buildings belong to (may be null) 453 * @param streetName 454 * the name of a street (may be null). Used if not null and 455 * street is null. 456 * @param number 457 * The house number 458 * @param additionalKeys 459 * More keys to be copied onto the new outline 305 460 * @return the way with added address details 306 461 */ 307 private Way addressBuilding(Way outline, Way street, String streetName, String number) { 462 private Way addressBuilding(Way outline, Way street, String streetName, 463 String number, Set<Entry<String, String>> additionalKeys) { 308 464 Way changedOutline = outline; 309 465 if (number != null) { 310 466 // only, if the user has specified house numbers 311 this.commands.add(new ChangePropertyCommand(changedOutline, "addr:housenumber", number)); 467 this.commands.add(new ChangePropertyCommand(changedOutline, 468 "addr:housenumber", number)); 312 469 } 470 if (additionalKeys != null) { 471 for (Entry<String, String> entry : additionalKeys) { 472 this.commands.add(new ChangePropertyCommand(changedOutline, 473 entry.getKey(), entry.getValue())); 474 } 475 } 313 476 changedOutline.put("building", "yes"); 314 477 if (street != null) { 315 this.commands.add(new ChangePropertyCommand(changedOutline, "addr:street", street.get("name"))); 478 this.commands.add(new ChangePropertyCommand(changedOutline, 479 "addr:street", street.get("name"))); 316 480 } else if (streetName != null) { 317 this.commands.add(new ChangePropertyCommand(changedOutline, "addr:street", streetName)); 481 this.commands.add(new ChangePropertyCommand(changedOutline, 482 "addr:street", streetName)); 318 483 } 319 484 return changedOutline; 320 485 } … … 322 487 /** 323 488 * Creates a node at a certain distance along a way, as calculated by the 324 489 * great circle distance. 325 * 490 * 326 491 * Note that this really isn't an efficient way to do this and leads to 327 * O(N^2) running time for the main algorithm, but its simple and easy 328 * to understand, and probably won't matter for reasonable-sized ways. 329 * 330 * @param w The way to interpolate. 331 * @param l The length at which to place the node. 492 * O(N^2) running time for the main algorithm, but its simple and easy to 493 * understand, and probably won't matter for reasonable-sized ways. 494 * 495 * @param w 496 * The way to interpolate. 497 * @param l 498 * The length at which to place the node. 332 499 * @return A node at a distance l along w from the first point. 333 500 */ 334 501 private Node interpolateAlong(Way w, double l) { 335 List<Pair<Node, Node>> pairs = w.getNodePairs(false);502 List<Pair<Node, Node>> pairs = w.getNodePairs(false); 336 503 for (int i = 0; i < pairs.size(); ++i) { 337 Pair<Node,Node> p = pairs.get(i); 338 final double seg_length = p.a.getCoor().greatCircleDistance(p.b.getCoor()); 339 if (l <= seg_length || 340 i == pairs.size() - 1) { // be generous on the last segment (numerical roudoff can lead to a small overshoot) 504 Pair<Node, Node> p = pairs.get(i); 505 final double seg_length = p.a.getCoor().greatCircleDistance( 506 p.b.getCoor()); 507 if (l <= seg_length || i == pairs.size() - 1) { // be generous on 508 // the last segment 509 // (numerical 510 // roudoff can lead 511 // to a small 512 // overshoot) 341 513 return interpolateNode(p.a, p.b, l / seg_length); 342 514 } else { 343 515 l -= seg_length; … … 350 522 /** 351 523 * Calculates the great circle length of a way by summing the great circle 352 524 * distance of each pair of nodes. 353 * 354 * @param w The way to calculate length of. 525 * 526 * @param w 527 * The way to calculate length of. 355 528 * @return The length of the way. 356 529 */ 357 530 private double wayLength(Way w) { … … 366 539 * Given a way, try and find a definite front and back by looking at the 367 540 * segments to find the "sides". Sides are assumed to be single segments 368 541 * which cannot be contiguous. 369 * 370 * @param w The way to analyse. 542 * 543 * @param w 544 * The way to analyse. 371 545 * @return A pair of ways (front, back) pointing in the same directions. 372 546 */ 373 547 private Pair<Way, Way> findFrontAndBack(Way w) { … … 428 602 * returns the distance of two segments of a closed polygon 429 603 */ 430 604 private int indexDistance(int i1, int i2, int n) { 431 return Math.min(positiveModulus(i1 - i2, n), positiveModulus(i2 - i1, n)); 605 return Math.min(positiveModulus(i1 - i2, n), 606 positiveModulus(i2 - i1, n)); 432 607 } 433 608 434 609 /** 435 610 * return the modulus in the range [0, n) 436 611 */ 437 612 private int positiveModulus(int a, int n) { 438 if (n <= 0)613 if (n <= 0) 439 614 throw new IllegalArgumentException(); 440 615 int res = a % n; 441 616 if (res < 0) { … … 445 620 } 446 621 447 622 /** 448 * Calculate the length of a side (from node i to i+1) in a way. This assumes that449 * the way is closed, but I only ever call it for buildings.623 * Calculate the length of a side (from node i to i+1) in a way. This 624 * assumes that the way is closed, but I only ever call it for buildings. 450 625 */ 451 626 private double sideLength(Way w, int i) { 452 627 Node a = w.getNode(i); … … 456 631 457 632 /** 458 633 * Given an array of doubles (but this could made generic very easily) sort 459 * into order and return the array of indexes such that, for a returned array 460 * x, a[x[i]] is sorted for ascending index i. 461 * 462 * This isn't efficient at all, but should be fine for the small arrays we're 463 * expecting. If this gets slow - replace it with some more efficient algorithm. 464 * 465 * @param a The array to sort. 466 * @return An array of indexes, the same size as the input, such that a[x[i]] 467 * is in sorted order. 634 * into order and return the array of indexes such that, for a returned 635 * array x, a[x[i]] is sorted for ascending index i. 636 * 637 * This isn't efficient at all, but should be fine for the small arrays 638 * we're expecting. If this gets slow - replace it with some more efficient 639 * algorithm. 640 * 641 * @param a 642 * The array to sort. 643 * @return An array of indexes, the same size as the input, such that 644 * a[x[i]] is in sorted order. 468 645 */ 469 646 private int[] sortedIndexes(final double[] a) { 470 647 class SortWithIndex implements Comparable<SortWithIndex> { … … 517 694 518 695 /** 519 696 * Calculate sideness of a single segment given the nodes which make up that 520 * segment and its previous and next segments in order. Sideness is calculated521 * for the segment b-c.697 * segment and its previous and next segments in order. Sideness is 698 * calculated for the segment b-c. 522 699 */ 523 700 private double calculateSideness(Node a, Node b, Node c, Node d) { 524 701 final double ndx = b.getCoor().getX() - a.getCoor().getX(); … … 533 710 /** 534 711 * Creates a new node at the interpolated position between the argument 535 712 * nodes. Interpolates linearly in projected coordinates. 536 * 537 * @param a First node, at which f=0. 538 * @param b Last node, at which f=1. 539 * @param f Fractional position between first and last nodes. 713 * 714 * @param a 715 * First node, at which f=0. 716 * @param b 717 * Last node, at which f=1. 718 * @param f 719 * Fractional position between first and last nodes. 540 720 * @return A new node at the interpolated position. 541 721 */ 542 722 private Node interpolateNode(Node a, Node b, double f) { -
src/terracer/HouseNumberInputHandler.java
18 18 import java.awt.event.FocusListener; 19 19 import java.awt.event.ItemEvent; 20 20 import java.awt.event.ItemListener; 21 import java.util.ArrayList; 21 22 22 23 import javax.swing.JButton; 23 24 import javax.swing.JTextField; 24 25 import javax.swing.JOptionPane; 25 26 26 27 import org.openstreetmap.josm.Main; 28 import org.openstreetmap.josm.data.osm.Node; 27 29 import org.openstreetmap.josm.data.osm.Way; 28 30 import org.openstreetmap.josm.data.osm.Relation; 29 31 import org.openstreetmap.josm.actions.JosmAction; 30 32 31 33 /** 32 * The Class HouseNumberInputHandler contains all the logic 33 * behind the housenumber input dialog.34 * 35 * From a refactoring viewpoint, this class is indeed more interested in the fields36 * of the HouseNumberInputDialog. This is desired design, as the HouseNumberInputDialog37 * is already cluttered with auto-generated layout code.38 * 34 * The Class HouseNumberInputHandler contains all the logic behind the house 35 * number input dialog. 36 * 37 * From a refactoring viewpoint, this class is indeed more interested in the 38 * fields of the HouseNumberInputDialog. This is desired design, as the 39 * HouseNumberInputDialog is already cluttered with auto-generated layout code. 40 * 39 41 * @author casualwalker 40 42 */ 41 public class HouseNumberInputHandler extends JosmAction implements ActionListener, FocusListener, ItemListener { 43 public class HouseNumberInputHandler extends JosmAction implements 44 ActionListener, FocusListener, ItemListener { 42 45 private TerracerAction terracerAction; 43 private Way outline, street; 46 private Way outline; 47 private Way street; 48 private String streetName; 44 49 private Relation associatedStreet; 50 private ArrayList<Node> housenumbers; 45 51 public HouseNumberInputDialog dialog; 46 52 47 53 /** 48 54 * Instantiates a new house number input handler. 49 * 50 * @param terracerAction the terracer action 51 * @param outline the closed, quadrilateral way to terrace. 52 * @param street the street, the buildings belong to (may be null) 53 * @param associatedStreet a relation where we can add the houses (may be null) 54 * @param title the title 55 * 56 * @param terracerAction 57 * the terracer action 58 * @param outline 59 * the closed, quadrilateral way to terrace. 60 * @param street 61 * the street, the buildings belong to (may be null) 62 * @param streetName 63 * the name of the street, derived from either the street line or 64 * the house numbers which are guaranteed to have the same name 65 * attached (may be null) 66 * @param associatedStreet 67 * a relation where we can add the houses (may be null) 68 * @param housenumbers 69 * a list of house number nodes in this outline (may be empty) 70 * @param title 71 * the title 55 72 */ 56 73 public HouseNumberInputHandler(final TerracerAction terracerAction, 57 final Way outline, final Way street, final Relation associatedStreet, 58 final String title) { 74 final Way outline, final Way street, final String streetName, 75 final Relation associatedStreet, 76 final ArrayList<Node> housenumbers, final String title) { 59 77 this.terracerAction = terracerAction; 60 78 this.outline = outline; 61 79 this.street = street; 80 this.streetName = streetName; 62 81 this.associatedStreet = associatedStreet; 82 this.housenumbers = housenumbers; 63 83 64 84 // This dialog is started modal 65 this.dialog = new HouseNumberInputDialog(this, street, associatedStreet != null); 85 this.dialog = new HouseNumberInputDialog(this, street, streetName, 86 associatedStreet != null, housenumbers); 66 87 67 88 // We're done 68 89 } 69 90 70 91 /** 71 * Find a button with a certain caption. 72 * Loops recursively through all objects to find all buttons. 73 * Function returns on the first match. 74 * 75 * @param root A container object that is recursively searched for other containers or buttons 76 * @param caption The caption of the button that is being searched 77 * 92 * Find a button with a certain caption. Loops recursively through all 93 * objects to find all buttons. Function returns on the first match. 94 * 95 * @param root 96 * A container object that is recursively searched for other 97 * containers or buttons 98 * @param caption 99 * The caption of the button that is being searched 100 * 78 101 * @return The first button that matches the caption or null if not found 79 102 */ 80 103 private static JButton getButton(Container root, String caption) { 81 104 Component children[] = root.getComponents(); 82 105 for (Component child : children) { 83 106 JButton b; 84 107 if (child instanceof JButton) { 85 108 b = (JButton) child; 86 if (caption.equals(b.getText())) return b; 109 if (caption.equals(b.getText())) 110 return b; 87 111 } else if (child instanceof Container) { 88 b = getButton((Container)child, caption); 89 if (b != null) return b; 90 } 91 } 112 b = getButton((Container) child, caption); 113 if (b != null) 114 return b; 115 } 116 } 92 117 return null; 93 118 } 94 119 95 120 /** 96 * Validate the current input fields. 97 * When the validation fails, a red message is 98 * displayed and the OK button is disabled. 99 * 121 * Validate the current input fields. When the validation fails, a red 122 * message is displayed and the OK button is disabled. 123 * 100 124 * Should be triggered each time the input changes. 101 125 */ 102 126 private boolean validateInput() { … … 107 131 isOk = isOk && checkSegmentsFromHousenumber(message); 108 132 isOk = isOk && checkSegments(message); 109 133 110 // Allow non numeric characters for the low number as long as there is no high number of the segmentcount is 1 134 // Allow non numeric characters for the low number as long as there is 135 // no high number of the segmentcount is 1 111 136 if (dialog.hi.getText().length() > 0 | segments() > 1) { 112 137 isOk = isOk 113 138 && checkNumberStringField(dialog.lo, tr("Lowest number"), … … 127 152 128 153 // For some reason the messageLabel doesn't want to show up 129 154 dialog.messageLabel.setForeground(Color.black); 130 dialog.messageLabel.setText(tr(HouseNumberInputDialog.DEFAULT_MESSAGE)); 155 dialog.messageLabel 156 .setText(tr(HouseNumberInputDialog.DEFAULT_MESSAGE)); 131 157 return true; 132 158 } else { 133 159 JButton okButton = getButton(dialog, "OK"); 134 160 if (okButton != null) 135 161 okButton.setEnabled(false); 136 162 137 // For some reason the messageLabel doesn't want to show up, so a MessageDialog is shown instead. Someone more knowledgeable might fix this. 163 // For some reason the messageLabel doesn't want to show up, so a 164 // MessageDialog is shown instead. Someone more knowledgeable might 165 // fix this. 138 166 dialog.messageLabel.setForeground(Color.red); 139 167 dialog.messageLabel.setText(message.toString()); 140 //JOptionPane.showMessageDialog(null, message.toString(), tr("Error"), JOptionPane.ERROR_MESSAGE); 168 // JOptionPane.showMessageDialog(null, message.toString(), 169 // tr("Error"), JOptionPane.ERROR_MESSAGE); 141 170 142 171 return false; 143 172 } 144 173 } 145 174 146 175 /** 147 * Checks, if the lowest house number is indeed lower than the 148 * highest house number. 149 * This check applies only, if the house number fields are used at all. 150 * 151 * @param message the message 152 * 176 * Checks, if the lowest house number is indeed lower than the highest house 177 * number. This check applies only, if the house number fields are used at 178 * all. 179 * 180 * @param message 181 * the message 182 * 153 183 * @return true, if successful 154 184 */ 155 185 private boolean checkNumberOrder(final StringBuffer message) { 156 186 if (numberFrom() != null && numberTo() != null) { 157 187 if (numberFrom().intValue() > numberTo().intValue()) { 158 188 appendMessageNewLine(message); 159 message.append(tr("Lowest housenumber cannot be higher than highest housenumber")); 189 message 190 .append(tr("Lowest housenumber cannot be higher than highest housenumber")); 160 191 return false; 161 192 } 162 193 } … … 164 195 } 165 196 166 197 /** 167 * Obtain the number segments from the house number fields and check, 168 * if they are valid. 169 * 170 * Also disables the segments field, if the house numbers contain 171 * valid information. 172 * 173 * @param message the message 174 * 198 * Obtain the number segments from the house number fields and check, if 199 * they are valid. 200 * 201 * Also disables the segments field, if the house numbers contain valid 202 * information. 203 * 204 * @param message 205 * the message 206 * 175 207 * @return true, if successful 176 208 */ 177 209 private boolean checkSegmentsFromHousenumber(final StringBuffer message) { 178 dialog.segments.setEditable(true); 210 if (!dialog.numbers.isVisible()) { 211 dialog.segments.setEditable(true); 179 212 180 if (numberFrom() != null && numberTo() != null) {213 if (numberFrom() != null && numberTo() != null) { 181 214 182 int segments = numberTo().intValue() - numberFrom().intValue();215 int segments = numberTo().intValue() - numberFrom().intValue(); 183 216 184 if (segments % stepSize() != 0) { 185 appendMessageNewLine(message); 186 message.append(tr("Housenumbers do not match odd/even setting")); 187 return false; 188 } 217 if (segments % stepSize() != 0) { 218 appendMessageNewLine(message); 219 message 220 .append(tr("Housenumbers do not match odd/even setting")); 221 return false; 222 } 189 223 190 int steps = segments / stepSize();191 steps++; // difference 0 means 1 building, see192 // TerracerActon.terraceBuilding193 dialog.segments.setText(String.valueOf(steps));194 dialog.segments.setEditable(false);224 int steps = segments / stepSize(); 225 steps++; // difference 0 means 1 building, see 226 // TerracerActon.terraceBuilding 227 dialog.segments.setText(String.valueOf(steps)); 228 dialog.segments.setEditable(false); 195 229 230 } 196 231 } 197 232 return true; 198 233 } 199 234 200 235 /** 201 * Check the number of segments. 202 * It must be a number and greater than 1.203 * 204 * @param messagethe message205 * 236 * Check the number of segments. It must be a number and greater than 1. 237 * 238 * @param message 239 * the message 240 * 206 241 * @return true, if successful 207 242 */ 208 243 private boolean checkSegments(final StringBuffer message) { … … 217 252 218 253 /** 219 254 * Check, if a string field contains a positive integer. 220 * 221 * @param field the field 222 * @param label the label 223 * @param message the message 224 * 255 * 256 * @param field 257 * the field 258 * @param label 259 * the label 260 * @param message 261 * the message 262 * 225 263 * @return true, if successful 226 264 */ 227 265 private boolean checkNumberStringField(final JTextField field, … … 247 285 248 286 /** 249 287 * Append a new line to the message, if the message is not empty. 250 * 251 * @param message the message 288 * 289 * @param message 290 * the message 252 291 */ 253 292 private void appendMessageNewLine(final StringBuffer message) { 254 293 if (message.length() > 0) { … … 256 295 } 257 296 } 258 297 259 /* (non-Javadoc) 260 * @see java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent) 298 /* 299 * (non-Javadoc) 300 * 301 * @see 302 * java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent) 261 303 * Called when the user selects from a pulldown selection 262 304 */ 263 305 public void itemStateChanged(ItemEvent e) { 264 306 validateInput(); 265 307 } 266 308 267 /* (non-Javadoc) 268 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) 309 /* 310 * (non-Javadoc) 311 * 312 * @see 313 * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) 269 314 */ 270 315 public void actionPerformed(final ActionEvent e) { 271 316 // OK or Cancel button-actions … … 274 319 if ("OK".equals(button.getActionCommand()) & button.isEnabled()) { 275 320 if (validateInput()) { 276 321 saveValues(); 277 322 278 323 terracerAction.terraceBuilding( 279 324 outline, 280 325 street, 281 326 associatedStreet, 282 327 segments(), 283 328 dialog.lo.getText(), 284 329 dialog.hi.getText(), 285 330 stepSize(), 331 housenumbers, 286 332 streetName(), 287 333 doHandleRelation(), 288 334 doDeleteOutline()); 289 335 290 336 this.dialog.dispose(); 291 }337 } 292 338 } else if ("Cancel".equals(button.getActionCommand())) { 293 339 this.dialog.dispose(); 294 340 } … … 299 345 } 300 346 301 347 /** 302 * Calculate the step size between two house numbers, 303 * based on theinterpolation setting.304 * 348 * Calculate the step size between two house numbers, based on the 349 * interpolation setting. 350 * 305 351 * @return the stepSize (1 for all, 2 for odd /even) 306 352 */ 307 353 public Integer stepSize() { … … 311 357 312 358 /** 313 359 * Gets the number of segments, if set. 314 * 360 * 315 361 * @return the number of segments or null, if not set / invalid. 316 362 */ 317 363 public Integer segments() { … … 324 370 325 371 /** 326 372 * Gets the lowest house number. 327 * 373 * 328 374 * @return the number of lowest house number or null, if not set / invalid. 329 375 */ 330 376 public Integer numberFrom() { … … 337 383 338 384 /** 339 385 * Gets the highest house number. 340 * 386 * 341 387 * @return the number of highest house number or null, if not set / invalid. 342 388 */ 343 389 public Integer numberTo() { … … 350 396 351 397 /** 352 398 * Gets the street name. 353 * 354 * @return the 399 * 400 * @return the street name or null, if not set / invalid. 355 401 */ 356 402 public String streetName() { 357 if (street != null)358 return null;403 if (streetName != null) 404 return streetName; 359 405 360 406 Object selected = dialog.streetComboBox.getSelectedItem(); 361 407 if (selected == null) { … … 371 417 } 372 418 373 419 /** 374 * Whether the user likes to create a relation or add to 375 * an existing one. 420 * Whether the user likes to create a relation or add to an existing one. 376 421 */ 377 422 public boolean doHandleRelation() { 378 423 if (this.dialog == null) { 379 JOptionPane.showMessageDialog(null, "dialog", "alert", JOptionPane.ERROR_MESSAGE); 424 JOptionPane.showMessageDialog(null, "dialog", "alert", 425 JOptionPane.ERROR_MESSAGE); 380 426 } 381 427 if (this.dialog.handleRelationCheckBox == null) { 382 JOptionPane.showMessageDialog(null, "checkbox", "alert", JOptionPane.ERROR_MESSAGE); 428 JOptionPane.showMessageDialog(null, "checkbox", "alert", 429 JOptionPane.ERROR_MESSAGE); 383 430 return true; 384 } 431 } else { 385 432 return this.dialog.handleRelationCheckBox.isSelected(); 386 433 } 387 434 } 388 435 389 390 436 /** 391 437 * Whether the user likes to delete the outline way. 392 438 */ … … 394 440 return dialog.deleteOutlineCheckBox.isSelected(); 395 441 } 396 442 397 /* (non-Javadoc) 443 /* 444 * (non-Javadoc) 445 * 398 446 * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent) 399 447 */ 400 448 public void focusGained(FocusEvent e) { 401 449 // Empty, but placeholder is required 402 450 } 403 451 404 /* (non-Javadoc) 452 /* 453 * (non-Javadoc) 454 * 405 455 * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent) 406 456 */ 407 457 public void focusLost(FocusEvent e) { … … 415 465 * Saves settings. 416 466 */ 417 467 public void saveValues() { 418 Main.pref.put(HouseNumberInputDialog.HANDLE_RELATION, doHandleRelation()); 468 Main.pref.put(HouseNumberInputDialog.HANDLE_RELATION, 469 doHandleRelation()); 419 470 Main.pref.put(HouseNumberInputDialog.DELETE_OUTLINE, doDeleteOutline()); 420 Main.pref.put(HouseNumberInputDialog.INTERPOLATION, stepSize().toString()); 471 Main.pref.put(HouseNumberInputDialog.INTERPOLATION, stepSize() 472 .toString()); 421 473 } 422 474 }