Ticket #7239: multijoin3.diff

File multijoin3.diff, 8.0 KB (added by cymerio, 11 years ago)

My implementation of the feature

  • src/org/openstreetmap/josm/actions/JoinNodeWayAction.java

    diff --git a/src/org/openstreetmap/josm/actions/JoinNodeWayAction.java b/src/org/openstreetmap/josm/actions/JoinNodeWayAction.java
    index 9453f6c..3f2623c 100644
    a b import org.openstreetmap.josm.tools.Shortcut;  
    1818
    1919import java.awt.event.ActionEvent;
    2020import java.awt.event.KeyEvent;
     21import java.util.Comparator;
    2122import java.util.Collection;
    2223import java.util.Collections;
    2324import java.util.LinkedList;
    2425import java.util.List;
    2526import java.util.Map;
     27import java.util.HashMap;
    2628import java.util.Set;
    2729import java.util.SortedSet;
    2830import java.util.TreeSet;
    public class JoinNodeWayAction extends JosmAction {  
    6567        if (!isEnabled())
    6668            return;
    6769        Collection<Node> selectedNodes = getCurrentDataSet().getSelectedNodes();
    68         // Allow multiple selected nodes too?
    69         if (selectedNodes.size() != 1) return;
    70 
    71         final Node node = selectedNodes.iterator().next();
    72 
    7370        Collection<Command> cmds = new LinkedList<>();
     71        Map<Way, MultiMap<Integer, Node>> data = new HashMap<>();
    7472
    7573        // If the user has selected some ways, only join the node to these.
    7674        boolean restrictToSelectedWays =
    7775                !getCurrentDataSet().getSelectedWays().isEmpty();
    7876
    79         List<WaySegment> wss = Main.map.mapView.getNearestWaySegments(
    80                 Main.map.mapView.getPoint(node), OsmPrimitive.isSelectablePredicate);
    81         MultiMap<Way, Integer> insertPoints = new MultiMap<>();
    82         for (WaySegment ws : wss) {
    83             // Maybe cleaner to pass a "isSelected" predicate to getNearestWaySegments, but this is less invasive.
    84             if (restrictToSelectedWays && !ws.way.isSelected()) {
    85                 continue;
    86             }
     77        // Planning phase: decide where we'll insert the nodes and put it all in "data"
     78        for (Node node : selectedNodes) {
     79            List<WaySegment> wss = Main.map.mapView.getNearestWaySegments(
     80                    Main.map.mapView.getPoint(node), OsmPrimitive.isSelectablePredicate);
     81
     82            MultiMap<Way, Integer> insertPoints = new MultiMap<>();
     83            for (WaySegment ws : wss) {
     84                // Maybe cleaner to pass a "isSelected" predicate to getNearestWaySegments, but this is less invasive.
     85                if (restrictToSelectedWays && !ws.way.isSelected()) {
     86                    continue;
     87                }
    8788
    88             if (ws.getFirstNode() != node && ws.getSecondNode() != node) {
    89                 insertPoints.put(ws.way, ws.lowerIndex);
     89                if (ws.getFirstNode() != node && ws.getSecondNode() != node) {
     90                    insertPoints.put(ws.way, ws.lowerIndex);
     91                }
     92            }
     93            for (Map.Entry<Way, Set<Integer>> entry : insertPoints.entrySet()) {
     94                final Way w = entry.getKey();
     95                final Set<Integer> insertPointsForWay = entry.getValue();
     96                for (int i : pruneSuccs(insertPointsForWay)) {
     97                    MultiMap<Integer, Node> innerMap;
     98                    if (!data.containsKey(w)) {
     99                        innerMap = new MultiMap<>();
     100                    } else {
     101                        innerMap = data.get(w);
     102                    }
     103                    innerMap.put(i, node);
     104                    data.put(w, innerMap);
     105                }
    90106            }
    91107        }
    92108
    93         for (Map.Entry<Way, Set<Integer>> entry : insertPoints.entrySet()) {
     109        // Execute phase: traverse the structure "data" and finally put the nodes into place
     110        for (Map.Entry<Way, MultiMap<Integer, Node>> entry : data.entrySet()) {
    94111            final Way w = entry.getKey();
    95             final Set<Integer> insertPointsForWay = entry.getValue();
    96             if (insertPointsForWay.isEmpty()) {
    97                 continue;
    98             }
     112            final MultiMap<Integer, Node> innerEntry = entry.getValue();
     113
     114            List<Integer> segmentIndexes = new LinkedList<Integer>();
     115            segmentIndexes.addAll(innerEntry.keySet());
     116            Collections.sort(segmentIndexes, Collections.reverseOrder());
    99117
    100             List<Node> nodesToAdd = w.getNodes();
    101             for (int i : pruneSuccsAndReverse(insertPointsForWay)) {
     118            List<Node> wayNodes = w.getNodes();
     119            for (Integer segmentIndex : segmentIndexes) {
     120                final Set<Node> nodesInSegment = innerEntry.get(segmentIndex);
    102121                if (joinWayToNode) {
    103                     EastNorth newPosition = Geometry.closestPointToSegment(
    104                             w.getNode(i).getEastNorth(), w.getNode(i + 1).getEastNorth(), node.getEastNorth());
    105                     cmds.add(new MoveCommand(node, Projections.inverseProject(newPosition)));
     122                    for (Node node : nodesInSegment) {
     123                        EastNorth newPosition = Geometry.closestPointToSegment(w.getNode(segmentIndex).getEastNorth(),
     124                                                                            w.getNode(segmentIndex+1).getEastNorth(),
     125                                                                            node.getEastNorth());
     126                        cmds.add(new MoveCommand(node, Projections.inverseProject(newPosition)));
     127                    }
    106128                }
    107                 nodesToAdd.add(i + 1, node);
     129                List<Node> nodesToAdd = new LinkedList<Node>();
     130                nodesToAdd.addAll(nodesInSegment);
     131                Collections.sort(nodesToAdd, new nodeDistanceToRefNodeComparator(w.getNode(segmentIndex), w.getNode(segmentIndex+1), !joinWayToNode));
     132                wayNodes.addAll(segmentIndex + 1, nodesToAdd);
    108133            }
    109134            Way wnew = new Way(w);
    110             wnew.setNodes(nodesToAdd);
     135            wnew.setNodes(wayNodes);
    111136            cmds.add(new ChangeCommand(w, wnew));
    112137        }
     138
    113139        if (cmds.isEmpty()) return;
    114140        Main.main.undoRedo.add(new SequenceCommand(getValue(NAME).toString(), cmds));
    115141        Main.map.repaint();
    116142    }
    117143
    118     private static SortedSet<Integer> pruneSuccsAndReverse(Collection<Integer> is) {
    119         SortedSet<Integer> is2 = new TreeSet<>(Collections.reverseOrder());
     144    private static SortedSet<Integer> pruneSuccs(Collection<Integer> is) {
     145        SortedSet<Integer> is2 = new TreeSet<>();
    120146        for (int i : is) {
    121147            if (!is2.contains(i - 1) && !is2.contains(i + 1)) {
    122148                is2.add(i);
    public class JoinNodeWayAction extends JosmAction {  
    125151        return is2;
    126152    }
    127153
     154    // Sorts collinear nodes by their distance to a common reference node.
     155    private class nodeDistanceToRefNodeComparator implements Comparator<Node> {
     156        private EastNorth refPoint;
     157        private EastNorth refPoint2;
     158        private boolean projectToSegment;
     159        nodeDistanceToRefNodeComparator(Node referenceNode) {
     160            refPoint = referenceNode.getEastNorth();
     161            projectToSegment = false;
     162        }
     163        nodeDistanceToRefNodeComparator(Node referenceNode, Node referenceNode2, boolean projectFirst) {
     164            refPoint = referenceNode.getEastNorth();
     165            refPoint2 = referenceNode2.getEastNorth();
     166            projectToSegment = projectFirst;
     167        }
     168        public int compare(Node first, Node second) {
     169            EastNorth firstPosition = first.getEastNorth();
     170            EastNorth secondPosition = second.getEastNorth();
     171
     172            if (projectToSegment) {
     173                firstPosition = Geometry.closestPointToSegment(refPoint, refPoint2, firstPosition);
     174                secondPosition = Geometry.closestPointToSegment(refPoint, refPoint2, secondPosition);
     175            }
     176
     177            double distanceFirst = firstPosition.distance(refPoint);
     178            double distanceSecond = secondPosition.distance(refPoint);
     179            double difference =  distanceFirst - distanceSecond;
     180
     181            if (difference > 0.0) return 1;
     182            if (difference < 0.0) return -1;
     183            return 0;
     184        }
     185    }
     186
    128187    @Override
    129188    protected void updateEnabledState() {
    130189        if (getCurrentDataSet() == null) {