Ignore:
Timestamp:
2010-01-28T11:42:39+01:00 (15 years ago)
Author:
bastik
Message:

josm terracer plugin - some updates and fixes

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  
    1212import java.awt.Choice;
    1313import java.awt.Color;
     14import java.awt.Container;
    1415import java.awt.Dimension;
    1516import java.awt.FlowLayout;
    1617import java.awt.Frame;
     18import java.awt.GridBagLayout;
    1719import java.awt.GridLayout;
    1820import java.util.TreeSet;
     
    2022import javax.swing.BoxLayout;
    2123import javax.swing.JButton;
     24import javax.swing.JCheckBox;
    2225import javax.swing.JDialog;
    2326import javax.swing.JLabel;
     27import javax.swing.JOptionPane;
    2428import javax.swing.JPanel;
    2529import javax.swing.JTextArea;
     
    2832import org.openstreetmap.josm.Main;
    2933import org.openstreetmap.josm.data.osm.OsmPrimitive;
     34import org.openstreetmap.josm.data.osm.Way;
    3035import org.openstreetmap.josm.gui.widgets.AutoCompleteComboBox;
     36import org.openstreetmap.josm.tools.GBC;
     37
    3138
    3239/**
    3340 * The HouseNumberInputDialog is the layout of the house number input logic.
    3441 * Created with the Eclipse Visual Editor.
    35  * 
     42 *
    3643 *  This dialog is concerned with the layout, all logic goes into the
    3744 *  HouseNumberinputHandler class.
    38  * 
     45 *
    3946 * @author casualwalker
    4047 *
    4148 */
    4249public 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    }
    301329}
  • applications/editors/josm/plugins/terracer/src/terracer/HouseNumberInputHandler.java

    r19234 r19658  
    2222import javax.swing.event.ChangeEvent;
    2323import javax.swing.event.ChangeListener;
    24  
     24
     25import org.openstreetmap.josm.Main;
    2526import org.openstreetmap.josm.data.osm.Way;
     27import org.openstreetmap.josm.data.osm.Relation;
    2628
    2729/**
    2830 * The Class HouseNumberInputHandler contains all the logic
    2931 * behind the house number input dialog.
    30  * 
     32 *
    3133 * 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
    3335 * is already cluttered with auto-generated layout code.
    34  * 
     36 *
    3537 * @author casualwalker
    3638 */
    3739public 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    }
    337378}
  • applications/editors/josm/plugins/terracer/src/terracer/ReverseTerraceAction.java

    r18924 r19658  
    3232public class ReverseTerraceAction extends JosmAction {
    3333
    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         }
     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    }
    4444
    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();
     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();
    5252
    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>();
     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>();
    5656
    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>();
     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>();
    6161
    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                 }
     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        }
    7070
    71                 // this is like a visitedWays set, but in a linear order.
    72                 LinkedList<Way> orderedWays = new LinkedList<Way>();
     71        // this is like a visitedWays set, but in a linear order.
     72        LinkedList<Way> orderedWays = new LinkedList<Way>();
    7373
    74                 // and the tags to reverse on the orderedWays.
    75                 LinkedList<String> houseNumbers = new LinkedList<String>();
     74        // and the tags to reverse on the orderedWays.
     75        LinkedList<String> houseNumbers = new LinkedList<String>();
    7676
    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();
     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();
    8080
    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                         }
     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            }
    9393
    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                 }
     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        }
    100100
    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                 }
     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        }
    109109
    110                 Main.main.undoRedo.add(new SequenceCommand(tr("Reverse Terrace"), commands));
    111                 Main.main.getCurrentDataSet().setSelected(orderedWays);
    112         }
     110        Main.main.undoRedo.add(new SequenceCommand(tr("Reverse Terrace"), commands));
     111        Main.main.getCurrentDataSet().setSelected(orderedWays);
     112    }
    113113
    114114}
  • applications/editors/josm/plugins/terracer/src/terracer/TerracerAction.java

    r19085 r19658  
    1616import java.util.Collection;
    1717import java.util.Collections;
     18import java.util.Iterator;
    1819import java.util.LinkedList;
     20import java.util.List;
    1921
    2022import javax.swing.JOptionPane;
     
    2325import org.openstreetmap.josm.actions.JosmAction;
    2426import org.openstreetmap.josm.command.AddCommand;
     27import org.openstreetmap.josm.command.ChangeCommand;
    2528import org.openstreetmap.josm.command.Command;
     29import org.openstreetmap.josm.command.DeleteCommand;
    2630import org.openstreetmap.josm.command.SequenceCommand;
    2731import org.openstreetmap.josm.data.coor.LatLon;
     
    3640/**
    3741 * 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 *
    3946 *
    4047 * At present it only works on quadrilaterals, but there is no reason
     
    4653public final class TerracerAction extends JosmAction {
    4754
    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    }
    421461}
  • applications/editors/josm/plugins/terracer/src/terracer/TerracerPlugin.java

    r19483 r19658  
    1919 */
    2020public class TerracerPlugin extends Plugin {
    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         }
     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    }
    2727}
Note: See TracChangeset for help on using the changeset viewer.