Changeset 8247 in josm for trunk/src/org


Ignore:
Timestamp:
2015-04-22T22:21:50+02:00 (10 years ago)
Author:
Balaitous
Message:

fix #11302 - "distribute nodes" acts weirdly if the way is not reasonably straight already

When one way (no self-crossing) is selected with at most two of its nodes, distibute node keeping nodes order along the way. Selected nodes are kept in place.
In any other case, no changes are made to the algorithm.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/DistributeAction.java

    r7828 r8247  
    1111import java.util.Iterator;
    1212import java.util.LinkedList;
     13import java.util.List;
    1314import java.util.Set;
    1415
     
    3233public final class DistributeAction extends JosmAction {
    3334
     35    private static final String ACTION_SHORT_NAME = "Distribute Nodes";
     36
    3437    /**
    3538     * Constructs a new {@code DistributeAction}.
    3639     */
    3740    public DistributeAction() {
    38         super(tr("Distribute Nodes"), "distribute", tr("Distribute the selected nodes to equal distances along a line."),
    39                 Shortcut.registerShortcut("tools:distribute", tr("Tool: {0}", tr("Distribute Nodes")), KeyEvent.VK_B,
    40                 Shortcut.SHIFT), true);
     41        super(tr(ACTION_SHORT_NAME), "distribute",
     42              tr("Distribute the selected nodes to equal distances along a line."),
     43              Shortcut.registerShortcut("tools:distribute",
     44                                        tr("Tool: {0}", tr(ACTION_SHORT_NAME)),
     45                                        KeyEvent.VK_B, Shortcut.SHIFT),
     46              true);
    4147        putValue("help", ht("/Action/DistributeNodes"));
    4248    }
    4349
    4450    /**
    45      * The general algorithm here is to find the two selected nodes
    46      * that are furthest apart, and then to distribute all other selected
    47      * nodes along the straight line between these nodes.
     51     * Perform action.
     52     * Select method according to user selection.
     53     * Case 1: One Way (no self-crossing) and at most 2 nodes contains by this way:
     54     *     Distribute nodes keeping order along the way
     55     * Case 2: Other
     56     *     Distribute nodes
    4857     */
    4958    @Override
     
    5160        if (!isEnabled())
    5261            return;
    53         Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
    54         Collection<Node> nodes = new LinkedList<>();
    55         Collection<Node> itnodes = new LinkedList<>();
    56         for (OsmPrimitive osm : sel)
    57             if (osm instanceof Node) {
    58                 nodes.add((Node)osm);
    59                 itnodes.add((Node)osm);
    60             }
    61         // special case if no single nodes are selected and exactly one way is:
    62         // then use the way's nodes
    63         if (nodes.isEmpty() && (sel.size() == 1)) {
    64             for (OsmPrimitive osm : sel)
    65                 if (osm instanceof Way) {
    66                     nodes.addAll(((Way)osm).getNodes());
    67                     itnodes.addAll(((Way)osm).getNodes());
    68                 }
     62
     63        // Collect user selected objects
     64        Collection<OsmPrimitive> selected = getCurrentDataSet().getSelected();
     65        Collection<Way> ways = new LinkedList<>();
     66        Collection<Node> nodes = new HashSet<>();
     67        for(OsmPrimitive osm : selected) {
     68            if(osm instanceof Node) {
     69                nodes.add((Node) osm);
     70            } else if(osm instanceof Way) {
     71                ways.add((Way) osm);
     72            }
    6973        }
    7074
    7175        Set<Node> ignoredNodes = removeNodesWithoutCoordinates(nodes);
    72         ignoredNodes.addAll(removeNodesWithoutCoordinates(itnodes));
    7376        if (!ignoredNodes.isEmpty()) {
    7477            Main.warn(tr("Ignoring {0} nodes with null coordinates", ignoredNodes.size()));
     
    7679        }
    7780
    78         if (nodes.size() < 3) {
     81        // Switch between algorithms
     82        Collection<Command> cmds;
     83        if(checkDistributeWay(ways, nodes)) {
     84            cmds = distributeWay(ways, nodes);
     85        } else if(checkDistributeNodes(ways, nodes)) {
     86            cmds = distributeNodes(nodes);
     87        } else {
    7988            new Notification(
    80                     tr("Please select at least three nodes."))
    81                     .setIcon(JOptionPane.INFORMATION_MESSAGE)
    82                     .setDuration(Notification.TIME_SHORT)
    83                     .show();
     89                             tr("Please select :\n" +
     90                                "* One no self-crossing way with at most two of its nodes;\n" +
     91                                "* Three nodes."))
     92                .setIcon(JOptionPane.INFORMATION_MESSAGE)
     93                .setDuration(Notification.TIME_SHORT)
     94                .show();
    8495            return;
    8596        }
    8697
     98        if(cmds.isEmpty()) {
     99            return;
     100        }
     101
     102        // Do it!
     103        Main.main.undoRedo.add(new SequenceCommand(tr(ACTION_SHORT_NAME), cmds));
     104        Main.map.repaint();
     105    }
     106
     107    /**
     108     * Test if one way, no self-crossing, is selected with at most two of its nodes.
     109     * @param ways Selected ways
     110     * @param nodes Selected nodes
     111     * @return true in this case
     112     */
     113    private Boolean checkDistributeWay(Collection<Way> ways, Collection<Node> nodes) {
     114        if(ways.size() == 1 && nodes.size() <= 2) {
     115            Way w = ways.iterator().next();
     116            Set<Node> unduplicated = new HashSet<>(w.getNodes());
     117            if(unduplicated.size() != w.getNodesCount()) {
     118                // No self crossing way
     119                return false;
     120            }
     121            for(Node node: nodes) {
     122                if(!w.containsNode(node)) {
     123                    return false;
     124                }
     125            }
     126            return true;
     127        }
     128        return false;
     129    }
     130
     131    /**
     132     * Distribute nodes contained by a way, keeping nodes order.
     133     * If one or two nodes are selected, keep these nodes in place.
     134     * @param ways Selected ways, must be collection of size 1.
     135     * @param nodes Selected nodes, at most two nodes.
     136     * @return Collection of command to be executed.
     137     */
     138    private Collection<Command> distributeWay(Collection<Way> ways,
     139                                              Collection<Node> nodes) {
     140        Way w = ways.iterator().next();
     141        Collection<Command> cmds = new LinkedList<>();
     142
     143        if(w.getNodesCount() == nodes.size() || w.getNodesCount() <= 2) {
     144            // Nothing to do
     145            return cmds;
     146        }
     147
     148        double xa, ya; // Start point
     149        double dx, dy; // Segment increment
     150        if(nodes.isEmpty()) {
     151            Node na = w.firstNode();
     152            nodes.add(na);
     153            Node nb = w.lastNode();
     154            nodes.add(nb);
     155            xa = na.getEastNorth().east();
     156            ya = na.getEastNorth().north();
     157            dx = (nb.getEastNorth().east() - xa) / (w.getNodesCount() - 1);
     158            dy = (nb.getEastNorth().north() - ya) / (w.getNodesCount() - 1);
     159        } else if(nodes.size() == 1) {
     160            Node n = nodes.iterator().next();
     161            int nIdx = w.getNodes().indexOf(n);
     162            Node na = w.firstNode();
     163            Node nb = w.lastNode();
     164            dx = (nb.getEastNorth().east() - na.getEastNorth().east()) /
     165                (w.getNodesCount() - 1);
     166            dy = (nb.getEastNorth().north() - na.getEastNorth().north()) /
     167                (w.getNodesCount() - 1);
     168            xa = n.getEastNorth().east() - dx * nIdx;
     169            ya = n.getEastNorth().north() - dy * nIdx;
     170        } else {
     171            Iterator<Node> it = nodes.iterator();
     172            Node na = it.next();
     173            Node nb = it.next();
     174            List<Node> wayNodes = w.getNodes();
     175            int naIdx = wayNodes.indexOf(na);
     176            int nbIdx = wayNodes.indexOf(nb);
     177            dx = (nb.getEastNorth().east() - na.getEastNorth().east()) / (nbIdx - naIdx);
     178            dy = (nb.getEastNorth().north() - na.getEastNorth().north()) / (nbIdx - naIdx);
     179            xa = na.getEastNorth().east() - dx * naIdx;
     180            ya = na.getEastNorth().north() - dy * naIdx;
     181        }
     182
     183        for(int i = 0; i < w.getNodesCount(); i++) {
     184            Node n = w.getNode(i);
     185            if (!n.isLatLonKnown() || nodes.contains(n)) {
     186                continue;
     187            }
     188            double x = xa + i * dx;
     189            double y = ya + i * dy;
     190            cmds.add(new MoveCommand(n, x - n.getEastNorth().east(),
     191                                     y - n.getEastNorth().north()));
     192        }
     193        return cmds;
     194    }
     195
     196    /**
     197     * Test if nodes oriented algorithm applies to the selection.
     198     * @param ways Selected ways
     199     * @param nodes Selected nodes
     200     * @return true in this case
     201     */
     202    private Boolean checkDistributeNodes(Collection<Way> ways, Collection<Node> nodes) {
     203        return ways.isEmpty() && nodes.size() >= 3;
     204    }
     205
     206    /**
     207     * Distribute nodes when only nodes are selected.
     208     * The general algorithm here is to find the two selected nodes
     209     * that are furthest apart, and then to distribute all other selected
     210     * nodes along the straight line between these nodes.
     211     * @return Commands to execute to perform action
     212     */
     213    private Collection<Command> distributeNodes(Collection<Node> nodes) {
    87214        // Find from the selected nodes two that are the furthest apart.
    88215        // Let's call them A and B.
     
    92219        Node nodeb = null;
    93220
     221        Collection<Node> itnodes = new LinkedList<>(nodes);
    94222        for (Node n : nodes) {
    95223            itnodes.remove(n);
     
    146274        }
    147275
    148         // Do it!
    149         Main.main.undoRedo.add(new SequenceCommand(tr("Distribute Nodes"), cmds));
    150         Main.map.repaint();
    151     }
    152 
     276        return cmds;
     277    }
     278
     279    /**
     280     * Remove nodes without knowned coordinates from a collection.
     281     * @param col Collection of nodes to check
     282     * @return Set of nodes without coordinates
     283     */
    153284    private Set<Node> removeNodesWithoutCoordinates(Collection<Node> col) {
    154285        Set<Node> result = new HashSet<>();
Note: See TracChangeset for help on using the changeset viewer.