Ignore:
Timestamp:
2019-12-09T09:47:20+01:00 (5 years ago)
Author:
GerdP
Message:

fix #18367 and #18385: CombineWayAction (C) refuses to combine ways or silently reverses ways
Changes:

  • try first to combine the ways with the method Multipolygon.joinWays() If that method returns a single line string we can use it, else use the result of NodeGraph.buildSpanningPathNoRemove(). Both methods will not add or remove segments
  • if ways are combined execute checks for overlapping segments or self-intersection and show a notification popup right after the command was added to the UndoRedoHandler
  • The code which handles reversed ways needed changes. In the unpatched version it sometimes claims wrongly that ways were reversed, in special cases it sometimes silently reverted ways. The old code did not handle the case properly that a node can appear more than once. I really hope that I got it right now.
  • Fix some sonarlint issues
  • let NodeGraph routines return an ArrayList instead of a LinkedList (improves performance a bit)
  • Add unit tests
File:
1 edited

Legend:

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

    r15555 r15574  
    3232import org.openstreetmap.josm.data.osm.TagCollection;
    3333import org.openstreetmap.josm.data.osm.Way;
     34import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
     35import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay;
    3436import org.openstreetmap.josm.data.preferences.BooleanProperty;
     37import org.openstreetmap.josm.data.validation.Test;
     38import org.openstreetmap.josm.data.validation.tests.OverlappingWays;
     39import org.openstreetmap.josm.data.validation.tests.SelfIntersectingWay;
    3540import org.openstreetmap.josm.gui.ExtendedDialog;
    3641import org.openstreetmap.josm.gui.MainApplication;
     
    123128
    124129        // try to build a new way which includes all the combined ways
    125         NodeGraph graph = NodeGraph.createNearlyUndirectedGraphFromNodeWays(ways);
    126         List<Node> path = graph.buildSpanningPathNoRemove();
    127         if (path == null) {
     130        List<Node> path = tryJoin(ways);
     131        if (path.isEmpty()) {
    128132            warnCombiningImpossible();
    129133            return null;
     
    137141        List<Way> reversedWays = new LinkedList<>();
    138142        List<Way> unreversedWays = new LinkedList<>();
    139         for (Way w: ways) {
    140             // Treat zero or one-node ways as unreversed as Combine action action is a good way to fix them (see #8971)
    141             if (w.getNodesCount() < 2 || (path.indexOf(w.getNode(0)) + 1) == path.lastIndexOf(w.getNode(1))) {
    142                 unreversedWays.add(w);
    143             } else {
    144                 reversedWays.add(w);
    145             }
    146         }
     143        detectReversedWays(ways, path, reversedWays, unreversedWays);
    147144        // reverse path if all ways have been reversed
    148145        if (unreversedWays.isEmpty()) {
     
    164161            }
    165162            // if there are still reversed ways with direction-dependent tags, reverse their tags
    166             if (!reversedWays.isEmpty() && PROP_REVERSE_WAY.get()) {
     163            if (!reversedWays.isEmpty() && Boolean.TRUE.equals(PROP_REVERSE_WAY.get())) {
    167164                List<Way> unreversedTagWays = new ArrayList<>(ways);
    168165                unreversedTagWays.removeAll(reversedWays);
     
    211208
    212209        return new Pair<>(targetWay, sequenceCommand);
     210    }
     211
     212    protected static void detectReversedWays(Collection<Way> ways, List<Node> path, List<Way> reversedWays,
     213            List<Way> unreversedWays) {
     214        for (Way w: ways) {
     215            // Treat zero or one-node ways as unreversed as Combine action action is a good way to fix them (see #8971)
     216            if (w.getNodesCount() < 2) {
     217                unreversedWays.add(w);
     218            } else {
     219                boolean foundStartSegment = false;
     220                int last = path.lastIndexOf(w.getNode(0));
     221
     222                for (int i = path.indexOf(w.getNode(0)); i <= last; i++) {
     223                    if (path.get(i) == w.getNode(0) && i + 1 < path.size() && w.getNode(1) == path.get(i + 1)) {
     224                        foundStartSegment = true;
     225                        break;
     226                    }
     227                }
     228                if (foundStartSegment) {
     229                    unreversedWays.add(w);
     230                } else {
     231                    reversedWays.add(w);
     232                }
     233            }
     234        }
     235    }
     236
     237    protected static List<Node> tryJoin(Collection<Way> ways) {
     238        List<Node> path = joinWithMultipolygonCode(ways);
     239        if (path.isEmpty()) {
     240            NodeGraph graph = NodeGraph.createNearlyUndirectedGraphFromNodeWays(ways);
     241            path = graph.buildSpanningPathNoRemove();
     242        }
     243        return path;
     244    }
     245
     246    /**
     247     * Use {@link Multipolygon#joinWays(Collection)} to join ways.
     248     * @param ways the ways
     249     * @return List of nodes of the combined ways or null if ways could not be combined to a single way.
     250     * Result may contain overlapping segments.
     251     */
     252    private static List<Node> joinWithMultipolygonCode(Collection<Way> ways) {
     253        // sort so that old unclosed ways appear first
     254        LinkedList<Way> toJoin = new LinkedList<>(ways);
     255        toJoin.sort((o1, o2) -> {
     256            int d = Boolean.compare(o1.isNew(), o2.isNew());
     257            if (d == 0)
     258                d = Boolean.compare(o1.isClosed(), o2.isClosed());
     259            return d;
     260        });
     261        Collection<JoinedWay> list = Multipolygon.joinWays(toJoin);
     262        if (list.size() == 1) {
     263            // ways form a single line string
     264            return new ArrayList<>(list.iterator().next().getNodes());
     265        }
     266        return Collections.emptyList();
    213267    }
    214268
     
    238292        if (combineResult == null)
    239293            return;
     294
    240295        final Way selectedWay = combineResult.a;
    241296        UndoRedoHandler.getInstance().add(combineResult.b);
     297        Test test = new OverlappingWays();
     298        test.startTest(null);
     299        test.visit(combineResult.a);
     300        test.endTest();
     301        if (test.getErrors().isEmpty()) {
     302            test = new SelfIntersectingWay();
     303            test.startTest(null);
     304            test.visit(combineResult.a);
     305            test.endTest();
     306        }
     307        if (!test.getErrors().isEmpty()) {
     308            new Notification(test.getErrors().get(0).getMessage())
     309            .setIcon(JOptionPane.WARNING_MESSAGE)
     310            .setDuration(Notification.TIME_SHORT)
     311            .show();
     312        }
    242313        if (selectedWay != null) {
    243314            GuiHelper.runInEDT(() -> ds.setSelected(selectedWay));
     
    262333        setEnabled(numWays >= 2);
    263334    }
     335
    264336}
Note: See TracChangeset for help on using the changeset viewer.