Changeset 36181 in osm


Ignore:
Timestamp:
2023-10-25T18:19:52+02:00 (14 months ago)
Author:
taylor.smock
Message:

Fix #6855: Reverse terracer considers buildings without an address when reversing a terrace

This also fixes several lint issues.

Location:
applications/editors/josm/plugins/terracer
Files:
8 added
5 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/terracer/src/org/openstreetmap/josm/plugins/terracer/HouseNumberInputDialog.java

    r36088 r36181  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    6 import java.awt.Choice;
    76import java.awt.Color;
    87import java.awt.Container;
     
    1110import java.awt.GridBagLayout;
    1211import java.awt.event.ActionEvent;
    13 import java.util.ArrayList;
    1412import java.util.Iterator;
     13import java.util.List;
     14import java.util.Set;
    1515import java.util.TreeSet;
    1616
    1717import javax.swing.BoxLayout;
    1818import javax.swing.JCheckBox;
     19import javax.swing.JComponent;
    1920import javax.swing.JLabel;
    2021import javax.swing.JPanel;
     
    3233import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
    3334import org.openstreetmap.josm.gui.util.WindowGeometry;
     35import org.openstreetmap.josm.gui.widgets.JosmComboBox;
    3436import org.openstreetmap.josm.spi.preferences.Config;
    3537import org.openstreetmap.josm.tools.GBC;
     
    3739/**
    3840 * The HouseNumberInputDialog is the layout of the house number input logic.
    39  *
     41 * <p>
    4042 *  This dialog is concerned with the layout, all logic goes into the
    4143 *  HouseNumberinputHandler class.
     
    5860    private final String buildingType;
    5961    private final boolean relationExists;
    60     final ArrayList<Node> housenumbers;
     62    final List<Node> houseNumbers;
    6163
    6264    protected static final String DEFAULT_MESSAGE = tr("Enter housenumbers or amount of segments");
    6365    private Container jContentPane;
    6466    private JPanel inputPanel;
    65     private JLabel loLabel;
    6667    JTextField lo;
    67     private JLabel hiLabel;
    6868    JTextField hi;
    6969    private JLabel numbersLabel;
    7070    JTextField numbers;
    71     private JLabel streetLabel;
    7271    AutoCompComboBox<String> streetComboBox;
    73     private JLabel buildingLabel;
    7472    AutoCompComboBox<AutoCompletionItem> buildingComboBox;
    75     private JLabel segmentsLabel;
    7673    JTextField segments;
    7774    JTextArea messageLabel;
    78     private JLabel interpolationLabel;
    79     Choice interpolation;
     75    JosmComboBox<String> interpolationType;
    8076    JCheckBox handleRelationCheckBox;
    8177    JCheckBox keepOutlineCheckBox;
     
    8480
    8581    /**
     82     * Create a new dialog to get settings for the current operation
    8683     * @param street If street is not null, we assume, the name of the street to be fixed
    8784     * and just show a label. If street is null, we show a ComboBox/InputField.
     
    9188     * @param buildingType The value to add for building key
    9289     * @param relationExists If the buildings can be added to an existing relation or not.
    93      * @param housenumbers a list of house numbers in this outline (may be empty)
     90     * @param houseNumbers a list of house numbers in this outline (may be empty)
     91     * @param handler The callback for the inputs
    9492     */
    9593    public HouseNumberInputDialog(HouseNumberInputHandler handler, Way street, String streetName,
    96             String buildingType, boolean relationExists, ArrayList<Node> housenumbers) {
     94            String buildingType, boolean relationExists, List<Node> houseNumbers) {
    9795        super(MainApplication.getMainFrame(),
    9896                tr("Terrace a house"),
     
    105103        this.buildingType = buildingType;
    106104        this.relationExists = relationExists;
    107         this.housenumbers = housenumbers;
     105        this.houseNumbers = houseNumbers;
    108106        handler.dialog = this;
    109107        JPanel content = getInputPanel();
    110108        setContent(content);
    111         setButtonIcons(new String[] {"ok", "cancel" });
     109        setButtonIcons("ok", "cancel");
    112110        getJContentPane();
    113111        initialize();
     
    128126        this.hi.addFocusListener(this.inputHandler);
    129127        this.segments.addFocusListener(this.inputHandler);
    130         this.interpolation.addItemListener(this.inputHandler);
     128        this.interpolationType.addItemListener(this.inputHandler);
    131129    }
    132130
     
    165163            messageLabel.setFocusable(false); // Needed so that lowest number can have focus immediately
    166164
    167             interpolationLabel = new JLabel(tr("Interpolation"));
    168             segmentsLabel = new JLabel(tr("Segments"));
    169             streetLabel = new JLabel(tr("Street"));
    170             buildingLabel = new JLabel(tr("Building"));
    171             loLabel = new JLabel(tr("Lowest Number"));
     165            JLabel interpolationLabel = new JLabel(tr("Interpolation"));
     166            JLabel segmentsLabel = new JLabel(tr("Segments"));
     167            JLabel streetLabel = new JLabel(tr("Street"));
     168            JLabel buildingLabel = new JLabel(tr("Building"));
     169            JLabel loLabel = new JLabel(tr("Lowest Number"));
    172170            loLabel.setPreferredSize(new Dimension(111, 16));
    173171            loLabel.setToolTipText(tr("Lowest housenumber of the terraced house"));
    174             hiLabel = new JLabel(tr("Highest Number"));
     172            JLabel hiLabel = new JLabel(tr("Highest Number"));
    175173            numbersLabel = new JLabel(tr("List of Numbers"));
    176174            loLabel.setPreferredSize(new Dimension(111, 16));
    177175            final String txt = relationExists ? tr("add to existing associatedStreet relation") : tr("create an associatedStreet relation");
    178176
    179             handleRelationCheckBox = new JCheckBox(txt, relationExists ? Config.getPref().getBoolean(HANDLE_RELATION, true) : false);
     177            handleRelationCheckBox = new JCheckBox(txt, relationExists && Config.getPref().getBoolean(HANDLE_RELATION, true));
    180178            keepOutlineCheckBox = new JCheckBox(tr("keep outline way"), Config.getPref().getBoolean(KEEP_OUTLINE, false));
    181179
     
    187185
    188186            inputPanel.add(loLabel, GBC.std().insets(3, 3, 0, 0));
    189             inputPanel.add(getLo(), GBC.eol().fill(GBC.HORIZONTAL).insets(5, 3, 0, 0));
     187            inputPanel.add(getLo(), GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(5, 3, 0, 0));
    190188            inputPanel.add(hiLabel, GBC.std().insets(3, 3, 0, 0));
    191             inputPanel.add(getHi(), GBC.eol().fill(GBC.HORIZONTAL).insets(5, 3, 0, 0));
     189            inputPanel.add(getHi(), GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(5, 3, 0, 0));
    192190            inputPanel.add(numbersLabel, GBC.std().insets(3, 3, 0, 0));
    193             inputPanel.add(getNumbers(), GBC.eol().fill(GBC.HORIZONTAL).insets(5, 3, 0, 0));
     191            inputPanel.add(getNumbers(), GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(5, 3, 0, 0));
    194192            inputPanel.add(interpolationLabel, GBC.std().insets(3, 3, 0, 0));
    195193            inputPanel.add(getInterpolation(), GBC.eol().insets(5, 3, 0, 0));
    196194            inputPanel.add(segmentsLabel, GBC.std().insets(3, 3, 0, 0));
    197             inputPanel.add(getSegments(), GBC.eol().fill(GBC.HORIZONTAL).insets(5, 3, 0, 0));
     195            inputPanel.add(getSegments(), GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(5, 3, 0, 0));
    198196            if (streetName == null) {
    199197                inputPanel.add(streetLabel, GBC.std().insets(3, 3, 0, 0));
     
    215213                hi.setEnabled(false);
    216214                interpolationLabel.setVisible(false);
    217                 interpolation.setVisible(false);
    218                 interpolation.setEnabled(false);
    219                 segments.setText(String.valueOf(housenumbers.size()));
     215                interpolationType.setVisible(false);
     216                interpolationType.setEnabled(false);
     217                segments.setText(String.valueOf(houseNumbers.size()));
    220218                segments.setEditable(false);
    221219            }
     
    267265            numbers = new JTextField();
    268266
    269             Iterator<Node> it = housenumbers.iterator();
     267            Iterator<Node> it = houseNumbers.iterator();
    270268            StringBuilder s = new StringBuilder(256);
    271269            if (it.hasNext()) {
     
    338336     * @return java.awt.Choice
    339337     */
    340     private Choice getInterpolation() {
    341         if (interpolation == null) {
    342             interpolation = new Choice();
    343             interpolation.add(tr("All"));
    344             interpolation.add(tr("Even/Odd"));
     338    private JComponent getInterpolation() {
     339        if (interpolationType == null) {
     340            interpolationType = new JosmComboBox<>();
     341            interpolationType.setEditable(false);
     342            interpolationType.addItem(tr("All"));
     343            interpolationType.addItem(tr("Even/Odd"));
    345344            if (Config.getPref().getInt(INTERPOLATION, 2) == 1) {
    346                 interpolation.select(tr("All"));
     345                interpolationType.setSelectedItemText(tr("All"));
    347346            } else {
    348                 interpolation.select(tr("Even/Odd"));
    349             }
    350         }
    351         return interpolation;
     347                interpolationType.setSelectedItemText(tr("Even/Odd"));
     348            }
     349        }
     350        return interpolationType;
    352351    }
    353352
     
    355354     * Generates a list of all visible names of highways in order to do
    356355     * autocompletion on the road name.
    357      */
    358     TreeSet<String> createAutoCompletionInfo() {
     356     * @return The visible names
     357     */
     358    Set<String> createAutoCompletionInfo() {
    359359        final TreeSet<String> names = new TreeSet<>();
    360360        for (OsmPrimitive osm : MainApplication.getLayerManager().getEditDataSet()
  • applications/editors/josm/plugins/terracer/src/org/openstreetmap/josm/plugins/terracer/HouseNumberInputHandler.java

    r35827 r36181  
    88import java.awt.Container;
    99import java.awt.event.ActionEvent;
    10 import java.awt.event.ActionListener;
    1110import java.awt.event.FocusEvent;
    1211import java.awt.event.FocusListener;
    1312import java.awt.event.ItemEvent;
    1413import java.awt.event.ItemListener;
    15 import java.util.ArrayList;
     14import java.util.List;
    1615
    1716import javax.swing.JButton;
    18 import javax.swing.JOptionPane;
    1917import javax.swing.JTextField;
    2018
     
    2624import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox;
    2725import org.openstreetmap.josm.spi.preferences.Config;
    28 import org.openstreetmap.josm.tools.Logging;
    29 import org.openstreetmap.josm.tools.UserCancelException;
    3026import org.openstreetmap.josm.tools.Utils;
    3127
     
    3329 * The Class HouseNumberInputHandler contains all the logic
    3430 * behind the house number input dialog.
    35  *
     31 * <p>
    3632 * From a refactoring viewpoint, this class is indeed more interested in the fields
    3733 * of the HouseNumberInputDialog. This is desired design, as the HouseNumberInputDialog
     
    4036 * @author casualwalker - Copyright 2009 CloudMade Ltd
    4137 */
    42 public class HouseNumberInputHandler extends JosmAction implements ActionListener, FocusListener, ItemListener {
     38public class HouseNumberInputHandler extends JosmAction implements FocusListener, ItemListener {
    4339    private final TerracerAction terracerAction;
    44     private final Way outline, street;
     40    private final Way outline;
     41    private final Way street;
    4542    private final String streetName;
    4643    private final Node init;
    4744    private final Relation associatedStreet;
    48     private final ArrayList<Node> housenumbers;
    49     public HouseNumberInputDialog dialog;
     45    private final List<Node> housenumbers;
     46    HouseNumberInputDialog dialog;
    5047
    5148    /**
     
    6764            final Way outline, final Node init, final Way street, final String streetName, final String buildingType,
    6865            final Relation associatedStreet,
    69             final ArrayList<Node> housenumbers, final String title) {
     66            final List<Node> housenumbers, final String title) {
    7067        this.terracerAction = terracerAction;
    7168        this.outline = outline;
     
    109106     * When the validation fails, a red message is
    110107     * displayed and the OK button is disabled.
    111      *
     108     * <p>
    112109     * Should be triggered each time the input changes.
     110     * @return {@code true} if the inputs are ok
    113111     */
    114112    private boolean validateInput() {
    115113        boolean isOk = true;
    116         StringBuffer message = new StringBuffer();
    117 
    118         isOk = isOk && checkNumberOrder(message);
    119         isOk = isOk && checkSegmentsFromHousenumber(message);
    120         isOk = isOk && checkSegments(message);
     114        final StringBuilder message = new StringBuilder();
     115
     116        isOk &= checkNumberOrder(message);
     117        isOk &= checkSegmentsFromHousenumber(message);
     118        isOk &= checkSegments(message);
    121119
    122120        // Allow non numeric characters for the low number as long as there is
    123121        // no high number of the segmentcount is 1
    124122        if (dialog.hi.getText().length() > 0 && (segments() != null || segments() < 1)) {
    125             isOk = isOk
    126                     && checkNumberStringField(dialog.lo, tr("Lowest number"),
     123            isOk &= checkNumberStringField(dialog.lo, tr("Lowest number"),
    127124                            message);
    128125        }
    129         isOk = isOk
    130                 && checkNumberStringField(dialog.hi, tr("Highest number"),
     126        isOk &= checkNumberStringField(dialog.hi, tr("Highest number"),
    131127                        message);
    132         isOk = isOk
    133                 && checkNumberStringField(dialog.segments, tr("Segments"),
     128        isOk &= checkNumberStringField(dialog.segments, tr("Segments"),
    134129                        message);
    135130
     131        JButton okButton = getButton(dialog, "OK");
     132        if (okButton != null)
     133            okButton.setEnabled(isOk);
    136134        if (isOk) {
    137             JButton okButton = getButton(dialog, "OK");
    138             if (okButton != null)
    139                 okButton.setEnabled(true);
    140135
    141136            // For some reason the messageLabel doesn't want to show up
     
    144139            return true;
    145140        } else {
    146             JButton okButton = getButton(dialog, "OK");
    147             if (okButton != null)
    148                 okButton.setEnabled(false);
    149 
    150141            // For some reason the messageLabel doesn't want to show up, so a
    151142            // MessageDialog is shown instead. Someone more knowledgeable might fix this.
     
    168159     * @return true, if successful
    169160     */
    170     private boolean checkNumberOrder(final StringBuffer message) {
    171         if (numberFrom() != null && numberTo() != null) {
    172             if (numberFrom().intValue() > numberTo().intValue()) {
    173                 appendMessageNewLine(message);
    174                 message.append(tr("Lowest housenumber cannot be higher than highest housenumber"));
    175                 return false;
    176             }
     161    private boolean checkNumberOrder(final StringBuilder message) {
     162        if (numberFrom() != null && numberTo() != null && numberFrom() > numberTo()) {
     163            appendMessageNewLine(message);
     164            message.append(tr("Lowest housenumber cannot be higher than highest housenumber"));
     165            return false;
    177166        }
    178167        return true;
     
    182171     * Obtain the number segments from the house number fields and check,
    183172     * if they are valid.
    184      *
     173     * <p>
    185174     * Also disables the segments field, if the house numbers contain
    186175     * valid information.
     
    190179     * @return true, if successful
    191180     */
    192     private boolean checkSegmentsFromHousenumber(final StringBuffer message) {
     181    private boolean checkSegmentsFromHousenumber(final StringBuilder message) {
    193182        if (!dialog.numbers.isVisible()) {
    194183            dialog.segments.setEditable(true);
    195184
    196185            if (numberFrom() != null && numberTo() != null) {
    197                 int segments = numberTo().intValue() - numberFrom().intValue();
     186                int segments = numberTo() - numberFrom();
    198187
    199188                if (segments % stepSize() != 0) {
     
    222211     * @return true, if successful
    223212     */
    224     private boolean checkSegments(final StringBuffer message) {
    225         if (segments() == null || segments().intValue() < 1) {
     213    private boolean checkSegments(final StringBuilder message) {
     214        if (segments() == null || segments() < 1) {
    226215            appendMessageNewLine(message);
    227216            message.append(tr("Segment must be a number greater 1"));
     
    241230     * @return true, if successful
    242231     */
    243     private boolean checkNumberStringField(final JTextField field,
    244             final String label, final StringBuffer message) {
     232    private static boolean checkNumberStringField(final JTextField field,
     233            final String label, final StringBuilder message) {
    245234        final String content = field.getText();
    246235        if (content != null && content.length() != 0) {
     
    267256     * @param message the message
    268257     */
    269     private void appendMessageNewLine(final StringBuffer message) {
     258    private static void appendMessageNewLine(final StringBuilder message) {
    270259        if (message.length() > 0) {
    271260            message.append("\n");
     
    287276                    saveValues();
    288277
    289                     try {
    290                         terracerAction.terraceBuilding(
    291                             outline,
    292                             init,
    293                             street,
    294                             associatedStreet,
    295                             segments(),
    296                             dialog.lo.getText(),
    297                             dialog.hi.getText(),
    298                             stepSize(),
    299                             housenumbers,
    300                             streetName(),
    301                             doHandleRelation(),
    302                             doKeepOutline(), buildingType());
    303                     } catch (UserCancelException ex) {
    304                         Logging.trace(ex);
    305                     }
     278                    terracerAction.terraceBuilding(
     279                        outline,
     280                        init,
     281                        street,
     282                        associatedStreet,
     283                        segments(),
     284                        dialog.lo.getText(),
     285                        dialog.hi.getText(),
     286                        stepSize(),
     287                        housenumbers,
     288                        streetName(),
     289                        doHandleRelation(),
     290                        doKeepOutline(), buildingType());
    306291
    307292                    this.dialog.setVisible(false);
     
    323308     */
    324309    public Integer stepSize() {
    325         return dialog.interpolation.getSelectedItem().equals(tr("All")) ? 1 : 2;
     310        return tr("All").equals(dialog.interpolationType.getSelectedItem()) ? 1 : 2;
    326311    }
    327312
     
    405390     * Whether the user likes to create a relation or add to
    406391     * an existing one.
     392     * @return {@code true} if the user wants to create a relation
    407393     */
    408394    public boolean doHandleRelation() {
    409         if (this.dialog == null) {
    410             JOptionPane.showMessageDialog(null, "dialog", "alert", JOptionPane.ERROR_MESSAGE);
    411         }
    412         if (this.dialog.handleRelationCheckBox == null) {
    413             JOptionPane.showMessageDialog(null, "checkbox", "alert", JOptionPane.ERROR_MESSAGE);
    414             return true;
    415         } else {
    416             return this.dialog.handleRelationCheckBox.isSelected();
    417         }
     395        return this.dialog.handleRelationCheckBox.isSelected();
    418396    }
    419397
    420398    /**
    421399     * Whether the user likes to keep the outline way.
     400     * @return {@code true} if the user wants to keep the selected outline
    422401     */
    423402    public boolean doKeepOutline() {
  • applications/editors/josm/plugins/terracer/src/org/openstreetmap/josm/plugins/terracer/ReverseTerraceAction.java

    r35579 r36181  
    66import java.awt.event.ActionEvent;
    77import java.awt.event.KeyEvent;
     8import java.util.ArrayDeque;
    89import java.util.Collection;
    910import java.util.Collections;
     11import java.util.Deque;
    1012import java.util.HashSet;
    1113import java.util.LinkedList;
     14import java.util.Set;
    1215
    1316import javax.swing.JOptionPane;
     
    2629/**
    2730 * Tool to reverse the house numbers in a terrace.
    28  *
     31 * <p>
    2932 * Useful for when you're using the Terracer tool and the house numbers come out
    3033 * in the wrong direction, or when someone has added house numbers in the wrong
    3134 * direction anyway.
    32  *
    33  * Finds all connected ways which have a building=* tag on them in order (breadth
     35 * <p>
     36 * Finds all connected ways which have a building=* and addr tag on them in order (breadth
    3437 * first search) and then changes the tags to be the reverse of the order in which
    3538 * they were found.
    3639 */
    3740public class ReverseTerraceAction extends JosmAction {
     41    private static final String ADDR_HOUSENUMBER = "addr:housenumber";
    3842
     43    /**
     44     * Create a new action for reversing a terrace
     45     */
    3946    public ReverseTerraceAction() {
    4047        super(tr("Reverse a terrace"),
     
    5461    public void actionPerformed(ActionEvent e) {
    5562        Collection<Way> selectedWays = MainApplication.getLayerManager().getEditDataSet().getSelectedWays();
     63        reverseTerracedAddresses(selectedWays);
     64    }
    5665
     66    static void reverseTerracedAddresses(Collection<Way> selectedWays) {
    5767        // Set to keep track of all the nodes that have been visited - that is: if
    5868        // we encounter them again we will not follow onto the connected ways.
    59         HashSet<Node> visitedNodes = new HashSet<>();
     69        Set<Node> visitedNodes = new HashSet<>();
    6070
    6171        // Set to keep track of the ways the algorithm has seen, but not yet visited.
    6272        // Since when a way is visited all of its nodes are marked as visited, there
    6373        // is no need to keep a visitedWays set.
    64         HashSet<Way> front = new HashSet<>();
    65 
    66         // Find the first or last way from the teracced houses.
    67         // It should be connected to exactly one other way.
    68         for (Way w : selectedWays) {
    69             int conn = 0;
    70             for (Way v : selectedWays) {
    71                 if (w.equals(v)) continue;
    72                 if (!Collections.disjoint(w.getNodes(), v.getNodes())) {
    73                     ++conn;
    74                 }
    75             }
    76             if (conn == 1) {
    77                 front.add(w);
    78                 break;
    79             }
    80         }
     74        final Deque<Way> front = findFirstWay(selectedWays);
    8175
    8276        if (front.isEmpty()) {
     
    8680        }
    8781
     82
    8883        // This is like a visitedWays set, but in a linear order.
    8984        LinkedList<Way> orderedWays = new LinkedList<>();
     
    9287        LinkedList<String> houseNumbers = new LinkedList<>();
    9388
    94         while (front.size() > 0) {
     89        while (!front.isEmpty()) {
    9590            // Java apparently doesn't have useful methods to get single items from sets...
    96             Way w = front.iterator().next();
     91            Way w = front.pop();
    9792
    9893            // Visit all the nodes in the way, adding the building's they're members of
     
    10196                if (!visitedNodes.contains(n)) {
    10297                    for (OsmPrimitive prim : n.getReferrers()) {
    103                         if (prim.keySet().contains("building") && prim instanceof Way) {
     98                        if (prim.hasKey("building") && prim.hasKey(ADDR_HOUSENUMBER)
     99                                && prim instanceof Way && !front.contains(prim)) {
    104100                            front.add((Way) prim);
    105101                        }
     
    111107            // We've finished visiting this way, so record the attributes we're interested
    112108            // in for re-writing.
    113             front.remove(w);
    114109            orderedWays.addLast(w);
    115             houseNumbers.addFirst(w.get("addr:housenumber"));
     110            houseNumbers.addFirst(w.get(ADDR_HOUSENUMBER));
    116111        }
    117112
     
    120115            commands.add(new ChangePropertyCommand(
    121116                    orderedWays.get(i),
    122                     "addr:housenumber",
     117                    ADDR_HOUSENUMBER,
    123118                    houseNumbers.get(i)));
    124119        }
     
    128123    }
    129124
     125    private static Deque<Way> findFirstWay(Collection<Way> selectedWays) {
     126        // Find the first or last way from the terraced houses.
     127        // It should be connected to exactly one other way.
     128        for (Way w : selectedWays) {
     129            int conn = 0;
     130            for (Way v : selectedWays) {
     131                if (!w.equals(v) && !Collections.disjoint(w.getNodes(), v.getNodes())) {
     132                    ++conn;
     133                    if (conn > 1) {
     134                        break;
     135                    }
     136                }
     137            }
     138            if (conn == 1) {
     139                return new ArrayDeque<>(Collections.singletonList(w));
     140            }
     141        }
     142        return new ArrayDeque<>();
     143    }
     144
    130145    @Override
    131146    protected void updateEnabledState() {
  • applications/editors/josm/plugins/terracer/src/org/openstreetmap/josm/plugins/terracer/TerracerAction.java

    r36088 r36181  
    1414import java.util.HashMap;
    1515import java.util.HashSet;
    16 import java.util.Iterator;
    1716import java.util.LinkedList;
    1817import java.util.List;
     
    5554 * a street (highway=*, name=*) then the given street will be added
    5655 * to the 'associatedStreet' relation.
    57  *
     56 * <p>
    5857 *
    5958 * At present it only works on quadrilaterals, but there is no reason
     
    6463 */
    6564public final class TerracerAction extends JosmAction {
     65    private static final String BUILDING = "building";
     66    private static final String ADDR_HOUSENUMBER = "addr:housenumber";
     67    private static final String ADDR_STREET = "addr:street";
    6668
    6769    private Collection<Command> commands;
     
    123125            } else if (sel.size() > 1) {
    124126                List<Way> ways = new ArrayList<>(Utils.filteredCollection(sel, Way.class));
    125                 Iterator<Way> wit = ways.iterator();
    126                 while (wit.hasNext()) {
    127                     Way way = wit.next();
    128                     if (way.hasKey("building")) {
     127                for (Way way : ways) {
     128                    if (way.hasKey(BUILDING)) {
    129129                        if (outline != null)
    130130                            // already have a building
     
    140140                            throw new InvalidUserInputException("street does not have any name");
    141141                    } else
    142                         throw new InvalidUserInputException(way+" is neither a building nor a highway");
     142                        throw new InvalidUserInputException(way + " is neither a building nor a highway");
    143143                }
    144144
     
    147147
    148148                List<Node> nodes = new ArrayList<>(Utils.filteredCollection(sel, Node.class));
    149                 Iterator<Node> nit = nodes.iterator();
    150149                // Actually this should test if the selected address nodes lie
    151150                // within the selected outline. Any ideas how to do this?
    152                 while (nit.hasNext()) {
    153                     Node node = nit.next();
    154                     if (node.hasKey("addr:housenumber")) {
    155                         String nodesStreetName = node.get("addr:street");
     151                for (Node node : nodes) {
     152                    if (node.hasKey(ADDR_HOUSENUMBER)) {
     153                        String nodesStreetName = node.get(ADDR_STREET);
    156154                        // if a node has a street name if must be equal
    157155                        // to the one of the other address nodes
     
    174172                }
    175173
    176                 Collections.sort(housenumbers, new HousenumberNodeComparator());
     174                housenumbers.sort(new HousenumberNodeComparator());
    177175            }
    178176
     
    182180        } catch (InvalidUserInputException ex) {
    183181            Logging.warn("Terracer: "+ex.getMessage());
    184             new ExtendedDialog(MainApplication.getMainFrame(), tr("Invalid selection"), new String[] {"OK"})
    185                 .setButtonIcons(new String[] {"ok"}).setIcon(JOptionPane.INFORMATION_MESSAGE)
     182            new ExtendedDialog(MainApplication.getMainFrame(), tr("Invalid selection"), "OK")
     183                .setButtonIcons("ok").setIcon(JOptionPane.INFORMATION_MESSAGE)
    186184                .setContent(tr("Select a single, closed way of at least four nodes. " +
    187185                    "(Optionally you can also select a street for the addr:street tag " +
     
    217215            // Special case of one outline and one address node.
    218216            // Don't open the dialog
    219             try {
    220                 terraceBuilding(outline, init, street, associatedStreet, 0, null, null, 0,
    221                         housenumbers, streetname, associatedStreet != null, false, "yes");
    222             } catch (UserCancelException ex) {
    223                 Logging.trace(ex);
    224             }
     217            terraceBuilding(outline, init, street, associatedStreet, 0, null, null, 0,
     218                    housenumbers, streetname, associatedStreet != null, false, "yes");
    225219        } else {
    226220            String title = trn("Change {0} object", "Change {0} objects", sel.size(), sel.size());
    227221            // show input dialog.
    228             new HouseNumberInputHandler(this, outline, init, street, streetname, outline.get("building"),
     222            new HouseNumberInputHandler(this, outline, init, street, streetname, outline.get(BUILDING),
    229223                    associatedStreet, housenumbers, title).dialog.showDialog();
    230224        }
     
    238232    }
    239233
    240     public Integer getNumber(String number) {
     234    private static Integer getNumber(String number) {
    241235        try {
    242236            return Integer.parseInt(number);
     
    251245     */
    252246    static class HousenumberNodeComparator implements Comparator<Node> {
    253         private final Pattern pat = Pattern.compile("^(\\d+)\\s*(.*)");
     247        private static final Pattern PATTERN_HOUSE_NUMBER = Pattern.compile("^(\\d+)\\s*(.*)", Pattern.UNICODE_CHARACTER_CLASS);
    254248
    255249        @Override
     
    259253            // doesn't work for numbers with different number of digits,
    260254            // e.g. 9 is higher than 11
    261             String node1String = node1.get("addr:housenumber");
    262             String node2String = node2.get("addr:housenumber");
    263             Matcher mat = pat.matcher(node1String);
     255            String node1String = node1.get(ADDR_HOUSENUMBER);
     256            String node2String = node2.get(ADDR_HOUSENUMBER);
     257            Matcher mat = PATTERN_HOUSE_NUMBER.matcher(node1String);
    264258            if (mat.find()) {
    265259                Integer node1Int = Integer.valueOf(mat.group(1));
    266260                String node1Rest = mat.group(2);
    267                 mat = pat.matcher(node2String);
     261                mat = PATTERN_HOUSE_NUMBER.matcher(node2String);
    268262                if (mat.find()) {
    269263                    Integer node2Int = Integer.valueOf(mat.group(1));
     
    285279    /**
    286280     * Terraces a single, closed, quadrilateral way.
    287      *
     281     * <p>
    288282     * Any node must be adjacent to both a short and long edge, we naively
    289283     * choose the longest edge and its opposite and interpolate along them
     
    307301     * @param keepOutline If the outline way should be kept
    308302     * @param buildingValue The value for {@code building} key to add
    309      * @throws UserCancelException if user cancels the operation
    310303     */
    311304    public void terraceBuilding(final Way outline, Node init, Way street, Relation associatedStreet, Integer segments,
    312305                String start, String end, int step, List<Node> housenumbers, String streetName, boolean handleRelations,
    313                 boolean keepOutline, String buildingValue) throws UserCancelException {
     306                boolean keepOutline, String buildingValue) {
    314307        final int nb;
    315         Integer to = null, from = null;
     308        Integer to;
     309        Integer from = null;
    316310        if (housenumbers == null || housenumbers.isEmpty()) {
    317311            to = getNumber(end);
    318312            from = getNumber(start);
    319313            if (to != null && from != null) {
    320                 nb = 1 + (to.intValue() - from.intValue()) / step;
     314                nb = 1 + (to - from) / step;
    321315            } else if (segments != null) {
    322                 nb = segments.intValue();
     316                nb = segments;
    323317            } else {
    324318                // if we get here, there is is a bug in the input validation.
     
    335329        Pair<Way, Way> interp = findFrontAndBack(outline);
    336330
    337         final boolean swap = init != null && (interp.a.lastNode().equals(init) || interp.b.lastNode().equals(init));
     331        final boolean swap = init != null && (init.equals(interp.a.lastNode()) || init.equals(interp.b.lastNode()));
    338332
    339333        final double frontLength = wayLength(interp.a);
     
    418412        // Remove the address nodes since their tags have been incorporated into the terraces.
    419413        // Or should removing them also be an option?
    420         if (!housenumbers.isEmpty()) {
     414        if (housenumbers != null && !housenumbers.isEmpty()) {
    421415            commands.add(DeleteCommand.delete(housenumbers, true, true));
    422416        }
     
    511505     * @param associatedStreet The associated street. Used to determine if addr:street should be set or not.
    512506     * @param buildingValue The value for {@code building} key to add
    513      * @throws UserCancelException if user cancels the operation
     507     * @param houseNumbers The house numbers to use
     508     * @param i The index to use in {@code houseNumbers} for a replacement house number (preferential)
     509     * @param defaultNumber The number to use if there was not an underlying house number
    514510     */
    515511    private void addressBuilding(Way outline, Way street, String streetName, Relation associatedStreet,
    516             List<Node> housenumbers, int i, String defaultNumber, String buildingValue) throws UserCancelException {
    517         Node houseNum = (housenumbers != null && i >= 0 && i < housenumbers.size()) ? housenumbers.get(i) : null;
     512            List<Node> houseNumbers, int i, String defaultNumber, String buildingValue) {
     513        Node houseNum = (houseNumbers != null && i >= 0 && i < houseNumbers.size()) ? houseNumbers.get(i) : null;
    518514        boolean buildingAdded = false;
    519515        boolean numberAdded = false;
    520516        Map<String, String> tags = new HashMap<>();
    521517        if (houseNum != null) {
    522             primitives = Arrays.asList(new OsmPrimitive[]{houseNum, outline});
     518            primitives = Arrays.asList(houseNum, outline);
    523519
    524520            TagCollection tagsToCopy = TagCollection.unionOfAllPrimitives(primitives).getTagsFor(houseNum.keySet());
     
    530526            }
    531527
    532             buildingAdded = houseNum.hasKey("building");
    533             numberAdded = houseNum.hasKey("addr:housenumber");
     528            buildingAdded = houseNum.hasKey(BUILDING);
     529            numberAdded = houseNum.hasKey(ADDR_HOUSENUMBER);
    534530        }
    535531        if (!buildingAdded && buildingValue != null && !buildingValue.isEmpty()) {
    536             tags.put("building", buildingValue);
     532            tags.put(BUILDING, buildingValue);
    537533        }
    538534        if (defaultNumber != null && !numberAdded) {
    539             tags.put("addr:housenumber", defaultNumber);
     535            tags.put(ADDR_HOUSENUMBER, defaultNumber);
    540536        }
    541537        // Only put addr:street if no relation exists or if it has no name
    542538        if (associatedStreet == null || !associatedStreet.hasKey("name")) {
    543539            if (street != null) {
    544                 tags.put("addr:street", street.get("name"));
     540                tags.put(ADDR_STREET, street.get("name"));
    545541            } else if (streetName != null && !streetName.trim().isEmpty()) {
    546                 tags.put("addr:street", streetName.trim());
     542                tags.put(ADDR_STREET, streetName.trim());
    547543            }
    548544        }
     
    555551     * Creates a node at a certain distance along a way, as calculated by the
    556552     * great circle distance.
    557      *
     553     * <p>
    558554     * Note that this really isn't an efficient way to do this and leads to
    559555     * O(N^2) running time for the main algorithm, but its simple and easy
     
    564560     * @return A node at a distance l along w from the first point.
    565561     */
    566     private Node interpolateAlong(Way w, double l) {
     562    private static Node interpolateAlong(Way w, double l) {
    567563        List<Pair<Node, Node>> pairs = w.getNodePairs(false);
    568564        for (int i = 0; i < pairs.size(); ++i) {
     
    587583     * @return The length of the way.
    588584     */
    589     private double wayLength(Way w) {
     585    private static double wayLength(Way w) {
    590586        double length = 0.0;
    591587        for (Pair<Node, Node> p : w.getNodePairs(false)) {
     
    603599     * @return A pair of ways (front, back) pointing in the same directions.
    604600     */
    605     private Pair<Way, Way> findFrontAndBack(Way w) {
     601    private static Pair<Way, Way> findFrontAndBack(Way w) {
    606602        // calculate the "side-ness" score for each segment of the way
    607603        double[] sideness = calculateSideness(w);
     
    658654    /**
    659655     * returns the distance of two segments of a closed polygon
    660      */
    661     private int indexDistance(int i1, int i2, int n) {
     656     * @param i1 The first segment index
     657     * @param i2 The second segment index
     658     * @param n The number of segments in the polygon
     659     * @return The distance between the two segments
     660     */
     661    private static int indexDistance(int i1, int i2, int n) {
    662662        return Math.min(positiveModulus(i1 - i2, n), positiveModulus(i2 - i1, n));
    663663    }
     
    665665    /**
    666666     * return the modulus in the range [0, n)
    667      */
    668     private int positiveModulus(int a, int n) {
     667     * @param a dividend
     668     * @param n divisor
     669     * @return The positive modulus (if {@code a} is negative)
     670     */
     671    private static int positiveModulus(int a, int n) {
    669672        if (n <= 0)
    670673            throw new IllegalArgumentException();
     
    679682     * Calculate the length of a side (from node i to i+1) in a way. This assumes that
    680683     * the way is closed, but I only ever call it for buildings.
    681      */
    682     private double sideLength(Way w, int i) {
     684     * @param w The way
     685     * @param i The side (0 indexed)
     686     * @return The length of that way segment
     687     */
     688    private static double sideLength(Way w, int i) {
    683689        Node a = w.getNode(i);
    684690        Node b = w.getNode((i + 1) % (w.getNodesCount() - 1));
     
    690696     * into order and return the array of indexes such that, for a returned array
    691697     * x, a[x[i]] is sorted for ascending index i.
    692      *
     698     * <p>
    693699     * This isn't efficient at all, but should be fine for the small arrays we're
    694700     * expecting. If this gets slow - replace it with some more efficient algorithm.
     
    698704     * is in sorted order.
    699705     */
    700     private int[] sortedIndexes(final double[] a) {
    701         class SortWithIndex implements Comparable<SortWithIndex> {
    702             public double x;
    703             public int i;
    704 
    705             SortWithIndex(double a, int b) {
    706                 x = a;
    707                 i = b;
    708             }
    709 
    710             @Override
    711             public int compareTo(SortWithIndex o) {
    712                 return Double.compare(x, o.x);
    713             }
    714         }
     706    private static int[] sortedIndexes(final double[] a) {
    715707
    716708        final int length = a.length;
     
    731723    /**
    732724     * Calculate "sideness" metric for each segment in a way.
    733      */
    734     private double[] calculateSideness(Way w) {
     725     * @param w The way to get the sideness metric for
     726     * @return The sideness for each segment of the way
     727     */
     728    private static double[] calculateSideness(Way w) {
    735729        final int length = w.getNodesCount() - 1;
    736730        double[] sideness = new double[length];
     
    752746     * segment and its previous and next segments in order. Sideness is calculated
    753747     * for the segment b-c.
    754      */
    755     private double calculateSideness(Node a, Node b, Node c, Node d) {
     748     * @param a The previous node
     749     * @param b The first node of the current segment
     750     * @param c The last node of the current segment
     751     * @param d The next node
     752     * @return the sideness
     753     */
     754    private static double calculateSideness(ILatLon a, ILatLon b, ILatLon c, ILatLon d) {
    756755        final double ndx = b.lon() - a.lon();
    757756        final double pdx = d.lon() - c.lon();
     
    766765     * Creates a new node at the interpolated position between the argument
    767766     * nodes. Interpolates linearly in projected coordinates.
    768      *
     767     * <p>
    769768     * If new node coordinate matches a or b coordinates, a or b is returned.
    770769     *
     
    774773     * @return A new node at the interpolated position (or a or b in case if f ≈ 0 or f ≈ 1).
    775774     */
    776     private Node interpolateNode(Node a, Node b, double f) {
     775    private static Node interpolateNode(Node a, Node b, double f) {
    777776        Node n = new Node(a.getEastNorth().interpolate(b.getEastNorth(), f));
    778                 if (n.equalsEpsilon(a, ILatLon.MAX_SERVER_PRECISION))
     777        if (n.equalsEpsilon(a, ILatLon.MAX_SERVER_PRECISION))
    779778            return a;
    780779        if (n.equalsEpsilon(b, ILatLon.MAX_SERVER_PRECISION))
     
    787786        setEnabled(getLayerManager().getEditDataSet() != null);
    788787    }
     788
     789    private static class SortWithIndex implements Comparable<SortWithIndex> {
     790        /**
     791         * The value to sort
     792         */
     793        public final double x;
     794        /**
     795         * The index in the original array
     796         */
     797        public final int i;
     798
     799        SortWithIndex(double a, int b) {
     800            x = a;
     801            i = b;
     802        }
     803
     804        @Override
     805        public int compareTo(SortWithIndex o) {
     806            return Double.compare(x, o.x);
     807        }
     808    }
    789809}
  • applications/editors/josm/plugins/terracer/src/org/openstreetmap/josm/plugins/terracer/TerracerPlugin.java

    r35327 r36181  
    2626 */
    2727public class TerracerPlugin extends Plugin implements Destroyable {
    28         private List<JosmAction> actions = Arrays.asList(new TerracerAction(), new ReverseTerraceAction());
     28    private final List<JosmAction> actions = Arrays.asList(new TerracerAction(), new ReverseTerraceAction());
    2929
    3030    public TerracerPlugin(PluginInformation info) {
    3131        super(info);
    3232        for (JosmAction action : actions) {
    33                 MainMenu.add(MainApplication.getMenu().moreToolsMenu, action);
     33            MainMenu.add(MainApplication.getMenu().moreToolsMenu, action);
    3434        }
    3535    }
    3636
    37         @Override
    38         public void destroy() {
     37    @Override
     38    public void destroy() {
    3939        final JMenu moreToolsMenu = MainApplication.getMenu().moreToolsMenu;
    40         final Map<Action, Component> actionsMap = Arrays.asList(moreToolsMenu.getMenuComponents()).stream()
     40        final Map<Action, Component> actionsMap = Arrays.stream(moreToolsMenu.getMenuComponents())
    4141                .filter(JMenuItem.class::isInstance).map(JMenuItem.class::cast)
    4242                .collect(Collectors.toMap(JMenuItem::getAction, component -> component));
     
    4747            }
    4848        }
    49                 actions.forEach(JosmAction::destroy);
    50         }
     49        actions.forEach(JosmAction::destroy);
     50    }
    5151}
Note: See TracChangeset for help on using the changeset viewer.