Ticket #18420: 18420.patch

File 18420.patch, 5.5 KB (added by GerdP, 5 months ago)
  • src/org/openstreetmap/josm/actions/JoinNodeWayAction.java

     
    1212import java.util.Comparator;
    1313import java.util.HashMap;
    1414import java.util.HashSet;
     15import java.util.LinkedHashMap;
    1516import java.util.LinkedList;
    1617import java.util.List;
    1718import java.util.Map;
     19import java.util.Map.Entry;
    1820import java.util.Set;
     21import java.util.TreeMap;
    1922
    2023import javax.swing.JOptionPane;
    2124
     
    9093        DataSet ds = getLayerManager().getEditDataSet();
    9194        Collection<Node> selectedNodes = ds.getSelectedNodes();
    9295        Collection<Command> cmds = new LinkedList<>();
    93         Map<Way, MultiMap<Integer, Node>> data = new HashMap<>();
     96        Map<Way, MultiMap<Integer, Node>> data = new LinkedHashMap<>();
    9497
    9598        // If the user has selected some ways, only join the node to these.
    9699        boolean restrictToSelectedWays = !ds.getSelectedWays().isEmpty();
     
    99102        MapView mapView = MainApplication.getMap().mapView;
    100103        for (Node node : selectedNodes) {
    101104            List<WaySegment> wss = mapView.getNearestWaySegments(mapView.getPoint(node), OsmPrimitive::isSelectable);
    102             Set<Way> seenWays = new HashSet<>();
     105            // we cannot trust the order of elements in wss because it was calculated based on rounded position value of node
     106            TreeMap<Double, List<WaySegment>> nearestMap = new TreeMap<>();
     107            EastNorth en = node.getEastNorth();
    103108            for (WaySegment ws : wss) {
    104109                // Maybe cleaner to pass a "isSelected" predicate to getNearestWaySegments, but this is less invasive.
    105110                if (restrictToSelectedWays && !ws.way.isSelected()) {
    106111                    continue;
    107112                }
    108                 // only use the closest WaySegment of each way and ignore those that already contain the node
    109                 if (!ws.getFirstNode().equals(node) && !ws.getSecondNode().equals(node)
    110                         && !seenWays.contains(ws.way)) {
    111                     MultiMap<Integer, Node> innerMap = data.get(ws.way);
    112                     if (innerMap == null) {
    113                         innerMap = new MultiMap<>();
    114                         data.put(ws.way, innerMap);
     113                /* perpendicular distance squared
     114                 * loose some precision to account for possible deviations in the calculation above
     115                 * e.g. if identical (A and B) come about reversed in another way, values may differ
     116                 * -- zero out least significant 32 dual digits of mantissa..
     117                 */
     118                double distSq = en.distanceSq(Geometry.closestPointToSegment(ws.getFirstNode().getEastNorth(),
     119                        ws.getSecondNode().getEastNorth(), en));
     120                distSq = Double.longBitsToDouble(Double.doubleToLongBits(distSq) >> 32 << 32); // resolution in numbers with large exponent not needed here..
     121                List<WaySegment> wslist = nearestMap.computeIfAbsent(distSq, k -> new LinkedList<>());
     122                wslist.add(ws);
     123            }
     124            Set<Way> seenWays = new HashSet<>();
     125            Double usedDist = null;
     126            while (!nearestMap.isEmpty()) {
     127                Entry<Double, List<WaySegment>> entry = nearestMap.pollFirstEntry();
     128                if (usedDist != null) {
     129                    double delta = entry.getKey() - usedDist;
     130                    if (delta > 1e-4)
     131                        break;
     132                }
     133                for (WaySegment ws : entry.getValue()) {
     134                    // only use the closest WaySegment of each way and ignore those that already contain the node
     135                    if (!ws.getFirstNode().equals(node) && !ws.getSecondNode().equals(node)
     136                            && !seenWays.contains(ws.way)) {
     137                        if (usedDist == null)
     138                            usedDist = entry.getKey();
     139                        MultiMap<Integer, Node> innerMap = data.get(ws.way);
     140                        if (innerMap == null) {
     141                            innerMap = new MultiMap<>();
     142                            data.put(ws.way, innerMap);
     143                        }
     144                        innerMap.put(ws.lowerIndex, node);
    115145                    }
    116                     innerMap.put(ws.lowerIndex, node);
    117146                    seenWays.add(ws.way);
    118147                }
    119148            }
     
    141170                        EastNorth prevMove = movedNodes.get(node);
    142171                        if (prevMove != null) {
    143172                            if (!prevMove.equalsEpsilon(newPosition, 1e-4)) {
     173                                // very unlikely: node has same distance to multiple ways which are not nearly overlapping
    144174                                new Notification(tr("Multiple target ways, no common point found. Nothing was changed."))
    145175                                        .setIcon(JOptionPane.INFORMATION_MESSAGE)
    146176                                        .show();
     
    166196        }
    167197
    168198        if (cmds.isEmpty()) return;
     199        if (joinWayToNode && selectedNodes.size() > movedNodes.size()) {
     200            new Notification(tr("Not all selected nodes were moved."))
     201            .setIcon(JOptionPane.INFORMATION_MESSAGE)
     202            .show();
     203        }
    169204        UndoRedoHandler.getInstance().add(new SequenceCommand(getValue(NAME).toString(), cmds));
    170205    }
    171206