Changeset 19658 in osm for applications/editors/josm/plugins/terracer/src
- Timestamp:
- 2010-01-28T11:42:39+01:00 (15 years ago)
- Location:
- applications/editors/josm/plugins/terracer/src/terracer
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
applications/editors/josm/plugins/terracer/src/terracer/HouseNumberInputDialog.java
r19085 r19658 12 12 import java.awt.Choice; 13 13 import java.awt.Color; 14 import java.awt.Container; 14 15 import java.awt.Dimension; 15 16 import java.awt.FlowLayout; 16 17 import java.awt.Frame; 18 import java.awt.GridBagLayout; 17 19 import java.awt.GridLayout; 18 20 import java.util.TreeSet; … … 20 22 import javax.swing.BoxLayout; 21 23 import javax.swing.JButton; 24 import javax.swing.JCheckBox; 22 25 import javax.swing.JDialog; 23 26 import javax.swing.JLabel; 27 import javax.swing.JOptionPane; 24 28 import javax.swing.JPanel; 25 29 import javax.swing.JTextArea; … … 28 32 import org.openstreetmap.josm.Main; 29 33 import org.openstreetmap.josm.data.osm.OsmPrimitive; 34 import org.openstreetmap.josm.data.osm.Way; 30 35 import org.openstreetmap.josm.gui.widgets.AutoCompleteComboBox; 36 import org.openstreetmap.josm.tools.GBC; 37 31 38 32 39 /** 33 40 * The HouseNumberInputDialog is the layout of the house number input logic. 34 41 * Created with the Eclipse Visual Editor. 35 * 42 * 36 43 * This dialog is concerned with the layout, all logic goes into the 37 44 * HouseNumberinputHandler class. 38 * 45 * 39 46 * @author casualwalker 40 47 * 41 48 */ 42 49 public class HouseNumberInputDialog extends JDialog { 43 44 protected static final String DEFAULT_MESSAGE = "Enter housenumbers or amount of segments"; 45 private static final long serialVersionUID = 1L; 46 private JPanel jContentPane = null; 47 private JPanel inputPanel = null; 48 private JPanel buttonPanel = null; 49 private JLabel loLabel = null; 50 JTextField lo = null; 51 private JLabel hiLabel = null; 52 JTextField hi = null; 53 private JLabel streetLabel = null; 54 AutoCompleteComboBox street; 55 // JTextField street = null; 56 private JLabel segmentsLabel = null; 57 JTextField segments = null; 58 JTextArea messageLabel = null; 59 JButton okButton = null; 60 JButton cancelButton = null; 61 private JLabel interpolationLabel = null; 62 Choice interpolation = null; 63 64 /** 65 * @param owner 66 */ 67 public HouseNumberInputDialog(Frame owner) { 68 super(owner); 69 initialize(); 70 } 71 72 /** 73 * This method initializes this 74 * 75 * @return void 76 */ 77 private void initialize() { 78 this.setSize(300, 200); 79 this.setTitle("Terrace a house"); 80 this.setContentPane(getJContentPane()); 81 } 82 83 /** 84 * This method initializes jContentPane 85 * 86 * @return javax.swing.JPanel 87 */ 88 private JPanel getJContentPane() { 89 if (jContentPane == null) { 90 messageLabel = new JTextArea(); 91 messageLabel.setText(DEFAULT_MESSAGE); 92 messageLabel.setAutoscrolls(true); 93 94 messageLabel.setLineWrap(true); 95 messageLabel.setRows(2); 96 messageLabel.setBackground(new Color(238, 238, 238)); 97 messageLabel.setEditable(false); 98 jContentPane = new JPanel(); 99 jContentPane.setLayout(new BoxLayout(getJContentPane(), 100 BoxLayout.Y_AXIS)); 101 jContentPane.add(getInputPanel(), null); 102 jContentPane.add(messageLabel, null); 103 jContentPane.add(getButtonPanel(), null); 104 105 } 106 return jContentPane; 107 } 108 109 /** 110 * This method initializes inputPanel 111 * 112 * @return javax.swing.JPanel 113 */ 114 private JPanel getInputPanel() { 115 if (inputPanel == null) { 116 interpolationLabel = new JLabel(); 117 interpolationLabel.setText("Interpolation"); 118 segmentsLabel = new JLabel(); 119 segmentsLabel.setText("Segments"); 120 streetLabel = new JLabel(); 121 streetLabel.setText("Street"); 122 hiLabel = new JLabel(); 123 hiLabel.setText("Highest Number"); 124 loLabel = new JLabel(); 125 loLabel.setText("Lowest Number"); 126 loLabel.setPreferredSize(new Dimension(111, 16)); 127 loLabel.setToolTipText("Lowest housenumber of the terraced house"); 128 GridLayout gridLayout = new GridLayout(); 129 gridLayout.setRows(5); 130 gridLayout.setColumns(2); 131 inputPanel = new JPanel(); 132 inputPanel.setLayout(gridLayout); 133 inputPanel.add(loLabel, null); 134 135 inputPanel.add(getLo(), null); 136 inputPanel.add(hiLabel, null); 137 inputPanel.add(getHi(), null); 138 inputPanel.add(interpolationLabel, null); 139 inputPanel.add(getInterpolation(), null); 140 inputPanel.add(segmentsLabel, null); 141 inputPanel.add(getSegments(), null); 142 inputPanel.add(streetLabel, null); 143 inputPanel.add(getStreet(), null); 144 } 145 return inputPanel; 146 } 147 148 /** 149 * This method initializes buttonPanel 150 * 151 * @return javax.swing.JPanel 152 */ 153 private JPanel getButtonPanel() { 154 if (buttonPanel == null) { 155 buttonPanel = new JPanel(); 156 buttonPanel.setLayout(new FlowLayout()); 157 buttonPanel.add(getOkButton(), null); 158 buttonPanel.add(getCancelButton(), null); 159 } 160 return buttonPanel; 161 } 162 163 /** 164 * This method initializes lo 165 * 166 * @return javax.swing.JTextField 167 */ 168 private JTextField getLo() { 169 if (lo == null) { 170 lo = new JTextField(); 171 lo.setText(""); 172 } 173 return lo; 174 } 175 176 /** 177 * This method initializes hi 178 * 179 * @return javax.swing.JTextField 180 */ 181 private JTextField getHi() { 182 if (hi == null) { 183 hi = new JTextField(); 184 hi.setText(""); 185 } 186 return hi; 187 } 188 189 /** 190 * This method initializes street 191 * 192 * @return javax.swing.JTextField 193 */ 194 private AutoCompleteComboBox getStreet() { 195 196 if (street == null) { 197 final TreeSet<String> names = createAutoCompletionInfo(); 198 199 street = new AutoCompleteComboBox(); 200 street.setPossibleItems(names); 201 street.setEditable(true); 202 street.setSelectedItem(null); 203 204 } 205 return street; 206 } 207 208 /** 209 * This method initializes segments 210 * 211 * @return javax.swing.JTextField 212 */ 213 private JTextField getSegments() { 214 if (segments == null) { 215 segments = new JTextField(); 216 segments.setText("1"); 217 } 218 return segments; 219 } 220 221 /** 222 * This method initializes okButton 223 * 224 * @return javax.swing.JButton 225 */ 226 private JButton getOkButton() { 227 if (okButton == null) { 228 okButton = new JButton(); 229 okButton.setText("OK"); 230 okButton.setName("OK"); 231 } 232 return okButton; 233 } 234 235 /** 236 * This method initializes cancelButton 237 * 238 * @return javax.swing.JButton 239 */ 240 private JButton getCancelButton() { 241 if (cancelButton == null) { 242 cancelButton = new JButton(); 243 cancelButton.setText("Cancel"); 244 cancelButton.setName("CANCEL"); 245 } 246 return cancelButton; 247 } 248 249 /** 250 * This method initializes interpolation 251 * 252 * @return java.awt.Choice 253 */ 254 private Choice getInterpolation() { 255 if (interpolation == null) { 256 interpolation = new Choice(); 257 interpolation.add(tr("All")); 258 interpolation.add(tr("Even/Odd")); 259 } 260 return interpolation; 261 } 262 263 /** 264 * Registers the handler as a listener to all relevant events. 265 * 266 * @param handler the handler 267 */ 268 public void addHandler(HouseNumberInputHandler handler) { 269 this.hi.addActionListener(handler); 270 this.hi.addFocusListener(handler); 271 272 this.lo.addActionListener(handler); 273 this.lo.addFocusListener(handler); 274 275 this.segments.addActionListener(handler); 276 this.segments.addFocusListener(handler); 277 278 this.okButton.addActionListener(handler); 279 this.cancelButton.addActionListener(handler); 280 281 this.interpolation.addItemListener(handler); 282 283 } 284 285 /** 286 * Generates a list of all visible names of highways in order to do 287 * autocompletion on the road name. 288 */ 289 TreeSet<String> createAutoCompletionInfo() { 290 final TreeSet<String> names = new TreeSet<String>(); 291 for (OsmPrimitive osm : Main.main.getCurrentDataSet() 292 .allNonDeletedPrimitives()) { 293 if (osm.getKeys() != null && osm.keySet().contains("highway") 294 && osm.keySet().contains("name")) { 295 names.add(osm.get("name")); 296 } 297 } 298 return names; 299 } 300 50 /* 51 final static String MIN_NUMBER = "plugin.terracer.lowest_number"; 52 final static String MAX_NUMBER = "plugin.terracer.highest_number"; 53 final static String INTERPOLATION = "plugin.terracer.interpolation_mode"; 54 */ 55 final static String HANDLE_RELATION = "plugins.terracer.handle_relation"; 56 final static String DELETE_OUTLINE = "plugins.terracer.delete_outline"; 57 58 final private Way street; 59 final private boolean relationExists; 60 61 protected static final String DEFAULT_MESSAGE = tr("Enter housenumbers or amount of segments"); 62 private static final long serialVersionUID = 1L; 63 private Container jContentPane; 64 private JPanel inputPanel; 65 private JPanel buttonPanel; 66 private JLabel loLabel; 67 JTextField lo; 68 private JLabel hiLabel; 69 JTextField hi; 70 private JLabel streetLabel; 71 AutoCompleteComboBox streetComboBox; 72 private JLabel segmentsLabel; 73 JTextField segments; 74 JTextArea messageLabel; 75 JButton okButton; 76 JButton cancelButton; 77 private JLabel interpolationLabel; 78 Choice interpolation; 79 JCheckBox handleRelationCheckBox; 80 JCheckBox deleteOutlineCheckBox; 81 82 /** 83 * @param street If street is not null, we assume, the name of the street to be fixed 84 * and just show a label. If street is null, we show a ComboBox/InputField. 85 * @param relationExists If the buildings can be added to an existing relation or not. 86 */ 87 public HouseNumberInputDialog(Way street, boolean relationExists) { 88 super(JOptionPane.getFrameForComponent(Main.parent)); 89 this.street = street; 90 this.relationExists = relationExists; 91 initialize(); 92 } 93 94 /** 95 * This method initializes this 96 * 97 * @return void 98 */ 99 private void initialize() { 100 this.setTitle(tr("Terrace a house")); 101 getJContentPane(); 102 this.pack(); 103 this.setLocationRelativeTo(Main.parent); 104 } 105 106 /** 107 * This method initializes jContentPane 108 * 109 * @return javax.swing.JPanel 110 */ 111 private Container getJContentPane() { 112 if (jContentPane == null) { 113 messageLabel = new JTextArea(); 114 messageLabel.setText(DEFAULT_MESSAGE); 115 messageLabel.setAutoscrolls(true); 116 117 messageLabel.setLineWrap(true); 118 messageLabel.setRows(2); 119 messageLabel.setBackground(new Color(238, 238, 238)); 120 messageLabel.setEditable(false); 121 jContentPane = this.getContentPane(); 122 jContentPane.setLayout(new BoxLayout(jContentPane, 123 BoxLayout.Y_AXIS)); 124 jContentPane.add(messageLabel, jContentPane); 125 jContentPane.add(getInputPanel(), jContentPane); 126 jContentPane.add(getButtonPanel(), jContentPane); 127 128 } 129 return jContentPane; 130 } 131 132 /** 133 * This method initializes inputPanel 134 * 135 * @return javax.swing.JPanel 136 */ 137 private JPanel getInputPanel() { 138 if (inputPanel == null) { 139 interpolationLabel = new JLabel(); 140 interpolationLabel.setText(tr("Interpolation")); 141 segmentsLabel = new JLabel(); 142 segmentsLabel.setText(tr("Segments")); 143 streetLabel = new JLabel(); 144 streetLabel.setText(tr("Street")); 145 hiLabel = new JLabel(); 146 hiLabel.setText(tr("Highest Number")); 147 loLabel = new JLabel(); 148 loLabel.setText(tr("Lowest Number")); 149 loLabel.setPreferredSize(new Dimension(111, 16)); 150 loLabel.setToolTipText(tr("Lowest housenumber of the terraced house")); 151 final String txt = relationExists ? tr("add to existing associatedStreet relation") : tr("create an associatedStreet relation"); 152 handleRelationCheckBox = new JCheckBox(txt, Main.pref.getBoolean(HANDLE_RELATION, true)); 153 deleteOutlineCheckBox = new JCheckBox(tr("delete outline way"), Main.pref.getBoolean(DELETE_OUTLINE, true)); 154 155 inputPanel = new JPanel(); 156 inputPanel.setLayout(new GridBagLayout()); 157 inputPanel.add(loLabel, GBC.std().insets(3,3,0,0)); 158 inputPanel.add(getLo(), GBC.eol().fill(GBC.HORIZONTAL).insets(5,3,0,0)); 159 inputPanel.add(hiLabel, GBC.std().insets(3,3,0,0)); 160 inputPanel.add(getHi(), GBC.eol().fill(GBC.HORIZONTAL).insets(5,3,0,0)); 161 inputPanel.add(interpolationLabel, GBC.std().insets(3,3,0,0)); 162 inputPanel.add(getInterpolation(), GBC.eol().insets(5,3,0,0)); 163 inputPanel.add(segmentsLabel, GBC.std().insets(3,3,0,0)); 164 inputPanel.add(getSegments(), GBC.eol().fill(GBC.HORIZONTAL).insets(5,3,0,0)); 165 if (street == null) { 166 inputPanel.add(streetLabel, GBC.std().insets(3,3,0,0)); 167 inputPanel.add(getStreet(), GBC.eol().insets(5,3,0,0)); 168 } else { 169 inputPanel.add(new JLabel(tr("Street name: ")+"\""+street.get("name")+"\""), GBC.eol().insets(3,3,0,0)); 170 } 171 inputPanel.add(handleRelationCheckBox, GBC.eol().insets(3,3,0,0)); 172 inputPanel.add(deleteOutlineCheckBox, GBC.eol().insets(3,3,0,0)); 173 } 174 return inputPanel; 175 } 176 177 /** 178 * This method initializes buttonPanel 179 * 180 * @return javax.swing.JPanel 181 */ 182 private JPanel getButtonPanel() { 183 if (buttonPanel == null) { 184 buttonPanel = new JPanel(); 185 buttonPanel.setLayout(new FlowLayout()); 186 buttonPanel.add(getOkButton(), null); 187 buttonPanel.add(getCancelButton(), null); 188 } 189 return buttonPanel; 190 } 191 192 /** 193 * This method initializes lo 194 * 195 * @return javax.swing.JTextField 196 */ 197 private JTextField getLo() { 198 if (lo == null) { 199 lo = new JTextField(); 200 lo.setText(""); 201 } 202 return lo; 203 } 204 205 /** 206 * This method initializes hi 207 * 208 * @return javax.swing.JTextField 209 */ 210 private JTextField getHi() { 211 if (hi == null) { 212 hi = new JTextField(); 213 hi.setText(""); 214 } 215 return hi; 216 } 217 218 /** 219 * This method initializes street 220 * 221 * @return javax.swing.JTextField 222 */ 223 private AutoCompleteComboBox getStreet() { 224 225 if (streetComboBox == null) { 226 final TreeSet<String> names = createAutoCompletionInfo(); 227 228 streetComboBox = new AutoCompleteComboBox(); 229 streetComboBox.setPossibleItems(names); 230 streetComboBox.setEditable(true); 231 streetComboBox.setSelectedItem(null); 232 233 } 234 return streetComboBox; 235 } 236 237 /** 238 * This method initializes segments 239 * 240 * @return javax.swing.JTextField 241 */ 242 private JTextField getSegments() { 243 if (segments == null) { 244 segments = new JTextField(); 245 segments.setText("1"); 246 } 247 return segments; 248 } 249 250 /** 251 * This method initializes okButton 252 * 253 * @return javax.swing.JButton 254 */ 255 private JButton getOkButton() { 256 if (okButton == null) { 257 okButton = new JButton(); 258 okButton.setText(tr("OK")); 259 okButton.setName("OK"); 260 } 261 return okButton; 262 } 263 264 /** 265 * This method initializes cancelButton 266 * 267 * @return javax.swing.JButton 268 */ 269 private JButton getCancelButton() { 270 if (cancelButton == null) { 271 cancelButton = new JButton(); 272 cancelButton.setText(tr("Cancel")); 273 cancelButton.setName("CANCEL"); 274 } 275 return cancelButton; 276 } 277 278 /** 279 * This method initializes interpolation 280 * 281 * @return java.awt.Choice 282 */ 283 private Choice getInterpolation() { 284 if (interpolation == null) { 285 interpolation = new Choice(); 286 interpolation.add(tr("All")); 287 interpolation.add(tr("Even/Odd")); 288 } 289 return interpolation; 290 } 291 292 /** 293 * Registers the handler as a listener to all relevant events. 294 * 295 * @param handler the handler 296 */ 297 public void addHandler(HouseNumberInputHandler handler) { 298 this.hi.addActionListener(handler); 299 this.hi.addFocusListener(handler); 300 301 this.lo.addActionListener(handler); 302 this.lo.addFocusListener(handler); 303 304 this.segments.addActionListener(handler); 305 this.segments.addFocusListener(handler); 306 307 this.okButton.addActionListener(handler); 308 this.cancelButton.addActionListener(handler); 309 310 this.interpolation.addItemListener(handler); 311 312 } 313 314 /** 315 * Generates a list of all visible names of highways in order to do 316 * autocompletion on the road name. 317 */ 318 TreeSet<String> createAutoCompletionInfo() { 319 final TreeSet<String> names = new TreeSet<String>(); 320 for (OsmPrimitive osm : Main.main.getCurrentDataSet() 321 .allNonDeletedPrimitives()) { 322 if (osm.getKeys() != null && osm.keySet().contains("highway") 323 && osm.keySet().contains("name")) { 324 names.add(osm.get("name")); 325 } 326 } 327 return names; 328 } 301 329 } -
applications/editors/josm/plugins/terracer/src/terracer/HouseNumberInputHandler.java
r19234 r19658 22 22 import javax.swing.event.ChangeEvent; 23 23 import javax.swing.event.ChangeListener; 24 24 25 import org.openstreetmap.josm.Main; 25 26 import org.openstreetmap.josm.data.osm.Way; 27 import org.openstreetmap.josm.data.osm.Relation; 26 28 27 29 /** 28 30 * The Class HouseNumberInputHandler contains all the logic 29 31 * behind the house number input dialog. 30 * 32 * 31 33 * From a refactoring viewpoint, this class is indeed more interested in the fields 32 * of the HouseNumberInputDialog. This is desired design, as the HouseNumberInputDialog 34 * of the HouseNumberInputDialog. This is desired design, as the HouseNumberInputDialog 33 35 * is already cluttered with auto-generated layout code. 34 * 36 * 35 37 * @author casualwalker 36 38 */ 37 39 public class HouseNumberInputHandler implements ChangeListener, ItemListener, 38 ActionListener, FocusListener { 39 40 private TerracerAction terracerAction; 41 private Way way; 42 private HouseNumberInputDialog dialog; 43 44 /** 45 * Instantiates a new house number input handler. 46 * 47 * @param terracerAction the terracer action 48 * @param way the way 49 * @param title the title 50 */ 51 public HouseNumberInputHandler(final TerracerAction terracerAction, 52 final Way way, final String title) { 53 this.terracerAction = terracerAction; 54 this.way = way; 55 dialog = new HouseNumberInputDialog(null); 56 dialog.addHandler(this); 57 58 dialog.setVisible(true); 59 dialog.setTitle(title); 60 61 } 62 63 /** 64 * Validate the current input fields. 65 * When the validation fails, a red message is 66 * displayed and the OK button is disabled. 67 * 68 * Should be triggered each time the input changes. 69 */ 70 private void validateInput() { 71 boolean isOk = true; 72 StringBuffer message = new StringBuffer(); 73 74 isOk = isOk && checkNumberOrder(message); 75 isOk = isOk && checkSegmentsFromHousenumber(message); 76 isOk = isOk && checkSegments(message); 77 isOk = isOk 78 && checkNumberStringField(dialog.lo, tr("Lowest number"), 79 message); 80 isOk = isOk 81 && checkNumberStringField(dialog.hi, tr("Highest number"), 82 message); 83 isOk = isOk 84 && checkNumberStringField(dialog.segments, tr("Segments"), 85 message); 86 87 if (isOk) { 88 dialog.okButton.setEnabled(true); 89 dialog.messageLabel.setForeground(Color.black); 90 dialog.messageLabel 91 .setText(tr(HouseNumberInputDialog.DEFAULT_MESSAGE)); 92 93 } else { 94 dialog.okButton.setEnabled(false); 95 dialog.messageLabel.setForeground(Color.red); 96 dialog.messageLabel.setText(message.toString()); 97 } 98 } 99 100 /** 101 * Checks, if the lowest house number is indeed lower than the 102 * highest house number. 103 * This check applies only, if the house number fields are used at all. 104 * 105 * @param message the message 106 * 107 * @return true, if successful 108 */ 109 private boolean checkNumberOrder(final StringBuffer message) { 110 if (numberFrom() != null && numberTo() != null) { 111 if (numberFrom().intValue() > numberTo().intValue()) { 112 appendMessageNewLine(message); 113 message 114 .append(tr("Lowest housenumber cannot be higher than highest housenumber")); 115 return false; 116 } 117 } 118 return true; 119 } 120 121 /** 122 * Obtain the number segments from the house number fields and check, 123 * if they are valid. 124 * 125 * Also disables the segments field, if the house numbers contain 126 * valid information. 127 * 128 * @param message the message 129 * 130 * @return true, if successful 131 */ 132 private boolean checkSegmentsFromHousenumber(final StringBuffer message) { 133 dialog.segments.setEditable(true); 134 135 if (numberFrom() != null && numberTo() != null) { 136 137 int segments = numberTo().intValue() - numberFrom().intValue(); 138 139 if (segments % stepSize() != 0) { 140 appendMessageNewLine(message); 141 message 142 .append(tr("Housenumbers do not match odd/even setting")); 143 return false; 144 } 145 146 int steps = segments / stepSize(); 147 steps++; // difference 0 means 1 building, see 148 // TerracerActon.terraceBuilding 149 dialog.segments.setText(String.valueOf(steps)); 150 dialog.segments.setEditable(false); 151 152 } 153 return true; 154 } 155 156 /** 157 * Check the number of segments. 158 * It must be a number and greater than 1. 159 * 160 * @param message the message 161 * 162 * @return true, if successful 163 */ 164 private boolean checkSegments(final StringBuffer message) { 165 if (segments() == null || segments().intValue() < 1) { 166 appendMessageNewLine(message); 167 message.append(tr("Segment must be a number greater 1")); 168 return false; 169 170 } 171 return true; 172 } 173 174 /** 175 * Check, if a string field contains a positive integer. 176 * 177 * @param field the field 178 * @param label the label 179 * @param message the message 180 * 181 * @return true, if successful 182 */ 183 private boolean checkNumberStringField(final JTextField field, 184 final String label, final StringBuffer message) { 185 final String content = field.getText(); 186 if (content != null && content.length() != 0) { 187 try { 188 int i = Integer.parseInt(content); 189 if (i < 0) { 190 appendMessageNewLine(message); 191 message.append(tr("{0} must be greater than 0", label)); 192 return false; 193 } 194 } catch (NumberFormatException e) { 195 appendMessageNewLine(message); 196 message.append(tr("{0} is not a number", label)); 197 return false; 198 } 199 200 } 201 return true; 202 } 203 204 /** 205 * Append a new line to the message, if the message is not empty. 206 * 207 * @param message the message 208 */ 209 private void appendMessageNewLine(final StringBuffer message) { 210 if (message.length() > 0) { 211 message.append("\n"); 212 } 213 } 214 215 /* (non-Javadoc) 216 * @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent) 217 */ 218 public void stateChanged(ChangeEvent e) { 219 validateInput(); 220 221 } 222 223 /* (non-Javadoc) 224 * @see java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent) 225 */ 226 public void itemStateChanged(ItemEvent e) { 227 validateInput(); 228 } 229 230 /* (non-Javadoc) 231 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) 232 */ 233 public void actionPerformed(final ActionEvent e) { 234 235 // OK or Cancel button-actions 236 if (e.getSource() instanceof JButton) { 237 JButton button = (JButton) e.getSource(); 238 if ("OK".equals(button.getName())) { 239 terracerAction.terraceBuilding(way, segments(), numberFrom(), 240 numberTo(), stepSize(), streetName()); 241 242 this.dialog.dispose(); 243 } else if ("CANCEL".equals(button.getName())) { 244 this.dialog.dispose(); 245 } 246 } else { 247 // anything else is a change in the input 248 validateInput(); 249 } 250 251 } 252 253 /** 254 * Calculate the step size between two house numbers, 255 * based on the interpolation setting. 256 * 257 * @return the stepSize (1 for all, 2 for odd /even) 258 */ 259 public int stepSize() { 260 return (dialog.interpolation.getSelectedItem().equals(tr("All"))) ? 1 261 : 2; 262 } 263 264 /** 265 * Gets the number of segments, if set. 266 * 267 * @return the number of segments or null, if not set / invalid. 268 */ 269 public Integer segments() { 270 try { 271 return Integer.parseInt(dialog.segments.getText()); 272 } catch (NumberFormatException ex) { 273 return null; 274 } 275 } 276 277 /** 278 * Gets the lowest house number. 279 * 280 * @return the number of lowest house number or null, if not set / invalid. 281 */ 282 public Integer numberFrom() { 283 try { 284 return Integer.parseInt(dialog.lo.getText()); 285 } catch (NumberFormatException ex) { 286 return null; 287 } 288 } 289 290 /** 291 * Gets the highest house number. 292 * 293 * @return the number of highest house number or null, if not set / invalid. 294 */ 295 public Integer numberTo() { 296 try { 297 return Integer.parseInt(dialog.hi.getText()); 298 } catch (NumberFormatException ex) { 299 return null; 300 } 301 } 302 303 /** 304 * Gets the street name. 305 * 306 * @return the street name or null, if not set / invalid. 307 */ 308 public String streetName() { 309 // Object selected = street.getSelectedItem(); 310 Object selected = dialog.street.getSelectedItem(); 311 if (selected == null) { 312 return null; 313 } else { 314 String name = selected.toString(); 315 if (name.length() == 0) { 316 return null; 317 } else { 318 return name; 319 } 320 } 321 } 322 323 /* (non-Javadoc) 324 * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent) 325 */ 326 public void focusGained(FocusEvent e) { 327 validateInput(); 328 } 329 330 /* (non-Javadoc) 331 * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent) 332 */ 333 public void focusLost(FocusEvent e) { 334 validateInput(); 335 } 336 40 ActionListener, FocusListener { 41 42 private TerracerAction terracerAction; 43 private Way outline, street; 44 private Relation associatedStreet; 45 private HouseNumberInputDialog dialog; 46 47 /** 48 * 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 public HouseNumberInputHandler(final TerracerAction terracerAction, 57 final Way outline, final Way street, final Relation associatedStreet, 58 final String title) { 59 this.terracerAction = terracerAction; 60 this.outline = outline; 61 this.street = street; 62 this.associatedStreet = associatedStreet; 63 dialog = new HouseNumberInputDialog(street, associatedStreet != null); 64 dialog.addHandler(this); 65 66 dialog.setVisible(true); 67 dialog.setTitle(title); 68 69 } 70 71 /** 72 * Validate the current input fields. 73 * When the validation fails, a red message is 74 * displayed and the OK button is disabled. 75 * 76 * Should be triggered each time the input changes. 77 */ 78 private void validateInput() { 79 boolean isOk = true; 80 StringBuffer message = new StringBuffer(); 81 82 isOk = isOk && checkNumberOrder(message); 83 isOk = isOk && checkSegmentsFromHousenumber(message); 84 isOk = isOk && checkSegments(message); 85 isOk = isOk 86 && checkNumberStringField(dialog.lo, tr("Lowest number"), 87 message); 88 isOk = isOk 89 && checkNumberStringField(dialog.hi, tr("Highest number"), 90 message); 91 isOk = isOk 92 && checkNumberStringField(dialog.segments, tr("Segments"), 93 message); 94 95 if (isOk) { 96 dialog.okButton.setEnabled(true); 97 dialog.messageLabel.setForeground(Color.black); 98 dialog.messageLabel 99 .setText(tr(HouseNumberInputDialog.DEFAULT_MESSAGE)); 100 101 } else { 102 dialog.okButton.setEnabled(false); 103 dialog.messageLabel.setForeground(Color.red); 104 dialog.messageLabel.setText(message.toString()); 105 } 106 } 107 108 /** 109 * Checks, if the lowest house number is indeed lower than the 110 * highest house number. 111 * This check applies only, if the house number fields are used at all. 112 * 113 * @param message the message 114 * 115 * @return true, if successful 116 */ 117 private boolean checkNumberOrder(final StringBuffer message) { 118 if (numberFrom() != null && numberTo() != null) { 119 if (numberFrom().intValue() > numberTo().intValue()) { 120 appendMessageNewLine(message); 121 message 122 .append(tr("Lowest housenumber cannot be higher than highest housenumber")); 123 return false; 124 } 125 } 126 return true; 127 } 128 129 /** 130 * Obtain the number segments from the house number fields and check, 131 * if they are valid. 132 * 133 * Also disables the segments field, if the house numbers contain 134 * valid information. 135 * 136 * @param message the message 137 * 138 * @return true, if successful 139 */ 140 private boolean checkSegmentsFromHousenumber(final StringBuffer message) { 141 dialog.segments.setEditable(true); 142 143 if (numberFrom() != null && numberTo() != null) { 144 145 int segments = numberTo().intValue() - numberFrom().intValue(); 146 147 if (segments % stepSize() != 0) { 148 appendMessageNewLine(message); 149 message 150 .append(tr("Housenumbers do not match odd/even setting")); 151 return false; 152 } 153 154 int steps = segments / stepSize(); 155 steps++; // difference 0 means 1 building, see 156 // TerracerActon.terraceBuilding 157 dialog.segments.setText(String.valueOf(steps)); 158 dialog.segments.setEditable(false); 159 160 } 161 return true; 162 } 163 164 /** 165 * Check the number of segments. 166 * It must be a number and greater than 1. 167 * 168 * @param message the message 169 * 170 * @return true, if successful 171 */ 172 private boolean checkSegments(final StringBuffer message) { 173 if (segments() == null || segments().intValue() < 1) { 174 appendMessageNewLine(message); 175 message.append(tr("Segment must be a number greater 1")); 176 return false; 177 178 } 179 return true; 180 } 181 182 /** 183 * Check, if a string field contains a positive integer. 184 * 185 * @param field the field 186 * @param label the label 187 * @param message the message 188 * 189 * @return true, if successful 190 */ 191 private boolean checkNumberStringField(final JTextField field, 192 final String label, final StringBuffer message) { 193 final String content = field.getText(); 194 if (content != null && content.length() != 0) { 195 try { 196 int i = Integer.parseInt(content); 197 if (i < 0) { 198 appendMessageNewLine(message); 199 message.append(tr("{0} must be greater than 0", label)); 200 return false; 201 } 202 } catch (NumberFormatException e) { 203 appendMessageNewLine(message); 204 message.append(tr("{0} is not a number", label)); 205 return false; 206 } 207 208 } 209 return true; 210 } 211 212 /** 213 * Append a new line to the message, if the message is not empty. 214 * 215 * @param message the message 216 */ 217 private void appendMessageNewLine(final StringBuffer message) { 218 if (message.length() > 0) { 219 message.append("\n"); 220 } 221 } 222 223 /* (non-Javadoc) 224 * @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent) 225 */ 226 public void stateChanged(ChangeEvent e) { 227 validateInput(); 228 229 } 230 231 /* (non-Javadoc) 232 * @see java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent) 233 */ 234 public void itemStateChanged(ItemEvent e) { 235 validateInput(); 236 } 237 238 /* (non-Javadoc) 239 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) 240 */ 241 public void actionPerformed(final ActionEvent e) { 242 243 // OK or Cancel button-actions 244 if (e.getSource() instanceof JButton) { 245 JButton button = (JButton) e.getSource(); 246 if ("OK".equals(button.getName())) { 247 saveValues(); 248 terracerAction.terraceBuilding( 249 outline, 250 street, 251 associatedStreet, 252 segments(), 253 numberFrom(), 254 numberTo(), 255 stepSize(), 256 streetName(), 257 doHandleRelation(), 258 doDeleteOutline()); 259 260 this.dialog.dispose(); 261 } else if ("CANCEL".equals(button.getName())) { 262 this.dialog.dispose(); 263 } 264 } else { 265 // anything else is a change in the input 266 validateInput(); 267 } 268 269 } 270 271 /** 272 * Calculate the step size between two house numbers, 273 * based on the interpolation setting. 274 * 275 * @return the stepSize (1 for all, 2 for odd /even) 276 */ 277 public int stepSize() { 278 return (dialog.interpolation.getSelectedItem().equals(tr("All"))) ? 1 279 : 2; 280 } 281 282 /** 283 * Gets the number of segments, if set. 284 * 285 * @return the number of segments or null, if not set / invalid. 286 */ 287 public Integer segments() { 288 try { 289 return Integer.parseInt(dialog.segments.getText()); 290 } catch (NumberFormatException ex) { 291 return null; 292 } 293 } 294 295 /** 296 * Gets the lowest house number. 297 * 298 * @return the number of lowest house number or null, if not set / invalid. 299 */ 300 public Integer numberFrom() { 301 try { 302 return Integer.parseInt(dialog.lo.getText()); 303 } catch (NumberFormatException ex) { 304 return null; 305 } 306 } 307 308 /** 309 * Gets the highest house number. 310 * 311 * @return the number of highest house number or null, if not set / invalid. 312 */ 313 public Integer numberTo() { 314 try { 315 return Integer.parseInt(dialog.hi.getText()); 316 } catch (NumberFormatException ex) { 317 return null; 318 } 319 } 320 321 /** 322 * Gets the street name. 323 * 324 * @return the street name or null, if not set / invalid. 325 */ 326 public String streetName() { 327 if (street != null) 328 return null; 329 Object selected = dialog.streetComboBox.getSelectedItem(); 330 if (selected == null) { 331 return null; 332 } else { 333 String name = selected.toString(); 334 if (name.length() == 0) { 335 return null; 336 } else { 337 return name; 338 } 339 } 340 } 341 342 /** 343 * Whether the user likes to create a relation or add to 344 * an existing one. 345 */ 346 public boolean doHandleRelation() { 347 return dialog.handleRelationCheckBox.isSelected(); 348 } 349 350 /** 351 * Whether the user likes to delete the outline way. 352 */ 353 public boolean doDeleteOutline() { 354 return dialog.deleteOutlineCheckBox.isSelected(); 355 } 356 357 /* (non-Javadoc) 358 * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent) 359 */ 360 public void focusGained(FocusEvent e) { 361 validateInput(); 362 } 363 364 /* (non-Javadoc) 365 * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent) 366 */ 367 public void focusLost(FocusEvent e) { 368 validateInput(); 369 } 370 371 /** 372 * Saves settings. 373 */ 374 public void saveValues() { 375 Main.pref.put(HouseNumberInputDialog.HANDLE_RELATION, doHandleRelation()); 376 Main.pref.put(HouseNumberInputDialog.DELETE_OUTLINE, doDeleteOutline()); 377 } 337 378 } -
applications/editors/josm/plugins/terracer/src/terracer/ReverseTerraceAction.java
r18924 r19658 32 32 public class ReverseTerraceAction extends JosmAction { 33 33 34 35 36 37 38 39 40 41 42 43 34 public ReverseTerraceAction() { 35 super(tr("Reverse a terrace"), 36 "reverse_terrace", 37 tr("Reverses house numbers on a terrace."), 38 Shortcut.registerShortcut("tools:ReverseTerrace", 39 tr("Tool: {0}", tr("Reverse a Terrace")), 40 KeyEvent.VK_R, Shortcut.GROUP_EDIT, 41 Shortcut.SHIFT_DEFAULT), 42 true); 43 } 44 44 45 46 47 48 49 50 51 45 /** 46 * Breadth-first searches based on the selection while the selection is a way 47 * with a building=* tag and then applies the addr:housenumber tag in reverse 48 * order. 49 */ 50 public void actionPerformed(ActionEvent e) { 51 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected(); 52 52 53 54 55 53 // set to keep track of all the nodes that have been visited - that is: if 54 // we encounter them again we will not follow onto the connected ways. 55 HashSet<Node> visitedNodes = new HashSet<Node>(); 56 56 57 58 59 60 57 // set to keep track of the ways the algorithm has seen, but not yet visited. 58 // since when a way is visited all of its nodes are marked as visited, there 59 // is no need to keep a visitedWays set. 60 HashSet<Way> front = new HashSet<Way>(); 61 61 62 63 64 65 66 67 68 69 62 // initialise the set with all the buildings in the selection. this means 63 // there is undefined behaviour when there is a multiple selection, as the 64 // ordering will be based on the hash. 65 for (OsmPrimitive prim : sel) { 66 if (prim.keySet().contains("building") && prim instanceof Way) { 67 front.add((Way)prim); 68 } 69 } 70 70 71 72 71 // this is like a visitedWays set, but in a linear order. 72 LinkedList<Way> orderedWays = new LinkedList<Way>(); 73 73 74 75 74 // and the tags to reverse on the orderedWays. 75 LinkedList<String> houseNumbers = new LinkedList<String>(); 76 76 77 78 79 77 while (front.size() > 0) { 78 // Java apparently doesn't have useful methods to get single items from sets... 79 Way w = front.iterator().next(); 80 80 81 82 83 84 85 86 87 88 89 90 91 92 81 // visit all the nodes in the way, adding the building's they're members of 82 // to the front. 83 for (Node n : w.getNodes()) { 84 if (!visitedNodes.contains(n)) { 85 for (OsmPrimitive prim : n.getReferrers()) { 86 if (prim.keySet().contains("building") && prim instanceof Way) { 87 front.add((Way)prim); 88 } 89 } 90 visitedNodes.add(n); 91 } 92 } 93 93 94 95 96 97 98 99 94 // we've finished visiting this way, so record the attributes we're interested 95 // in for re-writing. 96 front.remove(w); 97 orderedWays.addLast(w); 98 houseNumbers.addFirst(w.get("addr:housenumber")); 99 } 100 100 101 102 103 104 105 106 107 108 101 Collection<Command> commands = new LinkedList<Command>(); 102 // what, no zipWith? 103 for (int i = 0; i < orderedWays.size(); ++i) { 104 commands.add(new ChangePropertyCommand( 105 orderedWays.get(i), 106 "addr:housenumber", 107 houseNumbers.get(i))); 108 } 109 109 110 111 112 110 Main.main.undoRedo.add(new SequenceCommand(tr("Reverse Terrace"), commands)); 111 Main.main.getCurrentDataSet().setSelected(orderedWays); 112 } 113 113 114 114 } -
applications/editors/josm/plugins/terracer/src/terracer/TerracerAction.java
r19085 r19658 16 16 import java.util.Collection; 17 17 import java.util.Collections; 18 import java.util.Iterator; 18 19 import java.util.LinkedList; 20 import java.util.List; 19 21 20 22 import javax.swing.JOptionPane; … … 23 25 import org.openstreetmap.josm.actions.JosmAction; 24 26 import org.openstreetmap.josm.command.AddCommand; 27 import org.openstreetmap.josm.command.ChangeCommand; 25 28 import org.openstreetmap.josm.command.Command; 29 import org.openstreetmap.josm.command.DeleteCommand; 26 30 import org.openstreetmap.josm.command.SequenceCommand; 27 31 import org.openstreetmap.josm.data.coor.LatLon; … … 36 40 /** 37 41 * Terraces a quadrilateral, closed way into a series of quadrilateral, 38 * closed ways. 42 * closed ways. If two ways are selected and one of them can be identified as 43 * a street (highway=*, name=*) then the given street will be added 44 * to the 'associatedStreet' relation. 45 * 39 46 * 40 47 * At present it only works on quadrilaterals, but there is no reason … … 46 53 public final class TerracerAction extends JosmAction { 47 54 48 // smsms1 asked for the last value to be remembered to make it easier to do 49 // repeated terraces. this is the easiest, but not necessarily nicest, way. 50 // private static String lastSelectedValue = ""; 51 52 public TerracerAction() { 53 super(tr("Terrace a building"), "terrace", 54 tr("Creates individual buildings from a long building."), 55 Shortcut.registerShortcut("tools:Terracer", tr("Tool: {0}", 56 tr("Terrace a building")), KeyEvent.VK_T, 57 Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), true); 58 } 59 60 /** 61 * Checks that the selection is OK. If not, displays error message. If so 62 * calls to terraceBuilding(), which does all the real work. 63 */ 64 public void actionPerformed(ActionEvent e) { 65 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet() 66 .getSelected(); 67 boolean badSelect = false; 68 69 if (sel.size() == 1) { 70 OsmPrimitive prim = sel.iterator().next(); 71 72 if (prim instanceof Way) { 73 Way way = (Way) prim; 74 75 if ((way.getNodesCount() >= 5) && way.isClosed()) { 76 String title = trn("Change {0} object", 77 "Change {0} objects", sel.size(), sel.size()); 78 if (sel.size() == 0) 79 title = tr("Nothing selected!"); 80 81 // show input dialog. 82 new HouseNumberInputHandler(this, way, title); 83 84 } else { 85 badSelect = true; 86 } 87 } else { 88 badSelect = true; 89 } 90 } else { 91 badSelect = true; 92 } 93 94 if (badSelect) { 95 JOptionPane.showMessageDialog(Main.parent, 96 tr("Select a single, closed way of at least four nodes.")); 97 } 98 } 99 100 /** 101 * Terraces a single, closed, quadrilateral way. 102 * 103 * Any node must be adjacent to both a short and long edge, we naively 104 * choose the longest edge and its opposite and interpolate along them 105 * linearly to produce new nodes. Those nodes are then assembled into 106 * closed, quadrilateral ways and left in the selection. 107 * 108 * @param w The closed, quadrilateral way to terrace. 109 */ 110 public void terraceBuilding(Way w, Integer segments, Integer from, 111 Integer to, int step, String streetName) { 112 final int nb; 113 if (to != null && from != null) { 114 nb = 1 + (to.intValue() - from.intValue()) / step; 115 } else if (segments != null) { 116 nb = segments.intValue(); 117 } else { 118 // if we get here, there is is a bug in the input validation. 119 throw new TerracerRuntimeException( 120 "Could not determine segments from parameters, this is a bug. " 121 + "Parameters were: segments " + segments 122 + " from " + from + " to " + to + " step " + step); 123 } 124 125 // now find which is the longest side connecting the first node 126 Pair<Way, Way> interp = findFrontAndBack(w); 127 128 final double frontLength = wayLength(interp.a); 129 final double backLength = wayLength(interp.b); 130 131 // new nodes array to hold all intermediate nodes 132 Node[][] new_nodes = new Node[2][nb + 1]; 133 134 Collection<Command> commands = new LinkedList<Command>(); 135 Collection<Way> ways = new LinkedList<Way>(); 136 137 // create intermediate nodes by interpolating. 138 for (int i = 0; i <= nb; ++i) { 139 new_nodes[0][i] = interpolateAlong(interp.a, frontLength * (i) 140 / (nb)); 141 new_nodes[1][i] = interpolateAlong(interp.b, backLength * (i) 142 / (nb)); 143 commands.add(new AddCommand(new_nodes[0][i])); 144 commands.add(new AddCommand(new_nodes[1][i])); 145 } 146 147 // create a new relation for addressing 148 Relation relatedStreet = new Relation(); 149 relatedStreet.put("type", "relatedStreet"); 150 if (streetName != null) { 151 relatedStreet.put("name", streetName); 152 } 153 // note that we don't actually add the street member to the relation, as 154 // the name isn't unambiguous and it could cause confusion if the editor 155 // were 156 // to automatically select one which wasn't the one the user intended. 157 158 // assemble new quadrilateral, closed ways 159 for (int i = 0; i < nb; ++i) { 160 Way terr = new Way(); 161 // Using Way.nodes.add rather than Way.addNode because the latter 162 // doesn't 163 // exist in older versions of JOSM. 164 terr.addNode(new_nodes[0][i]); 165 terr.addNode(new_nodes[0][i + 1]); 166 terr.addNode(new_nodes[1][i + 1]); 167 terr.addNode(new_nodes[1][i]); 168 terr.addNode(new_nodes[0][i]); 169 170 if (from != null) { 171 // only, if the user has specified house numbers 172 terr.put("addr:housenumber", "" + (from + i * step)); 173 } 174 terr.put("building", "yes"); 175 if (streetName != null) { 176 terr.put("addr:street", streetName); 177 } 178 relatedStreet.addMember(new RelationMember("house", terr)); 179 ways.add(terr); 180 commands.add(new AddCommand(terr)); 181 } 182 183 commands.add(new AddCommand(relatedStreet)); 184 185 Main.main.undoRedo.add(new SequenceCommand(tr("Terrace"), commands)); 186 Main.main.getCurrentDataSet().setSelected(ways); 187 } 188 189 /** 190 * Creates a node at a certain distance along a way, as calculated by the 191 * great circle distance. 192 * 193 * Note that this really isn't an efficient way to do this and leads to 194 * O(N^2) running time for the main algorithm, but its simple and easy 195 * to understand, and probably won't matter for reasonable-sized ways. 196 * 197 * @param w The way to interpolate. 198 * @param l The length at which to place the node. 199 * @return A node at a distance l along w from the first point. 200 */ 201 private Node interpolateAlong(Way w, double l) { 202 Node n = null; 203 for (Pair<Node, Node> p : w.getNodePairs(false)) { 204 final double seg_length = p.a.getCoor().greatCircleDistance( 205 p.b.getCoor()); 206 if (l <= seg_length) { 207 n = interpolateNode(p.a, p.b, l / seg_length); 208 break; 209 } else { 210 l -= seg_length; 211 } 212 } 213 if (n == null) { 214 // sometimes there is a small overshoot due to numerical roundoff, 215 // so we just 216 // set these cases to be equal to the last node. its not pretty, but 217 // it works ;-) 218 n = w.getNode(w.getNodesCount() - 1); 219 } 220 return n; 221 } 222 223 /** 224 * Calculates the great circle length of a way by summing the great circle 225 * distance of each pair of nodes. 226 * 227 * @param w The way to calculate length of. 228 * @return The length of the way. 229 */ 230 private double wayLength(Way w) { 231 double length = 0.0; 232 for (Pair<Node, Node> p : w.getNodePairs(false)) { 233 length += p.a.getCoor().greatCircleDistance(p.b.getCoor()); 234 } 235 return length; 236 } 237 238 /** 239 * Given a way, try and find a definite front and back by looking at the 240 * segments to find the "sides". Sides are assumed to be single segments 241 * which cannot be contiguous. 242 * 243 * @param w The way to analyse. 244 * @return A pair of ways (front, back) pointing in the same directions. 245 */ 246 private Pair<Way, Way> findFrontAndBack(Way w) { 247 // calculate the "side-ness" score for each segment of the way 248 double[] sideness = calculateSideness(w); 249 250 // find the largest two sidenesses which are not contiguous 251 int[] indexes = sortedIndexes(sideness); 252 int side1 = indexes[0]; 253 int side2 = indexes[1]; 254 // if side2 is contiguous with side1 then look further down the 255 // list. we know there are at least 4 sides, as anything smaller 256 // than a quadrilateral would have been rejected at an earlier 257 // stage. 258 if (Math.abs(side1 - side2) < 2) { 259 side2 = indexes[2]; 260 } 261 if (Math.abs(side1 - side2) < 2) { 262 side2 = indexes[3]; 263 } 264 265 // if the second side has a shorter length and an approximately equal 266 // sideness then its better to choose the shorter, as with 267 // quadrilaterals 268 // created using the orthogonalise tool the sideness will be about the 269 // same for all sides. 270 if (sideLength(w, side1) > sideLength(w, side1 + 1) 271 && Math.abs(sideness[side1] - sideness[side1 + 1]) < 0.001) { 272 side1 = side1 + 1; 273 side2 = (side2 + 1) % (w.getNodesCount() - 1); 274 } 275 276 // swap side1 and side2 into sorted order. 277 if (side1 > side2) { 278 // i can't believe i have to write swap() myself - surely java 279 // standard 280 // library has this somewhere??!!?ONE! 281 int tmp = side2; 282 side2 = side1; 283 side1 = tmp; 284 } 285 286 Way front = new Way(); 287 Way back = new Way(); 288 for (int i = side2 + 1; i < w.getNodesCount() - 1; ++i) { 289 front.addNode(w.getNode(i)); 290 } 291 for (int i = 0; i <= side1; ++i) { 292 front.addNode(w.getNode(i)); 293 } 294 // add the back in reverse order so that the front and back ways point 295 // in the same direction. 296 for (int i = side2; i > side1; --i) { 297 back.addNode(w.getNode(i)); 298 } 299 300 return new Pair<Way, Way>(front, back); 301 } 302 303 /** 304 * Calculate the length of a side (from node i to i+1) in a way. This assumes that 305 * the way is closed, but I only ever call it for buildings. 306 */ 307 private double sideLength(Way w, int i) { 308 Node a = w.getNode(i); 309 Node b = w.getNode((i + 1) % (w.getNodesCount() - 1)); 310 return a.getCoor().greatCircleDistance(b.getCoor()); 311 } 312 313 /** 314 * Given an array of doubles (but this could made generic very easily) sort 315 * into order and return the array of indexes such that, for a returned array 316 * x, a[x[i]] is sorted for ascending index i. 317 * 318 * This isn't efficient at all, but should be fine for the small arrays we're 319 * expecting. If this gets slow - replace it with some more efficient algorithm. 320 * 321 * @param a The array to sort. 322 * @return An array of indexes, the same size as the input, such that a[x[i]] 323 * is in sorted order. 324 */ 325 private int[] sortedIndexes(final double[] a) { 326 class SortWithIndex implements Comparable<SortWithIndex> { 327 public double x; 328 public int i; 329 330 public SortWithIndex(double a, int b) { 331 x = a; 332 i = b; 333 } 334 335 public int compareTo(SortWithIndex o) { 336 return Double.compare(x, o.x); 337 }; 338 } 339 340 final int length = a.length; 341 ArrayList<SortWithIndex> sortable = new ArrayList<SortWithIndex>(length); 342 for (int i = 0; i < length; ++i) { 343 sortable.add(new SortWithIndex(a[i], i)); 344 } 345 Collections.sort(sortable); 346 347 int[] indexes = new int[length]; 348 for (int i = 0; i < length; ++i) { 349 indexes[i] = sortable.get(i).i; 350 } 351 352 return indexes; 353 } 354 355 /** 356 * Calculate "sideness" metric for each segment in a way. 357 */ 358 private double[] calculateSideness(Way w) { 359 final int length = w.getNodesCount() - 1; 360 double[] sideness = new double[length]; 361 362 sideness[0] = calculateSideness(w.getNode(length - 1), w.getNode(0), w 363 .getNode(1), w.getNode(2)); 364 for (int i = 1; i < length - 1; ++i) { 365 sideness[i] = calculateSideness(w.getNode(i - 1), w.getNode(i), w 366 .getNode(i + 1), w.getNode(i + 2)); 367 } 368 sideness[length - 1] = calculateSideness(w.getNode(length - 2), w 369 .getNode(length - 1), w.getNode(length), w.getNode(1)); 370 371 return sideness; 372 } 373 374 /** 375 * Calculate sideness of a single segment given the nodes which make up that 376 * segment and its previous and next segments in order. Sideness is calculated 377 * for the segment b-c. 378 */ 379 private double calculateSideness(Node a, Node b, Node c, Node d) { 380 final double ndx = b.getCoor().getX() - a.getCoor().getX(); 381 final double pdx = d.getCoor().getX() - c.getCoor().getX(); 382 final double ndy = b.getCoor().getY() - a.getCoor().getY(); 383 final double pdy = d.getCoor().getY() - c.getCoor().getY(); 384 385 return (ndx * pdx + ndy * pdy) 386 / Math.sqrt((ndx * ndx + ndy * ndy) * (pdx * pdx + pdy * pdy)); 387 } 388 389 /** 390 * Creates a new node at the interpolated position between the argument 391 * nodes. Interpolates linearly in Lat/Lon coordinates. 392 * 393 * @param a First node, at which f=0. 394 * @param b Last node, at which f=1. 395 * @param f Fractional position between first and last nodes. 396 * @return A new node at the interpolated position. 397 */ 398 private Node interpolateNode(Node a, Node b, double f) { 399 Node n = new Node(interpolateLatLon(a, b, f)); 400 return n; 401 } 402 403 /** 404 * Calculates the interpolated position between the argument nodes. Interpolates 405 * linearly in Lat/Lon coordinates. 406 * 407 * @param a First node, at which f=0. 408 * @param b Last node, at which f=1. 409 * @param f Fractional position between first and last nodes. 410 * @return The interpolated position. 411 */ 412 private LatLon interpolateLatLon(Node a, Node b, double f) { 413 // this isn't quite right - we should probably be interpolating 414 // screen coordinates rather than lat/lon, but it doesn't seem to 415 // make a great deal of difference at the scale of most terraces. 416 return new LatLon( 417 a.getCoor().lat() * (1.0 - f) + b.getCoor().lat() * f, a 418 .getCoor().lon() 419 * (1.0 - f) + b.getCoor().lon() * f); 420 } 55 // smsms1 asked for the last value to be remembered to make it easier to do 56 // repeated terraces. this is the easiest, but not necessarily nicest, way. 57 // private static String lastSelectedValue = ""; 58 59 public TerracerAction() { 60 super(tr("Terrace a building"), "terrace", 61 tr("Creates individual buildings from a long building."), 62 Shortcut.registerShortcut("tools:Terracer", tr("Tool: {0}", 63 tr("Terrace a building")), KeyEvent.VK_T, 64 Shortcut.GROUP_EDIT, Shortcut.SHIFT_DEFAULT), true); 65 } 66 67 /** 68 * Checks that the selection is OK. If not, displays error message. If so 69 * calls to terraceBuilding(), which does all the real work. 70 */ 71 public void actionPerformed(ActionEvent e) { 72 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet() 73 .getSelected(); 74 Way outline = null; 75 Way street = null; 76 77 class InvalidUserInputException extends Exception { 78 InvalidUserInputException(String message) { 79 super(message); 80 } 81 InvalidUserInputException() { 82 super(); 83 } 84 } 85 86 try { 87 if (sel.size() == 2) { 88 Iterator<OsmPrimitive> it = sel.iterator(); 89 OsmPrimitive prim1 = it.next(); 90 OsmPrimitive prim2 = it.next(); 91 if (!(prim1 instanceof Way && prim2 instanceof Way)) 92 throw new InvalidUserInputException(); 93 Way way1 = (Way) prim1; 94 Way way2 = (Way) prim2; 95 if (way2.get("highway") != null) { 96 street = way2; 97 outline = way1; 98 } else if (way1.get("highway") != null) { 99 street = way1; 100 outline = way2; 101 } else 102 throw new InvalidUserInputException(); 103 if (street.get("name") == null) 104 throw new InvalidUserInputException(); 105 106 } else if (sel.size() == 1) { 107 OsmPrimitive prim = sel.iterator().next(); 108 109 if (!(prim instanceof Way)) 110 throw new InvalidUserInputException(); 111 112 outline = (Way)prim; 113 } else 114 throw new InvalidUserInputException(); 115 116 if (outline.getNodesCount() < 5) 117 throw new InvalidUserInputException(); 118 119 if (!outline.isClosed()) 120 throw new InvalidUserInputException(); 121 } catch (InvalidUserInputException ex) { 122 JOptionPane.showMessageDialog(Main.parent, 123 tr("Select a single, closed way of at least four nodes.")); 124 return; 125 } 126 127 // If we have a street, try to find a associatedStreet relation that could be reused. 128 Relation associatedStreet = null; 129 if (street != null) { 130 outer:for (OsmPrimitive osm : Main.main.getCurrentDataSet().allNonDeletedPrimitives()) { 131 if (!(osm instanceof Relation)) continue; 132 Relation rel = (Relation) osm; 133 if ("associatedStreet".equals(rel.get("type")) && street.get("name").equals(rel.get("name"))) { 134 List<RelationMember> members = rel.getMembers(); 135 for (RelationMember m : members) { 136 if ("street".equals(m.getRole()) && m.isWay() && m.getMember().equals(street)) { 137 associatedStreet = rel; 138 break outer; 139 } 140 } 141 } 142 } 143 } 144 145 String title = trn("Change {0} object", "Change {0} objects", sel.size(), sel.size()); 146 // show input dialog. 147 new HouseNumberInputHandler(this, outline, street, associatedStreet, title); 148 } 149 150 /** 151 * Terraces a single, closed, quadrilateral way. 152 * 153 * Any node must be adjacent to both a short and long edge, we naively 154 * choose the longest edge and its opposite and interpolate along them 155 * linearly to produce new nodes. Those nodes are then assembled into 156 * closed, quadrilateral ways and left in the selection. 157 * 158 * @param outline The closed, quadrilateral way to terrace. 159 * @param street The street, the buildings belong to (may be null) 160 * @param handleRelations If the user likes to add a relation or extend an existing relation 161 * @param deleteOutline If the outline way should be deleted, when done 162 */ 163 public void terraceBuilding(Way outline, Way street, Relation associatedStreet, Integer segments, Integer from, 164 Integer to, int step, String streetName, boolean handleRelations, boolean deleteOutline) { 165 final int nb; 166 if (to != null && from != null) { 167 nb = 1 + (to.intValue() - from.intValue()) / step; 168 } else if (segments != null) { 169 nb = segments.intValue(); 170 } else { 171 // if we get here, there is is a bug in the input validation. 172 throw new TerracerRuntimeException( 173 "Could not determine segments from parameters, this is a bug. " 174 + "Parameters were: segments " + segments 175 + " from " + from + " to " + to + " step " + step); 176 } 177 178 // now find which is the longest side connecting the first node 179 Pair<Way, Way> interp = findFrontAndBack(outline); 180 181 final double frontLength = wayLength(interp.a); 182 final double backLength = wayLength(interp.b); 183 184 // new nodes array to hold all intermediate nodes 185 Node[][] new_nodes = new Node[2][nb + 1]; 186 187 Collection<Command> commands = new LinkedList<Command>(); 188 Collection<Way> ways = new LinkedList<Way>(); 189 190 // create intermediate nodes by interpolating. 191 for (int i = 0; i <= nb; ++i) { 192 new_nodes[0][i] = interpolateAlong(interp.a, frontLength * (i) 193 / (nb)); 194 new_nodes[1][i] = interpolateAlong(interp.b, backLength * (i) 195 / (nb)); 196 commands.add(new AddCommand(new_nodes[0][i])); 197 commands.add(new AddCommand(new_nodes[1][i])); 198 } 199 200 // assemble new quadrilateral, closed ways 201 for (int i = 0; i < nb; ++i) { 202 Way terr = new Way(); 203 // Using Way.nodes.add rather than Way.addNode because the latter 204 // doesn't 205 // exist in older versions of JOSM. 206 terr.addNode(new_nodes[0][i]); 207 terr.addNode(new_nodes[0][i + 1]); 208 terr.addNode(new_nodes[1][i + 1]); 209 terr.addNode(new_nodes[1][i]); 210 terr.addNode(new_nodes[0][i]); 211 if (from != null) { 212 // only, if the user has specified house numbers 213 terr.put("addr:housenumber", "" + (from + i * step)); 214 } 215 terr.put("building", "yes"); 216 if (street != null) { 217 terr.put("addr:street", street.get("name")); 218 } else if (streetName != null) { 219 terr.put("addr:street", streetName); 220 } 221 ways.add(terr); 222 commands.add(new AddCommand(terr)); 223 } 224 225 if (handleRelations) { // create a new relation or merge with existing 226 if (associatedStreet == null) { // create a new relation 227 associatedStreet = new Relation(); 228 associatedStreet.put("type", "associatedStreet"); 229 if (street != null) { // a street was part of the selection 230 associatedStreet.put("name", street.get("name")); 231 associatedStreet.addMember(new RelationMember("street", street)); 232 } else { 233 associatedStreet.put("name", streetName); 234 } 235 for (Way w : ways) { 236 associatedStreet.addMember(new RelationMember("house", w)); 237 } 238 commands.add(new AddCommand(associatedStreet)); 239 } 240 else { // relation exists already - add new members 241 Relation newAssociatedStreet = new Relation(associatedStreet); 242 for (Way w : ways) { 243 newAssociatedStreet.addMember(new RelationMember("house", w)); 244 } 245 commands.add(new ChangeCommand(associatedStreet, newAssociatedStreet)); 246 } 247 } 248 249 if (deleteOutline) { 250 commands.add(DeleteCommand.delete(Main.main.getEditLayer(), Collections.singleton(outline), true, true)); 251 } 252 253 Main.main.undoRedo.add(new SequenceCommand(tr("Terrace"), commands)); 254 Main.main.getCurrentDataSet().setSelected(ways); 255 } 256 257 /** 258 * Creates a node at a certain distance along a way, as calculated by the 259 * great circle distance. 260 * 261 * Note that this really isn't an efficient way to do this and leads to 262 * O(N^2) running time for the main algorithm, but its simple and easy 263 * to understand, and probably won't matter for reasonable-sized ways. 264 * 265 * @param w The way to interpolate. 266 * @param l The length at which to place the node. 267 * @return A node at a distance l along w from the first point. 268 */ 269 private Node interpolateAlong(Way w, double l) { 270 List<Pair<Node,Node>> pairs = w.getNodePairs(false); 271 for (int i = 0; i < pairs.size(); ++i) { 272 Pair<Node,Node> p = pairs.get(i); 273 final double seg_length = p.a.getCoor().greatCircleDistance(p.b.getCoor()); 274 if (l <= seg_length || 275 i == pairs.size() - 1) { // be generous on the last segment (numerical roudoff can lead to a small overshoot) 276 return interpolateNode(p.a, p.b, l / seg_length); 277 } else { 278 l -= seg_length; 279 } 280 } 281 // we shouldn't get here 282 throw new IllegalStateException(); 283 } 284 285 /** 286 * Calculates the great circle length of a way by summing the great circle 287 * distance of each pair of nodes. 288 * 289 * @param w The way to calculate length of. 290 * @return The length of the way. 291 */ 292 private double wayLength(Way w) { 293 double length = 0.0; 294 for (Pair<Node, Node> p : w.getNodePairs(false)) { 295 length += p.a.getCoor().greatCircleDistance(p.b.getCoor()); 296 } 297 return length; 298 } 299 300 /** 301 * Given a way, try and find a definite front and back by looking at the 302 * segments to find the "sides". Sides are assumed to be single segments 303 * which cannot be contiguous. 304 * 305 * @param w The way to analyse. 306 * @return A pair of ways (front, back) pointing in the same directions. 307 */ 308 private Pair<Way, Way> findFrontAndBack(Way w) { 309 // calculate the "side-ness" score for each segment of the way 310 double[] sideness = calculateSideness(w); 311 312 // find the largest two sidenesses which are not contiguous 313 int[] indexes = sortedIndexes(sideness); 314 int side1 = indexes[0]; 315 int side2 = indexes[1]; 316 // if side2 is contiguous with side1 then look further down the 317 // list. we know there are at least 4 sides, as anything smaller 318 // than a quadrilateral would have been rejected at an earlier 319 // stage. 320 if (Math.abs(side1 - side2) < 2) { 321 side2 = indexes[2]; 322 } 323 if (Math.abs(side1 - side2) < 2) { 324 side2 = indexes[3]; 325 } 326 327 // if the second side has a shorter length and an approximately equal 328 // sideness then its better to choose the shorter, as with 329 // quadrilaterals 330 // created using the orthogonalise tool the sideness will be about the 331 // same for all sides. 332 if (sideLength(w, side1) > sideLength(w, side1 + 1) 333 && Math.abs(sideness[side1] - sideness[side1 + 1]) < 0.001) { 334 side1 = side1 + 1; 335 side2 = (side2 + 1) % (w.getNodesCount() - 1); 336 } 337 338 // swap side1 and side2 into sorted order. 339 if (side1 > side2) { 340 int tmp = side2; 341 side2 = side1; 342 side1 = tmp; 343 } 344 345 Way front = new Way(); 346 Way back = new Way(); 347 for (int i = side2 + 1; i < w.getNodesCount() - 1; ++i) { 348 front.addNode(w.getNode(i)); 349 } 350 for (int i = 0; i <= side1; ++i) { 351 front.addNode(w.getNode(i)); 352 } 353 // add the back in reverse order so that the front and back ways point 354 // in the same direction. 355 for (int i = side2; i > side1; --i) { 356 back.addNode(w.getNode(i)); 357 } 358 359 return new Pair<Way, Way>(front, back); 360 } 361 362 /** 363 * Calculate the length of a side (from node i to i+1) in a way. This assumes that 364 * the way is closed, but I only ever call it for buildings. 365 */ 366 private double sideLength(Way w, int i) { 367 Node a = w.getNode(i); 368 Node b = w.getNode((i + 1) % (w.getNodesCount() - 1)); 369 return a.getCoor().greatCircleDistance(b.getCoor()); 370 } 371 372 /** 373 * Given an array of doubles (but this could made generic very easily) sort 374 * into order and return the array of indexes such that, for a returned array 375 * x, a[x[i]] is sorted for ascending index i. 376 * 377 * This isn't efficient at all, but should be fine for the small arrays we're 378 * expecting. If this gets slow - replace it with some more efficient algorithm. 379 * 380 * @param a The array to sort. 381 * @return An array of indexes, the same size as the input, such that a[x[i]] 382 * is in sorted order. 383 */ 384 private int[] sortedIndexes(final double[] a) { 385 class SortWithIndex implements Comparable<SortWithIndex> { 386 public double x; 387 public int i; 388 389 public SortWithIndex(double a, int b) { 390 x = a; 391 i = b; 392 } 393 394 public int compareTo(SortWithIndex o) { 395 return Double.compare(x, o.x); 396 }; 397 } 398 399 final int length = a.length; 400 ArrayList<SortWithIndex> sortable = new ArrayList<SortWithIndex>(length); 401 for (int i = 0; i < length; ++i) { 402 sortable.add(new SortWithIndex(a[i], i)); 403 } 404 Collections.sort(sortable); 405 406 int[] indexes = new int[length]; 407 for (int i = 0; i < length; ++i) { 408 indexes[i] = sortable.get(i).i; 409 } 410 411 return indexes; 412 } 413 414 /** 415 * Calculate "sideness" metric for each segment in a way. 416 */ 417 private double[] calculateSideness(Way w) { 418 final int length = w.getNodesCount() - 1; 419 double[] sideness = new double[length]; 420 421 sideness[0] = calculateSideness(w.getNode(length - 1), w.getNode(0), w 422 .getNode(1), w.getNode(2)); 423 for (int i = 1; i < length - 1; ++i) { 424 sideness[i] = calculateSideness(w.getNode(i - 1), w.getNode(i), w 425 .getNode(i + 1), w.getNode(i + 2)); 426 } 427 sideness[length - 1] = calculateSideness(w.getNode(length - 2), w 428 .getNode(length - 1), w.getNode(length), w.getNode(1)); 429 430 return sideness; 431 } 432 433 /** 434 * Calculate sideness of a single segment given the nodes which make up that 435 * segment and its previous and next segments in order. Sideness is calculated 436 * for the segment b-c. 437 */ 438 private double calculateSideness(Node a, Node b, Node c, Node d) { 439 final double ndx = b.getCoor().getX() - a.getCoor().getX(); 440 final double pdx = d.getCoor().getX() - c.getCoor().getX(); 441 final double ndy = b.getCoor().getY() - a.getCoor().getY(); 442 final double pdy = d.getCoor().getY() - c.getCoor().getY(); 443 444 return (ndx * pdx + ndy * pdy) 445 / Math.sqrt((ndx * ndx + ndy * ndy) * (pdx * pdx + pdy * pdy)); 446 } 447 448 /** 449 * Creates a new node at the interpolated position between the argument 450 * nodes. Interpolates linearly in projected coordinates. 451 * 452 * @param a First node, at which f=0. 453 * @param b Last node, at which f=1. 454 * @param f Fractional position between first and last nodes. 455 * @return A new node at the interpolated position. 456 */ 457 private Node interpolateNode(Node a, Node b, double f) { 458 Node n = new Node(a.getEastNorth().interpolate(b.getEastNorth(), f)); 459 return n; 460 } 421 461 } -
applications/editors/josm/plugins/terracer/src/terracer/TerracerPlugin.java
r19483 r19658 19 19 */ 20 20 public class TerracerPlugin extends Plugin { 21 22 23 24 25 26 21 public TerracerPlugin(PluginInformation info) { 22 super(info); 23 24 MainMenu.add(Main.main.menu.toolsMenu, new TerracerAction()); 25 MainMenu.add(Main.main.menu.toolsMenu, new ReverseTerraceAction()); 26 } 27 27 }
Note:
See TracChangeset
for help on using the changeset viewer.