Ticket #10744: josm-7155-ease-multipolygon-handling-and-creation.patch

File josm-7155-ease-multipolygon-handling-and-creation.patch, 12.6 KB (added by cmuelle8, 12 years ago)

proof-of-concept qnd patch to make MP creation less painful

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

     
    6060         * @param command The command to be performed to split the way (which is saved for later retrieval by the {@link #getCommand} method)
    6161         * @param newSelection The new list of selected primitives ids (which is saved for later retrieval by the {@link #getNewSelection} method)
    6262         * @param originalWay The original way being split (which is saved for later retrieval by the {@link #getOriginalWay} method)
    63          * @param newWays The resulting new ways (which is saved for later retrieval by the {@link #getOriginalWay} method)
     63         * @param newWays The resulting new ways (which is saved for later retrieval by the {@link #getNewWays} method)
    6464         */
    6565        public SplitWayResult(Command command, List<? extends PrimitiveId> newSelection, Way originalWay, List<Way> newWays) {
    6666            this.command = command;
     
    100100        public List<Way> getNewWays() {
    101101            return newWays;
    102102        }
     103
     104        /**
     105         * Replies the resulting new ways and the original way
     106         * @return All resulting ways
     107         */
     108        public List<Way> getAllWays() {
     109            List<Way> ret = new ArrayList<>();
     110            ret.add(originalWay);
     111            ret.addAll(newWays);
     112            return ret;
     113        }
    103114    }
    104115
    105116    /**
     
    249260     * Splits the nodes of {@code wayToSplit} into a list of node sequences
    250261     * which are separated at the nodes in {@code splitPoints}.
    251262     *
    252      * This method displays warning messages if {@code wayToSplit} and/or
    253      * {@code splitPoints} aren't consistent.
     263     * This method does not hog the GUI thread.
    254264     *
    255      * Returns null, if building the split chunks fails.
    256      *
    257265     * @param wayToSplit the way to split. Must not be null.
    258266     * @param splitPoints the nodes where the way is split. Must not be null.
    259267     * @return the list of chunks
    260268     */
    261     public static List<List<Node>> buildSplitChunks(Way wayToSplit, List<Node> splitPoints){
     269    public static List<List<Node>> buildSplitChunksNoGUI(Way wayToSplit, Collection<Node> splitPoints){
    262270        CheckParameterUtil.ensureParameterNotNull(wayToSplit, "wayToSplit");
    263271        CheckParameterUtil.ensureParameterNotNull(splitPoints, "splitPoints");
    264272
     
    289297                && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1)
    290298                && !nodeSet.contains(wayChunks.get(0).get(0))) {
    291299            if (wayChunks.size() == 2) {
    292                 new Notification(
    293                         tr("You must select two or more nodes to split a circular way."))
    294                         .setIcon(JOptionPane.WARNING_MESSAGE)
    295                         .show();
    296                 return null;
     300                wayChunks.get(0).set(wayChunks.get(0).size() - 1, wayChunks.get(0).get(0));
     301                wayChunks.remove(1);
     302                return wayChunks;
    297303            }
    298304            lastWayChunk.remove(lastWayChunk.size() - 1);
    299305            lastWayChunk.addAll(wayChunks.get(0));
     
    301307            wayChunks.set(0, lastWayChunk);
    302308        }
    303309
     310        return wayChunks;
     311    }
     312
     313    /**
     314     * Splits the nodes of {@code wayToSplit} into a list of node sequences
     315     * which are separated at the nodes in {@code splitPoints}.
     316     *
     317     * This method displays warning messages if {@code wayToSplit} and/or
     318     * {@code splitPoints} aren't consistent.
     319     *
     320     * Returns null, if building the split chunks fails.
     321     *
     322     * @param wayToSplit the way to split. Must not be null.
     323     * @param splitPoints the nodes where the way is split. Must not be null.
     324     * @return the list of chunks
     325     */
     326    public static List<List<Node>> buildSplitChunks(Way wayToSplit, List<Node> splitPoints) {
     327        List<List<Node>> wayChunks = buildSplitChunksNoGUI(wayToSplit, splitPoints);
     328
    304329        if (wayChunks.size() < 2) {
    305330            if (wayChunks.get(0).get(0) == wayChunks.get(0).get(wayChunks.get(0).size() - 1)) {
    306331                new Notification(
     
    313338                        .setIcon(JOptionPane.WARNING_MESSAGE)
    314339                        .show();
    315340            }
    316             return null;
    317341        }
    318         return wayChunks;
     342
     343        return (wayChunks.size() < 2) ? null : wayChunks;
    319344    }
    320345
    321346    /**
     
    333358     * @return the result from the split operation
    334359     */
    335360    public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks, Collection<? extends OsmPrimitive> selection) {
     361        if (selection == null) selection = new ArrayList<>();
     362
    336363        // build a list of commands, and also a new selection list
    337364        Collection<Command> commandList = new ArrayList<>(wayChunks.size());
    338365        List<OsmPrimitive> newSelection = new ArrayList<>(selection.size() + wayChunks.size());
     
    527554    public static SplitWayResult split(OsmDataLayer layer, Way way, List<Node> atNodes, Collection<? extends OsmPrimitive> selection) {
    528555        List<List<Node>> chunks = buildSplitChunks(way, atNodes);
    529556        if (chunks == null) return null;
    530         return splitWay(layer,way, chunks, selection);
     557        return splitWay(layer, way, chunks, selection);
    531558    }
    532559
    533560    @Override
  • src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java

     
    88import java.util.ArrayList;
    99import java.util.Collection;
    1010import java.util.Collections;
     11import java.util.HashMap;
    1112import java.util.HashSet;
    1213import java.util.List;
     14import java.util.Map;
    1315import java.util.Set;
    1416import java.util.concurrent.Callable;
    1517import java.util.concurrent.ExecutionException;
     
    1618import java.util.concurrent.ExecutorService;
    1719import java.util.concurrent.Future;
    1820
     21import org.openstreetmap.josm.Main;
     22import org.openstreetmap.josm.actions.SplitWayAction;
     23import org.openstreetmap.josm.actions.SplitWayAction.SplitWayResult;
     24import org.openstreetmap.josm.command.DeleteCommand;
    1925import org.openstreetmap.josm.tools.Geometry;
    2026import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
    2127import org.openstreetmap.josm.tools.MultiMap;
    2228import org.openstreetmap.josm.tools.Pair;
     29import org.openstreetmap.josm.tools.Predicate;
    2330import org.openstreetmap.josm.tools.Utils;
    2431
    2532/**
     
    129136    }
    130137
    131138    /**
    132      * Splits ways into inner and outer JoinedWays. Sets {@link #innerWays} and {@link #outerWays} to the result.
     139     * Separate ways into inner and outer JoinedWays. Sets {@link #innerWays} and {@link #outerWays} to the result.
    133140     * TODO: Currently cannot process touching polygons. See code in JoinAreasAction.
    134141     * @param ways ways to analyze
    135      * @return error description if the ways cannot be split, {@code null} if all fine.
     142     * @return error description if the ways cannot be separated, {@code null} if all fine.
    136143     */
    137144    public String makeFromWays(Collection<Way> ways) {
    138145        try {
    139146            List<JoinedPolygon> joinedWays = joinWays(ways);
    140             //analyze witch way is inside witch outside.
     147            //analyze which way is inside which outside.
    141148            return makeFromPolygons(joinedWays);
    142149        } catch (JoinedPolygonCreationException ex) {
    143150            return ex.getMessage();
     
    159166    }
    160167
    161168    /**
     169     * Replies the current dataset
     170     *
     171     * @return the current dataset. null, if no current dataset exists
     172     */
     173    protected static DataSet getCurrentDataSet() {
     174        return Main.main != null ? Main.main.getCurrentDataSet() : null;
     175    }
     176
     177    /**
    162178     * Joins the given {@code ways} to multipolygon rings.
    163179     * @param ways the ways to join.
    164180     * @return a list of multipolygon rings.
     
    167183    public static List<JoinedPolygon> joinWays(Collection<Way> ways) throws JoinedPolygonCreationException {
    168184        List<JoinedPolygon> joinedWays = new ArrayList<>();
    169185
     186        { /* code block to ease the pain in MP handling - looks for overlapped ways that
     187           * already are in another _MP_ relation and tries its best to reuse segments.
     188           * it does auto-splitting and -selection and exclusively deletes unneeded parts
     189           * from the original selection. these parts are replaced by reusable way segments.
     190           * consecutively used, overlapping ways should disappear for adjacent MP areas.
     191           */
     192        final Map<Way, Set<Node>> splits = new HashMap<>();
     193        final Set<Node> ways_nodes = new HashSet<>();
     194        for (final Way w: ways) {
     195            ways_nodes.addAll(w.getNodes());
     196
     197            for (final Pair<Node,Node> p: w.getNodePairs(false)) {
     198                Collection<Way> dups = Utils.filter(
     199                    OsmPrimitive.getFilteredList(p.b.getReferrers(), Way.class),
     200                    new Predicate<Way>() {
     201                        public boolean evaluate(final Way v) {
     202                            return v.equals(w) || v.containsNode(p.a) && Utils.exists(
     203                                OsmPrimitive.getFilteredList(v.getReferrers(), Relation.class),
     204                                new Predicate<Relation>() {
     205                                    public boolean evaluate(final Relation r) {
     206                                        return r.hasTag("type", "multipolygon");
     207                                    }
     208                                }
     209                            );
     210                        }
     211                    }
     212                );
     213
     214                // TODO: handle (rare?) cases where dups.size() > 2 applies or stop and warn user
     215                if (dups.size() > 1) {
     216                    for (Way d: dups) {
     217                        if (!splits.containsKey(d)) {
     218                            splits.put(d, new HashSet<Node>());
     219                        }
     220                        for (Node n: Pair.toArrayList(p)) {
     221                            if (splits.get(d).contains(n)) {
     222                                splits.get(d).remove(n);
     223                            } else {
     224                                splits.get(d).add(n);
     225                            }
     226                        }
     227                    }
     228                }
     229
     230            }
     231        }
     232
     233        ways = new ArrayList<>(ways);
     234
     235        final List<Way> reuse = new ArrayList<>();
     236        for (final Way d: splits.keySet()) {
     237            List<List<Node>> chunks =
     238                SplitWayAction.buildSplitChunksNoGUI(d, splits.get(d));
     239
     240            if (chunks.size() > 1) {
     241                SplitWayResult r = SplitWayAction.splitWay(Main.main.getEditLayer(), d, chunks, null);
     242                Main.main.undoRedo.addNoRedraw(r.getCommand());
     243
     244                if (ways.contains(r.getOriginalWay())) {
     245                    ways.addAll(r.getNewWays());
     246                } else {
     247                    reuse.addAll(Utils.filter(r.getAllWays(), new Predicate<Way>() {
     248                        public boolean evaluate(final Way w) {
     249                            return ways_nodes.containsAll(w.getNodes());
     250                        }
     251                    }));
     252                }
     253            } else if (!ways.contains(d)) {
     254                reuse.add(d);
     255            }
     256        }
     257
     258        final Set<Node> reuse_nodes = new HashSet<>();
     259        for (final Way w: reuse) {
     260            reuse_nodes.addAll(w.getNodes());
     261        }
     262
     263        Collection<Way> dups = new ArrayList<>(Utils.filter(ways, new Predicate<Way>() {
     264            public boolean evaluate(final Way w) {
     265                return reuse_nodes.containsAll(w.getNodes());
     266            }
     267        }));
     268
     269        Main.debug(ways.toString() +"\n  --> original way collection\n");
     270        if (!dups.isEmpty()) {
     271            Main.debug(dups.toString() +"\n  --> split dups about to be deleted\n");
     272            Main.debug(reuse.toString()+"\n  --> reusable ways detected\n");
     273
     274            ways.removeAll(dups);
     275            ways.addAll(reuse);
     276            Main.main.undoRedo.addNoRedraw(DeleteCommand.delete(Main.main.getEditLayer(), dups));
     277
     278            Main.debug(ways.toString() +"\n  --> modified way collection to be joined\n");
     279        }
     280        } /* end code block to ease the pain in MP handling */
     281
    170282        //collect ways connecting to each node.
    171283        MultiMap<Node, Way> nodesWithConnectedWays = new MultiMap<>();
    172284        Set<Way> usedWays = new HashSet<>();