Changeset 3554 in josm for trunk/src


Ignore:
Timestamp:
2010-09-22T22:28:06+02:00 (14 years ago)
Author:
bastiK
Message:

see #5179 (patch by viesturs) - improve join areas

File:
1 edited

Legend:

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

    r3449 r3554  
    22package org.openstreetmap.josm.actions;
    33
     4import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.combineTigerTags;
     5import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.completeTagCollectionForEditing;
     6import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing;
    47import static org.openstreetmap.josm.tools.I18n.marktr;
    58import static org.openstreetmap.josm.tools.I18n.tr;
    69import static org.openstreetmap.josm.tools.I18n.trn;
    710
    8 import java.awt.GridBagLayout;
    911import java.awt.event.ActionEvent;
    1012import java.awt.event.KeyEvent;
     
    1416import java.util.Collection;
    1517import java.util.Collections;
     18import java.util.Comparator;
    1619import java.util.HashMap;
    1720import java.util.HashSet;
     21import java.util.LinkedHashSet;
    1822import java.util.LinkedList;
    1923import java.util.List;
    2024import java.util.Map;
    21 import java.util.Map.Entry;
    2225import java.util.Set;
    2326import java.util.TreeMap;
    24 import java.util.TreeSet;
    25 
    26 import javax.swing.Box;
    27 import javax.swing.JComboBox;
    28 import javax.swing.JLabel;
     27
    2928import javax.swing.JOptionPane;
    30 import javax.swing.JPanel;
    3129
    3230import org.openstreetmap.josm.Main;
     31import org.openstreetmap.josm.actions.ReverseWayAction.ReverseWayResult;
    3332import org.openstreetmap.josm.actions.SplitWayAction.SplitWayResult;
    3433import org.openstreetmap.josm.command.AddCommand;
     
    3736import org.openstreetmap.josm.command.DeleteCommand;
    3837import org.openstreetmap.josm.command.SequenceCommand;
     38import org.openstreetmap.josm.corrector.UserCancelException;
    3939import org.openstreetmap.josm.data.UndoRedoHandler;
    4040import org.openstreetmap.josm.data.coor.EastNorth;
     
    4545import org.openstreetmap.josm.data.osm.Relation;
    4646import org.openstreetmap.josm.data.osm.RelationMember;
    47 import org.openstreetmap.josm.data.osm.TigerUtils;
     47import org.openstreetmap.josm.data.osm.TagCollection;
    4848import org.openstreetmap.josm.data.osm.Way;
    49 import org.openstreetmap.josm.gui.ExtendedDialog;
    50 import org.openstreetmap.josm.tools.GBC;
     49import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog;
     50import org.openstreetmap.josm.tools.Pair;
    5151import org.openstreetmap.josm.tools.Shortcut;
    5252
     
    5656    private int cmdsCount = 0;
    5757
     58
    5859    /**
    5960     * This helper class describes join ares action result.
     
    6364    public static class JoinAreasResult {
    6465
    65         public Way outerWay;
    66         public List<Way> innerWays;
    67 
    6866        public boolean mergeSuccessful;
    6967        public boolean hasChanges;
    7068        public boolean hasRelationProblems;
    71     }
    72 
    73     // HelperClass
    74     // Saves a node and two positions where to insert the node into the ways
    75     private static class NodeToSegs implements Comparable<NodeToSegs> {
    76         public int pos;
    77         public Node n;
    78         public double dis;
    79         public NodeToSegs(int pos, Node n, LatLon dis) {
    80             this.pos = pos;
    81             this.n = n;
    82             this.dis = n.getCoor().greatCircleDistance(dis);
    83         }
    84 
    85         public int compareTo(NodeToSegs o) {
    86             if(this.pos == o.pos)
    87                 return (this.dis - o.dis) > 0 ? 1 : -1;
    88                 return this.pos - o.pos;
    89         }
    90 
    91         @Override
    92         public int hashCode() {
    93             return pos;
    94         }
    95 
    96         @Override
    97         public boolean equals(Object o) {
    98             if (o instanceof NodeToSegs)
    99                 return compareTo((NodeToSegs) o) == 0;
    100             else
    101                 return false;
     69
     70        public List<Multipolygon> polygons;
     71    }
     72
     73    public static class Multipolygon {
     74        public Way outerWay;
     75        public List<Way> innerWays;
     76
     77        public Relation relation;
     78
     79        public Multipolygon(Way way) {
     80            outerWay = way;
     81            innerWays = new ArrayList<Way>();
    10282        }
    10383    }
     
    126106    }
    127107
    128     /**
    129      * HelperClass
    130      * saves a way and the "inside" side
    131      * insideToTheLeft: if true left side is "in", false -right side is "in".
    132      * Left and right are determined along the orientation of way.
    133      */
    134     private static class WayInPath {
     108
     109    //HelperClass
     110    //saves a way and the "inside" side
     111    // insideToTheLeft: if true left side is "in", false -right side is "in". Left and right are determined along the orientation of way.
     112    private static class WayInPolygon {
    135113        public final Way way;
    136         public boolean insideToTheLeft;
    137 
    138         public WayInPath(Way way, boolean insideLeft) {
    139             this.way = way;
    140             this.insideToTheLeft = insideLeft;
     114        public boolean insideToTheRight;
     115
     116        public WayInPolygon(Way _way, boolean _insideRight) {
     117            this.way = _way;
     118            this.insideToTheRight = _insideRight;
    141119        }
    142120
     
    148126        @Override
    149127        public boolean equals(Object other) {
    150             if (!(other instanceof WayInPath))
    151                 return false;
    152             WayInPath otherMember = (WayInPath) other;
    153             return otherMember.way.equals(this.way) && otherMember.insideToTheLeft == this.insideToTheLeft;
    154         }
    155     }
     128            if (!(other instanceof WayInPolygon)) return false;
     129            WayInPolygon otherMember = (WayInPolygon) other;
     130            return otherMember.way.equals(this.way) && otherMember.insideToTheRight == this.insideToTheRight;
     131        }
     132    }
     133
     134    /**
     135     * This helper class describes a polygon, assembled from several ways.
     136     * @author viesturs
     137     *
     138     */
     139    private static class AssembledPolygon {
     140        public List<WayInPolygon> ways;
     141
     142        public AssembledPolygon(List<WayInPolygon> boundary) {
     143            this.ways = boundary;
     144        }
     145
     146        public List<Node> getNodes() {
     147            List<Node> nodes = new ArrayList<Node>();
     148            for (WayInPolygon way : this.ways) {
     149                //do not add the last node as it will be repeated in the next way
     150                if (way.insideToTheRight) {
     151                    for (int pos = 0; pos < way.way.getNodesCount() - 1; pos++) {
     152                        nodes.add(way.way.getNode(pos));
     153                    }
     154                }
     155                else {
     156                    for (int pos = way.way.getNodesCount() - 1; pos > 0; pos--) {
     157                        nodes.add(way.way.getNode(pos));
     158                    }
     159                }
     160            }
     161
     162            return nodes;
     163        }
     164    }
     165
     166
     167    public static class AssembledMultipolygon {
     168        public AssembledPolygon outerWay;
     169        public List<AssembledPolygon> innerWays;
     170
     171        public AssembledMultipolygon(AssembledPolygon way) {
     172            outerWay = way;
     173            innerWays = new ArrayList<AssembledPolygon>();
     174        }
     175    }
     176
     177    /**
     178     * This hepler class implements algorithm traversing trough connected ways.
     179     * Assumes you are going in clockwise orientation.
     180     * @author viesturs
     181     *
     182     */
     183    private static class WayTraverser {
     184
     185        private Set<WayInPolygon> availableWays;
     186        private WayInPolygon lastWay;
     187        private boolean lastWayReverse;
     188
     189        public WayTraverser(Collection<WayInPolygon> ways) {
     190
     191            availableWays = new HashSet<WayInPolygon>(ways);
     192            lastWay = null;
     193        }
     194
     195        public void removeWays(Collection<WayInPolygon> ways) {
     196            availableWays.removeAll(ways);
     197        }
     198
     199        public boolean hasWays() {
     200            return availableWays.size() > 0;
     201        }
     202
     203        public WayInPolygon startNewWay(WayInPolygon way) {
     204            lastWay = way;
     205            lastWayReverse = !lastWay.insideToTheRight;
     206
     207            return lastWay;
     208        }
     209
     210        public WayInPolygon startNewWay() {
     211            if (availableWays.size() == 0) {
     212                lastWay = null;
     213            } else {
     214                lastWay = availableWays.iterator().next();
     215                lastWayReverse = !lastWay.insideToTheRight;
     216            }
     217
     218            return lastWay;
     219        }
     220
     221
     222        public  WayInPolygon advanceNextLeftmostWay() {
     223            return advanceNextWay(false);
     224        }
     225
     226        public  WayInPolygon advanceNextRightmostWay() {
     227            return advanceNextWay(true);
     228        }
     229
     230        private WayInPolygon advanceNextWay(boolean rightmost) {
     231
     232            Node headNode = !lastWayReverse ? lastWay.way.lastNode() : lastWay.way.firstNode();
     233            Node prevNode = !lastWayReverse ? lastWay.way.getNode(lastWay.way.getNodesCount() - 2) : lastWay.way.getNode(1);
     234
     235            //find best next way
     236            WayInPolygon bestWay = null;
     237            Node bestWayNextNode = null;
     238            boolean bestWayReverse = false;
     239
     240            for (WayInPolygon way : availableWays) {
     241                if (way.way.firstNode().equals(headNode)) {
     242                    //start adjacent to headNode
     243                    Node nextNode = way.way.getNode(1);
     244
     245                    if (nextNode.equals(prevNode))
     246                    {
     247                        //this is the path we came from - ignore it.
     248                    }
     249                    else if (bestWay == null || (isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) {
     250                        //the new way is better
     251                        bestWay = way;
     252                        bestWayReverse = false;
     253                        bestWayNextNode = nextNode;
     254                    }
     255                }
     256
     257                if (way.way.lastNode().equals(headNode)) {
     258                    //end adjacent to headNode
     259                    Node nextNode = way.way.getNode(way.way.getNodesCount() - 2);
     260
     261                    if (nextNode.equals(prevNode)) {
     262                        //this is the path we came from - ignore it.
     263                    }
     264                    else if (bestWay == null || (isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) {
     265                        //the new way is better
     266                        bestWay = way;
     267                        bestWayReverse = true;
     268                        bestWayNextNode = nextNode;
     269                    }
     270                }
     271            }
     272
     273            lastWay = bestWay;
     274            lastWayReverse = bestWayReverse;
     275
     276            return lastWay;
     277        }
     278
     279        public boolean isLastWayInsideToTheRight() {
     280            return lastWayReverse != lastWay.insideToTheRight;
     281        }
     282
     283        public Node getLastWayStartNode() {
     284            return lastWayReverse ? lastWay.way.lastNode() : lastWay.way.firstNode();
     285        }
     286
     287        public Node getLastWayEndNode() {
     288            return lastWayReverse ? lastWay.way.firstNode() : lastWay.way.lastNode();
     289        }
     290    }
     291
     292    /**
     293     * Provides some node order , based on coordinates, nodes with equal coordinates are equal.
     294     * @author viesturs
     295     *
     296     */
     297    private class NodePositionComparator implements Comparator<Node> {
     298
     299        @Override
     300        public int compare(Node n1, Node n2) {
     301
     302            double dLat = n1.getCoor().lat() - n2.getCoor().lat();
     303            double dLon = n1.getCoor().lon() - n2.getCoor().lon();
     304
     305            if (dLat > 0)
     306                return 1;
     307            else if (dLat < 0)
     308                return -1;
     309            else if (dLon == 0) //dlat is 0 here
     310                return 0;
     311            else
     312                return dLon > 0 ? 1 : -1;
     313        }
     314    }
     315
     316
     317    /**
     318     * Helper storage class for finding findOuterWays
     319     * @author viesturs
     320     */
     321    static class PolygonLevel {
     322        public final int level;
     323        public final AssembledMultipolygon pol;
     324
     325        public PolygonLevel(AssembledMultipolygon _pol, int _level) {
     326            pol = _pol;
     327            level = _level;
     328        }
     329    }
     330
    156331
    157332    // Adds the menu entry, Shortcuts, etc.
     
    173348        }
    174349
    175         // Too many ways
    176         if(ways.size() > 2) {
    177             JOptionPane.showMessageDialog(Main.parent, tr("Only up to two areas can be joined at the moment."));
    178             return;
    179         }
    180 
    181350        List<Node> allNodes = new ArrayList<Node>();
    182         for (Way way: ways) {
    183             if(!way.isClosed()) {
     351        for (Way way : ways) {
     352            if (!way.isClosed()) {
    184353                JOptionPane.showMessageDialog(Main.parent, tr("\"{0}\" is not closed and therefore cannot be joined.", way.getName()));
    185354                return;
     
    192361        Area dataSourceArea = Main.main.getCurrentDataSet().getDataSourceArea();
    193362        if (dataSourceArea != null) {
    194             for (Node node: allNodes) {
     363            for (Node node : allNodes) {
    195364                if (!dataSourceArea.contains(node.getCoor())) {
    196365                    int option = JOptionPane.showConfirmDialog(Main.parent,
     
    209378        }
    210379
    211         if (checkForTagConflicts(ways.getFirst(), ways.getLast()))
    212             //there was conflicts and user canceled abort the action.
     380        //analyze multipolygon relations and collect all areas
     381        List<Multipolygon> areas = collectMultipolygons(ways);
     382
     383        if (areas == null)
     384            //too complex multipolygon relations found
    213385            return;
    214386
    215 
    216         JoinAreasResult result = joinAreas(ways.getFirst(), ways.getLast());
    217 
    218         if (result.hasChanges) {
    219             Main.map.mapView.repaint();
    220             DataSet ds = Main.main.getCurrentDataSet();
    221             ds.fireSelectionChanged();
    222         } else {
     387        if (!testJoin(areas)) {
    223388            JOptionPane.showMessageDialog(Main.parent, tr("No intersection found. Nothing was changed."));
    224         }
    225     }
    226 
    227     /**
    228      * Will join two overlapping areas
    229      * @param Way First way/area
    230      * @param Way Second way/area
    231      */
    232     private JoinAreasResult joinAreas(Way a, Way b) {
     389            return;
     390        }
     391
     392        if (!resolveTagConflicts(areas))
     393            return;
     394        //user cancelled, do nothing.
     395
     396        try {
     397            JoinAreasResult result = joinAreas(areas);
     398
     399            if (result.hasChanges) {
     400
     401                Main.map.mapView.repaint();
     402                DataSet ds = Main.main.getCurrentDataSet();
     403                ds.fireSelectionChanged();
     404            } else {
     405                JOptionPane.showMessageDialog(Main.parent, tr("No intersection found. Nothing was changed."));
     406            }
     407        }
     408        catch (UserCancelException exception) {
     409            //revert changes
     410            //FIXME: this is dirty hack
     411            makeCommitsOneAction(tr("Reverting changes"));
     412            Main.main.undoRedo.undo();
     413            Main.main.undoRedo.redoCommands.clear();
     414        }
     415    }
     416
     417
     418    /**
     419     * Tests if the areas have some intersections to join.
     420     * @param areas
     421     * @return
     422     */
     423    private boolean testJoin(List<Multipolygon> areas) {
     424        List<Way> allStartingWays = new ArrayList<Way>();
     425
     426        for (Multipolygon area : areas) {
     427            allStartingWays.add(area.outerWay);
     428            allStartingWays.addAll(area.innerWays);
     429        }
     430
     431        //find intersection points
     432        ArrayList<Node> nodes = addIntersections(allStartingWays, true);
     433        return nodes.size() > 0;
     434    }
     435
     436    /**
     437     * Will join two or more overlapping areas
     438     * @param areas - list of areas to join
     439     * @return new area formed.
     440     */
     441    private JoinAreasResult joinAreas(List<Multipolygon> areas) throws UserCancelException {
    233442
    234443        JoinAreasResult result = new JoinAreasResult();
    235444        result.hasChanges = false;
    236445
    237         // Fix self-overlapping first or other errors
    238         boolean same = a.equals(b);
    239         if(!same) {
    240             int i = 0;
    241 
    242             //join each area with itself, fixing self-crossings.
    243             JoinAreasResult resultA = joinAreas(a, a);
    244             JoinAreasResult resultB = joinAreas(b, b);
    245 
    246             if (resultA.mergeSuccessful) {
    247                 a = resultA.outerWay;
    248                 ++i;
    249             }
    250             if(resultB.mergeSuccessful) {
    251                 b = resultB.outerWay;
    252                 ++i;
    253             }
    254 
    255             result.hasChanges = i > 0;
    256             cmdsCount = i;
    257         }
    258 
    259         ArrayList<Node> nodes = addIntersections(a, b);
     446        List<Way> allStartingWays = new ArrayList<Way>();
     447        List<Way> innerStartingWays = new ArrayList<Way>();
     448        List<Way> outerStartingWays = new ArrayList<Way>();
     449
     450        for (Multipolygon area : areas) {
     451            outerStartingWays.add(area.outerWay);
     452            innerStartingWays.addAll(area.innerWays);
     453        }
     454
     455        allStartingWays.addAll(innerStartingWays);
     456        allStartingWays.addAll(outerStartingWays);
     457
     458        //first remove nodes in the same coordinate
     459        boolean removedDuplicates = false;
     460        removedDuplicates |= removeDuplicateNodes(allStartingWays);
     461
     462        if (removedDuplicates) {
     463            result.hasChanges = true;
     464            commitCommands(marktr("Removed duplicate nodes"));
     465        }
     466
     467        //find intersection points
     468        ArrayList<Node> nodes = addIntersections(allStartingWays, false);
    260469
    261470        //no intersections, return.
    262         if(nodes.size() == 0) return result;
     471        if (nodes.size() == 0) return result;
    263472        commitCommands(marktr("Added node on all intersections"));
    264473
     474        ArrayList<RelationRole> relations = new ArrayList<RelationRole>();
     475
    265476        // Remove ways from all relations so ways can be combined/split quietly
    266         ArrayList<RelationRole> relations = removeFromRelations(a);
    267         if(!same) {
    268             relations.addAll(removeFromRelations(b));
     477        for (Way way : allStartingWays) {
     478            relations.addAll(removeFromAllRelations(way));
    269479        }
    270480
    271481        // Don't warn now, because it will really look corrupted
    272         boolean warnAboutRelations = relations.size() > 0;
    273 
    274         ArrayList<Way> allWays = splitWaysOnNodes(a, b, nodes);
    275 
    276         // Find inner ways save them to a list
    277         ArrayList<WayInPath> outerWays = findOuterWays(allWays);
    278         ArrayList<Way> innerWays = findInnerWays(allWays, outerWays);
    279 
    280         // Join outer ways
    281         Way outerWay = joinOuterWays(outerWays);
    282 
    283         // Fix Multipolygons if there are any
    284         List<Way> newInnerWays = fixMultipolygons(innerWays, outerWay, same);
    285 
    286         // Delete the remaining inner ways
    287         if(innerWays != null && innerWays.size() > 0) {
    288             cmds.add(DeleteCommand.delete(Main.map.mapView.getEditLayer(), innerWays, true));
     482        boolean warnAboutRelations = relations.size() > 0 && allStartingWays.size() > 1;
     483
     484        ArrayList<WayInPolygon> preparedWays = new ArrayList<WayInPolygon>();
     485
     486        for (Way way : outerStartingWays) {
     487            ArrayList<Way> splitWays = splitWayOnNodes(way, nodes);
     488            preparedWays.addAll(markWayInsideSide(splitWays, false));
     489        }
     490
     491        for (Way way : innerStartingWays) {
     492            ArrayList<Way> splitWays = splitWayOnNodes(way, nodes);
     493            preparedWays.addAll(markWayInsideSide(splitWays, true));
     494        }
     495
     496        // Find boundary ways
     497        ArrayList<Way> discardedWays = new ArrayList<Way>();
     498        List<AssembledPolygon> bounadries = findBoundaryPolygons(preparedWays, discardedWays);
     499
     500        //find polygons
     501        List<AssembledMultipolygon> preparedPolygons = findPolygons(bounadries);
     502
     503        //assemble final ways
     504        List<Multipolygon> polygons = new ArrayList<Multipolygon>();
     505        for (AssembledMultipolygon pol : preparedPolygons) {
     506            polygons.add(joinPolygon(pol));
     507        }
     508
     509        // Delete the discarded inner ways
     510        if (discardedWays.size() > 0) {
     511            cmds.add(DeleteCommand.delete(Main.map.mapView.getEditLayer(), discardedWays, true));
    289512        }
    290513        commitCommands(marktr("Delete Ways that are not part of an inner multipolygon"));
    291514
    292515        // We can attach our new multipolygon relation and pretend it has always been there
    293         addOwnMultigonRelation(newInnerWays, outerWay, relations);
    294         fixRelations(relations, outerWay);
     516        for (Multipolygon pol : polygons) {
     517            addOwnMultigonRelation(pol.innerWays, pol.outerWay, relations);
     518            fixRelations(relations, pol.outerWay);
     519        }
     520
    295521        commitCommands(marktr("Fix relations"));
    296522
    297         stripTags(newInnerWays);
    298 
    299         makeCommitsOneAction(
    300                 same
    301                 ? marktr("Joined self-overlapping area")
    302                         : marktr("Joined overlapping areas")
    303         );
    304 
    305         if(warnAboutRelations) {
     523        for (Multipolygon pol : polygons) {
     524            stripTags(pol.innerWays);
     525        }
     526
     527        makeCommitsOneAction(marktr("Joined overlapping areas"));
     528
     529        if (warnAboutRelations) {
    306530            JOptionPane.showMessageDialog(Main.parent, tr("Some of the ways were part of relations that have been modified. Please verify no errors have been introduced."));
    307531        }
     
    309533        result.hasChanges = true;
    310534        result.mergeSuccessful = true;
    311         result.outerWay = outerWay;
    312         result.innerWays = newInnerWays;
    313 
     535        result.polygons = polygons;
    314536        return result;
    315537    }
     
    319541     * @param Way First way to check
    320542     * @param Way Second Way to check
    321      * @return boolean True if not all conflicts could be resolved, False if everything's fine
    322      */
    323     private boolean checkForTagConflicts(Way a, Way b) {
    324         ArrayList<Way> ways = new ArrayList<Way>();
    325         ways.add(a);
    326         ways.add(b);
    327 
    328         // FIXME: This is mostly copied and pasted from CombineWayAction.java and one day should be moved into tools
    329         // We have TagCollection handling for that now - use it here as well
    330         Map<String, Set<String>> props = new TreeMap<String, Set<String>>();
    331         for (Way w : ways) {
    332             for (String key: w.keySet()) {
    333                 if (!props.containsKey(key)) {
    334                     props.put(key, new TreeSet<String>());
    335                 }
    336                 props.get(key).add(w.get(key));
    337             }
    338         }
    339 
    340         Way ax = new Way(a);
    341         Way bx = new Way(b);
    342 
    343         Map<String, JComboBox> components = new HashMap<String, JComboBox>();
    344         JPanel p = new JPanel(new GridBagLayout());
    345         for (Entry<String, Set<String>> e : props.entrySet()) {
    346             if (TigerUtils.isTigerTag(e.getKey())) {
    347                 String combined = TigerUtils.combineTags(e.getKey(), e.getValue());
    348                 ax.put(e.getKey(), combined);
    349                 bx.put(e.getKey(), combined);
    350             } else if (e.getValue().size() > 1) {
    351                 if("created_by".equals(e.getKey()))
    352                 {
    353                     ax.remove("created_by");
    354                     bx.remove("created_by");
     543     * @return boolean True if all conflicts are resolved, False if conflicts remain.
     544     */
     545    private boolean resolveTagConflicts(List<Multipolygon> polygons) {
     546
     547        List<Way> ways = new ArrayList<Way>();
     548
     549        for (Multipolygon pol : polygons) {
     550            ways.add(pol.outerWay);
     551            ways.addAll(pol.innerWays);
     552        }
     553
     554        if (ways.size() < 2)
     555            return true;
     556
     557        //mostly copied from CombineWayAction.java.
     558        TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways);
     559        TagCollection completeWayTags = new TagCollection(wayTags);
     560        combineTigerTags(completeWayTags);
     561        normalizeTagCollectionBeforeEditing(completeWayTags, ways);
     562        TagCollection tagsToEdit = new TagCollection(completeWayTags);
     563        completeTagCollectionForEditing(tagsToEdit);
     564
     565        CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
     566        dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues());
     567        dialog.setTargetPrimitive(ways.get(0));
     568        Collection<Relation> parentRelations = CombineWayAction.getParentRelations(ways);
     569        parentRelations = filterOwnMultipolygonRelations(parentRelations, polygons);
     570        dialog.getRelationMemberConflictResolverModel().populate(
     571                parentRelations,
     572                ways
     573        );
     574        dialog.prepareDefaultDecisions();
     575
     576        // resolve tag conflicts if necessary
     577        //
     578        if (!completeWayTags.isApplicableToPrimitive() || !parentRelations.isEmpty()) {
     579            dialog.setVisible(true);
     580            if (dialog.isCancelled())
     581                return false;
     582        }
     583
     584        for (Way way : ways) {
     585            dialog.setTargetPrimitive(way);
     586            cmds.addAll(dialog.buildResolutionCommands());
     587        }
     588
     589        commitCommands(marktr("Fix tag conflicts"));
     590        return true;
     591    }
     592
     593
     594    /**
     595     * This method removes duplicate points (if any) from the input way.
     596     * @param way the way to process
     597     * @return true if any changes where made
     598     */
     599    private boolean removeDuplicateNodes(List<Way> ways) {
     600        //TODO: maybe join nodes with JoinNodesAction, rather than reconnect the ways.
     601
     602        Map<Node, Node> nodeMap = new TreeMap<Node, Node>(new NodePositionComparator());
     603        int totalNodesRemoved = 0;
     604
     605        for (Way way : ways) {
     606            if (way.getNodes().size() < 2) {
     607                continue;
     608            }
     609
     610            int nodesRemoved = 0;
     611            List<Node> newNodes = new ArrayList<Node>();
     612            Node prevNode = null;
     613
     614            for (Node node : way.getNodes()) {
     615                if (!nodeMap.containsKey(node)) {
     616                    //new node
     617                    nodeMap.put(node, node);
     618
     619                    //avoid duplicate nodes
     620                    if (prevNode != node) {
     621                        newNodes.add(node);
     622                    } else {
     623                        nodesRemoved ++;
     624                    }
    355625                } else {
    356                     JComboBox c = new JComboBox(e.getValue().toArray());
    357                     c.setEditable(true);
    358                     p.add(new JLabel(e.getKey()), GBC.std());
    359                     p.add(Box.createHorizontalStrut(10), GBC.std());
    360                     p.add(c, GBC.eol());
    361                     components.put(e.getKey(), c);
    362                 }
    363             } else {
    364                 String val = e.getValue().iterator().next();
    365                 ax.put(e.getKey(), val);
    366                 bx.put(e.getKey(), val);
    367             }
    368         }
    369 
    370         if (components.isEmpty())
    371             return false; // No conflicts found
    372 
    373         ExtendedDialog ed = new ExtendedDialog(Main.parent,
    374                 tr("Enter values for all conflicts."),
    375                 new String[] {tr("Solve Conflicts"), tr("Cancel")});
    376         ed.setButtonIcons(new String[] {"dialogs/conflict.png", "cancel.png"});
    377         ed.setContent(p);
    378         ed.showDialog();
    379 
    380         if (ed.getValue() != 1) return true; // user cancel, unresolvable conflicts
    381 
    382         for (Entry<String, JComboBox> e : components.entrySet()) {
    383             String val = e.getValue().getEditor().getItem().toString();
    384             ax.put(e.getKey(), val);
    385             bx.put(e.getKey(), val);
    386         }
    387 
    388         cmds.add(new ChangeCommand(a, ax));
    389         cmds.add(new ChangeCommand(b, bx));
    390         commitCommands(marktr("Fix tag conflicts"));
    391         return false;
    392     }
    393 
    394     /**
    395      * Will find all intersection and add nodes there for two given ways
    396      * @param Way First way
    397      * @param Way Second way
    398      * @return ArrayList<OsmPrimitive> List of new nodes
    399      */
    400     private ArrayList<Node> addIntersections(Way a, Way b) {
    401         boolean same = a.equals(b);
    402         int nodesSizeA = a.getNodesCount();
    403         int nodesSizeB = b.getNodesCount();
    404 
    405         ArrayList<Node> nodes = new ArrayList<Node>();
    406         ArrayList<NodeToSegs> nodesA = new ArrayList<NodeToSegs>();
    407         ArrayList<NodeToSegs> nodesB = new ArrayList<NodeToSegs>();
    408 
    409         for (int i = (same ? 1 : 0); i < nodesSizeA - 1; i++) {
    410             for (int j = (same ? i + 2 : 0); j < nodesSizeB - 1; j++) {
    411                 // Avoid re-adding nodes that already exist on (some) intersections
    412                 if(a.getNode(i).equals(b.getNode(j)) || a.getNode(i+1).equals(b.getNode(j)))   {
    413                     nodes.add(b.getNode(j));
    414                     continue;
    415                 } else
    416                     if(a.getNode(i).equals(b.getNode(j+1)) || a.getNode(i+1).equals(b.getNode(j+1))) {
    417                         nodes.add(b.getNode(j+1));
    418                         continue;
     626                    //node with same coordinates already exists, substitute with existing node
     627                    Node representator = nodeMap.get(node);
     628
     629                    if (representator != node) {
     630                        nodesRemoved ++;
    419631                    }
    420                 LatLon intersection = getLineLineIntersection(
    421                         a.getNode(i)  .getEastNorth().east(), a.getNode(i)  .getEastNorth().north(),
    422                         a.getNode(i+1).getEastNorth().east(), a.getNode(i+1).getEastNorth().north(),
    423                         b.getNode(j)  .getEastNorth().east(), b.getNode(j)  .getEastNorth().north(),
    424                         b.getNode(j+1).getEastNorth().east(), b.getNode(j+1).getEastNorth().north());
    425                 if(intersection == null) {
    426                     continue;
    427                 }
    428 
    429                 // Create the node. Adding them to the ways must be delayed because we still loop over them
    430                 Node n = new Node(intersection);
    431                 cmds.add(new AddCommand(n));
    432                 nodes.add(n);
    433                 // The distance is needed to sort and add the nodes in direction of the way
    434                 nodesA.add(new NodeToSegs(i,  n, a.getNode(i).getCoor()));
    435                 if(same) {
    436                     nodesA.add(new NodeToSegs(j,  n, a.getNode(j).getCoor()));
    437                 } else {
    438                     nodesB.add(new NodeToSegs(j,  n, b.getNode(j).getCoor()));
    439                 }
    440             }
    441         }
    442 
    443         addNodesToWay(a, nodesA);
    444         if(!same) {
    445             addNodesToWay(b, nodesB);
    446         }
    447 
    448         return nodes;
     632
     633                    //avoid duplicate node
     634                    if (prevNode != representator) {
     635                        newNodes.add(representator);
     636                    }
     637                }
     638                prevNode = node;
     639            }
     640
     641            if (nodesRemoved > 0) {
     642
     643                if (newNodes.size() == 1) { //all nodes in the same coordinate - add one more node, to have closed way.
     644                    newNodes.add(newNodes.get(0));
     645                }
     646
     647                Way newWay=new Way(way);
     648                newWay.setNodes(newNodes);
     649                cmds.add(new ChangeCommand(way, newWay));
     650                totalNodesRemoved += nodesRemoved;
     651            }
     652        }
     653
     654        return totalNodesRemoved > 0;
     655    }
     656
     657
     658
     659    /**
     660     * Will find all intersection and add nodes there for list of given ways. Handles self-intersections too.
     661     * And make commands to add the intersection points to ways.
     662     * @param List<Way> - a list of ways to test
     663     * @return ArrayList<Node> List of new nodes
     664     * Prerequisite: no two nodes have the same coordinates.
     665     */
     666    private ArrayList<Node> addIntersections(List<Way> ways, boolean test) {
     667        //TODO: this is a bit slow - O( (number of nodes)^2 + numberOfIntersections * numberOfNodes )
     668
     669        //stupid java, cannot instantiate array of generic classes..
     670        @SuppressWarnings("unchecked")
     671        ArrayList<Node>[] newNodes = new ArrayList[ways.size()];
     672        boolean[] changedWays = new boolean[ways.size()];
     673
     674        Set<Node> intersectionNodes = new LinkedHashSet<Node>();
     675
     676        for (int pos = 0; pos < ways.size(); pos ++) {
     677            newNodes[pos] = new ArrayList<Node>(ways.get(pos).getNodes());
     678            changedWays[pos] = false;
     679        }
     680
     681        //iterate over all segment pairs and introduce the intersections
     682
     683        Comparator<Node> coordsComparator = new NodePositionComparator();
     684
     685        int seg1Way = 0;
     686        int seg1Pos = -1;
     687
     688        while (true) {
     689            //advance to next segment
     690            seg1Pos++;
     691            if (seg1Pos > newNodes[seg1Way].size() - 2) {
     692                seg1Way++;
     693                seg1Pos = 0;
     694
     695                if (seg1Way == ways.size()) { //finished
     696                    break;
     697                }
     698            }
     699
     700
     701            //iterate over secondary segment
     702
     703            int seg2Way = seg1Way;
     704            int seg2Pos = seg1Pos + 1;//skip the adjacent segment
     705
     706            while (true) {
     707
     708                //advance to next segment
     709                seg2Pos++;
     710                if (seg2Pos > newNodes[seg2Way].size() - 2) {
     711                    seg2Way++;
     712                    seg2Pos = 0;
     713
     714                    if (seg2Way == ways.size()) { //finished
     715                        break;
     716                    }
     717                }
     718
     719                //need to get them again every time, because other segments may be changed
     720                Node seg1Node1 = newNodes[seg1Way].get(seg1Pos);
     721                Node seg1Node2 = newNodes[seg1Way].get(seg1Pos + 1);
     722                Node seg2Node1 = newNodes[seg2Way].get(seg2Pos);
     723                Node seg2Node2 = newNodes[seg2Way].get(seg2Pos + 1);
     724
     725                int commonCount = 0;
     726                //test if we have common nodes to add.
     727                if (seg1Node1 == seg2Node1 || seg1Node1 == seg2Node2) {
     728                    commonCount ++;
     729
     730                    if (seg1Way == seg2Way &&
     731                            seg1Pos == 0 &&
     732                            seg2Pos == newNodes[seg2Way].size() -2) {
     733                        //do not add - this is first and last segment of the same way.
     734                    } else {
     735                        intersectionNodes.add(seg1Node1);
     736                    }
     737                }
     738
     739                if (seg1Node2 == seg2Node1 || seg1Node2 == seg2Node2) {
     740                    commonCount ++;
     741
     742                    intersectionNodes.add(seg1Node2);
     743                }
     744
     745                //no common nodes - find intersection
     746                if (commonCount == 0) {
     747                    LatLon intersection = getLineLineIntersection(
     748                            seg1Node1.getEastNorth().east(), seg1Node1.getEastNorth().north(),
     749                            seg1Node2.getEastNorth().east(), seg1Node2.getEastNorth().north(),
     750                            seg2Node1.getEastNorth().east(), seg2Node1.getEastNorth().north(),
     751                            seg2Node2.getEastNorth().east(), seg2Node2.getEastNorth().north());
     752
     753                    if (intersection != null) {
     754                        if (test) {
     755                            intersectionNodes.add(seg2Node1);
     756                            return new ArrayList<Node>(intersectionNodes);
     757                        }
     758
     759                        Node newNode = new Node(intersection);
     760                        Node intNode = newNode;
     761                        boolean insertInSeg1 = false;
     762                        boolean insertInSeg2 = false;
     763
     764                        //find if the intersection point is at end point of one of the segments, if so use that point
     765
     766                        //segment 1
     767                        if (coordsComparator.compare(newNode, seg1Node1) == 0) {
     768                            intNode = seg1Node1;
     769                        } else if (coordsComparator.compare(newNode, seg1Node2) == 0) {
     770                            intNode = seg1Node2;
     771                        } else {
     772                            insertInSeg1 = true;
     773                        }
     774
     775                        //segment 2
     776                        if (coordsComparator.compare(newNode, seg2Node1) == 0) {
     777                            intNode = seg2Node1;
     778                        } else if (coordsComparator.compare(newNode, seg2Node2) == 0) {
     779                            intNode = seg2Node2;
     780                        } else {
     781                            insertInSeg2 = true;
     782                        }
     783
     784                        if (insertInSeg1) {
     785                            newNodes[seg1Way].add(seg1Pos +1, intNode);
     786                            changedWays[seg1Way] = true;
     787
     788                            //fix seg2 position, as indexes have changed, seg2Pos is always bigger than seg1Pos on the same segment.
     789                            if (seg2Way == seg1Way) {
     790                                seg2Pos ++;
     791                            }
     792                        }
     793
     794                        if (insertInSeg2) {
     795                            newNodes[seg2Way].add(seg2Pos +1, intNode);
     796                            changedWays[seg2Way] = true;
     797
     798                            //Do not need to compare again to already split segment
     799                            seg2Pos ++;
     800                        }
     801
     802                        intersectionNodes.add(intNode);
     803
     804                        if (intNode == newNode) {
     805                            cmds.add(new AddCommand(intNode));
     806                        }
     807                    }
     808                }
     809                else if (test && intersectionNodes.size() > 0)
     810                    return new ArrayList<Node>(intersectionNodes);
     811            }
     812        }
     813
     814        for (int pos = 0; pos < ways.size(); pos ++) {
     815            if (changedWays[pos] == false) {
     816                continue;
     817            }
     818
     819            Way way = ways.get(pos);
     820            Way newWay = new Way(way);
     821            newWay.setNodes(newNodes[pos]);
     822
     823            cmds.add(new ChangeCommand(way, newWay));
     824        }
     825
     826        return new ArrayList<Node>(intersectionNodes);
    449827    }
    450828
     
    470848        // Solve the equations
    471849        double det = a1*b2 - a2*b1;
    472         if(det == 0) return null; // Lines are parallel
     850        if (det == 0) return null; // Lines are parallel
    473851
    474852        return Main.proj.eastNorth2latlon(new EastNorth(
     
    478856    }
    479857
    480     /**
    481      * Inserts given nodes with positions into the given ways
    482      * @param Way The way to insert the nodes into
    483      * @param Collection<NodeToSegs> The list of nodes with positions to insert
    484      */
    485     private void addNodesToWay(Way a, ArrayList<NodeToSegs> nodes) {
    486         if(nodes.size() == 0)
    487             return;
    488         Way ax=new Way(a);
    489         Collections.sort(nodes);
    490 
    491         int numOfAdds = 1;
    492         for(NodeToSegs n : nodes) {
    493             ax.addNode(n.pos + numOfAdds, n.n);
    494             numOfAdds++;
    495         }
    496 
    497         cmds.add(new ChangeCommand(a, ax));
    498     }
     858
    499859
    500860    /**
     
    519879    }
    520880
    521     /**
    522      * Removes a given OsmPrimitive from all relations
    523      * @param OsmPrimitive Element to remove from all relations
    524      * @return ArrayList<RelationRole> List of relations with roles the primitives was part of
    525      */
    526     private ArrayList<RelationRole> removeFromRelations(OsmPrimitive osm) {
    527         ArrayList<RelationRole> result = new ArrayList<RelationRole>();
    528         for (Relation r : Main.main.getCurrentDataSet().getRelations()) {
    529             if (r.isDeleted()) {
    530                 continue;
    531             }
    532             for (RelationMember rm : r.getMembers()) {
    533                 if (rm.getMember() != osm) {
     881
     882    /**
     883     * This method analyzes the way and assigns each part what direction polygon "inside" is.
     884     * @param parts the split parts of the way
     885     * @param isInner - if true, reverts the direction (for multipolygon islands)
     886     * @return list of parts, marked with the inside orientation.
     887     */
     888    private ArrayList<WayInPolygon> markWayInsideSide(List<Way> parts, boolean isInner) {
     889
     890        ArrayList<WayInPolygon> result = new ArrayList<WayInPolygon>();
     891
     892        //prepare prev and next maps
     893        Map<Way, Way> nextWayMap = new HashMap<Way, Way>();
     894        Map<Way, Way> prevWayMap = new HashMap<Way, Way>();
     895
     896        for (int pos = 0; pos < parts.size(); pos ++) {
     897
     898            if (!parts.get(pos).lastNode().equals(parts.get((pos + 1) % parts.size()).firstNode()))
     899                throw new RuntimeException("Way not circular");
     900
     901            nextWayMap.put(parts.get(pos), parts.get((pos + 1) % parts.size()));
     902            prevWayMap.put(parts.get(pos), parts.get((pos + parts.size() - 1) % parts.size()));
     903        }
     904
     905        //find the node with minimum y - it's guaranteed to be outer. (What about the south pole?)
     906        Way topWay = null;
     907        Node topNode = null;
     908        int topIndex = 0;
     909        double minY = Double.POSITIVE_INFINITY;
     910
     911        for (Way way : parts) {
     912            for (int pos = 0; pos < way.getNodesCount(); pos ++) {
     913                Node node = way.getNode(pos);
     914
     915                if (node.getEastNorth().getY() < minY) {
     916                    minY = node.getEastNorth().getY();
     917                    topWay = way;
     918                    topNode = node;
     919                    topIndex = pos;
     920                }
     921            }
     922        }
     923
     924        //get the upper way and it's orientation.
     925
     926        boolean wayClockwise; // orientation of the top way.
     927
     928        if (topNode.equals(topWay.firstNode()) || topNode.equals(topWay.lastNode())) {
     929            Node headNode = null; // the node at junction
     930            Node prevNode = null; // last node from previous path
     931            wayClockwise = false;
     932
     933            //node is in split point - find the outermost way from this point
     934
     935            headNode = topNode;
     936            //make a fake node that is downwards from head node (smaller Y). It will be a division point between paths.
     937            prevNode = new Node(new EastNorth(headNode.getEastNorth().getX(), headNode.getEastNorth().getY() - 1e5));
     938
     939            topWay = null;
     940            wayClockwise = false;
     941            Node bestWayNextNode = null;
     942
     943            for (Way way : parts) {
     944                if (way.firstNode().equals(headNode)) {
     945                    Node nextNode = way.getNode(1);
     946
     947                    if (topWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) {
     948                        //the new way is better
     949                        topWay = way;
     950                        wayClockwise = true;
     951                        bestWayNextNode = nextNode;
     952                    }
     953                }
     954
     955                if (way.lastNode().equals(headNode)) {
     956                    //end adjacent to headNode
     957                    Node nextNode = way.getNode(way.getNodesCount() - 2);
     958
     959                    if (topWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) {
     960                        //the new way is better
     961                        topWay = way;
     962                        wayClockwise = false;
     963                        bestWayNextNode = nextNode;
     964                    }
     965                }
     966            }
     967        } else {
     968            //node is inside way - pick the clockwise going end.
     969            Node prev = topWay.getNode(topIndex - 1);
     970            Node next = topWay.getNode(topIndex + 1);
     971
     972            //there will be no parallel segments in the middle of way, so all fine.
     973            wayClockwise = angleIsClockwise(prev, topNode, next);
     974        }
     975
     976        Way curWay = topWay;
     977        boolean curWayInsideToTheRight = wayClockwise ^ isInner;
     978
     979        //iterate till full circle is reached
     980        while (true) {
     981
     982            //add cur way
     983            WayInPolygon resultWay = new WayInPolygon(curWay, curWayInsideToTheRight);
     984            result.add(resultWay);
     985
     986            //process next way
     987            Way nextWay = nextWayMap.get(curWay);
     988            Node prevNode = curWay.getNode(curWay.getNodesCount() - 2);
     989            Node headNode = curWay.lastNode();
     990            Node nextNode = nextWay.getNode(1);
     991
     992            if (nextWay == topWay) {
     993                //full loop traversed - all done.
     994                break;
     995            }
     996
     997
     998            //find intersecting segments
     999            // the intersections will look like this:
     1000            //
     1001            //                       ^
     1002            //                       |
     1003            //                       X wayBNode
     1004            //                       |
     1005            //                  wayB |
     1006            //                       |
     1007            //             curWay    |       nextWay
     1008            //----X----------------->X----------------------X---->
     1009            //    prevNode           ^headNode              nextNode
     1010            //                       |
     1011            //                       |
     1012            //                  wayA |
     1013            //                       |
     1014            //                       X wayANode
     1015            //                       |
     1016
     1017            int intersectionCount = 0;
     1018
     1019            for (Way wayA : parts) {
     1020
     1021                if (wayA == curWay) {
    5341022                    continue;
    5351023                }
    5361024
    537                 Relation newRel = new Relation(r);
    538                 List<RelationMember> members = newRel.getMembers();
    539                 members.remove(rm);
    540                 newRel.setMembers(members);
    541 
    542                 cmds.add(new ChangeCommand(r, newRel));
    543                 RelationRole saverel =  new RelationRole(r, rm.getRole());
    544                 if(!result.contains(saverel)) {
    545                     result.add(saverel);
    546                 }
    547                 break;
    548             }
    549         }
    550 
    551         commitCommands(marktr("Removed Element from Relations"));
     1025                if (wayA.lastNode().equals(headNode)) {
     1026
     1027                    Way wayB = nextWayMap.get(wayA);
     1028
     1029                    //test if wayA is opposite wayB relative to curWay and nextWay
     1030
     1031                    Node wayANode = wayA.getNode(wayA.getNodesCount() - 2);
     1032                    Node wayBNode = wayB.getNode(1);
     1033
     1034                    boolean wayAToTheRight = isToTheRightSideOfLine(prevNode, headNode, nextNode, wayANode);
     1035                    boolean wayBToTheRight = isToTheRightSideOfLine(prevNode, headNode, nextNode, wayBNode);
     1036
     1037                    if (wayAToTheRight != wayBToTheRight) {
     1038                        intersectionCount ++;
     1039                    }
     1040                }
     1041            }
     1042
     1043            //if odd number of crossings, invert orientation
     1044            if (intersectionCount % 2 == 1) {
     1045                curWayInsideToTheRight = !curWayInsideToTheRight;
     1046            }
     1047
     1048            curWay = nextWay;
     1049        }
     1050
    5521051        return result;
    5531052    }
    5541053
    5551054    /**
    556      * This method splits ways into smaller parts, using the prepared nodes list as split points.
    557      * Uses SplitWayAction.splitWay for the heavy lifting.
     1055     * This is a method splits way into smaller parts, using the prepared nodes list as split points.
     1056     * Uses  SplitWayAction.splitWay for the heavy lifting.
    5581057     * @return list of split ways (or original ways if no splitting is done).
    5591058     */
    560     private ArrayList<Way> splitWaysOnNodes(Way a, Way b, Collection<Node> nodes) {
     1059    private ArrayList<Way> splitWayOnNodes(Way way, Collection<Node> nodes) {
    5611060
    5621061        ArrayList<Way> result = new ArrayList<Way>();
    563         List<Way> ways = new ArrayList<Way>();
    564         ways.add(a);
    565         ways.add(b);
    566 
    567         for (Way way: ways) {
    568             List<List<Node>> chunks = buildNodeChunks(way, nodes);
     1062        List<List<Node>> chunks = buildNodeChunks(way, nodes);
     1063
     1064        if (chunks.size() > 1) {
    5691065            SplitWayResult split = SplitWayAction.splitWay(Main.map.mapView.getEditLayer(), way, chunks, Collections.<OsmPrimitive>emptyList());
    5701066
    5711067            //execute the command, we need the results
    572             Main.main.undoRedo.add(split.getCommand());
    573             cmdsCount ++;
     1068            cmds.add(split.getCommand());
     1069            commitCommands(marktr("Split ways into fragments"));
    5741070
    5751071            result.add(split.getOriginalWay());
    5761072            result.addAll(split.getNewWays());
     1073        } else {
     1074            //nothing to split
     1075            result.add(way);
    5771076        }
    5781077
    5791078        return result;
    5801079    }
     1080
    5811081
    5821082    /**
     
    5841084     * @param way the way to chunk
    5851085     * @param splitNodes the places where to cut.
    586      * @return list of node segments to produce.
    587      */
    588     private List<List<Node>> buildNodeChunks(Way way, Collection<Node> splitNodes)
    589     {
     1086     * @return list of node paths to produce.
     1087     */
     1088    private List<List<Node>> buildNodeChunks(Way way, Collection<Node> splitNodes) {
    5901089        List<List<Node>> result = new ArrayList<List<Node>>();
    5911090        List<Node> curList = new ArrayList<Node>();
    5921091
    593         for(Node node: way.getNodes()){
     1092        for (Node node : way.getNodes()) {
    5941093            curList.add(node);
    595             if (curList.size() > 1 && splitNodes.contains(node)){
     1094            if (curList.size() > 1 && splitNodes.contains(node)) {
    5961095                result.add(curList);
    5971096                curList = new ArrayList<Node>();
     
    6001099        }
    6011100
    602         if (curList.size() > 1)
    603         {
     1101        if (curList.size() > 1) {
    6041102            result.add(curList);
    6051103        }
     
    6081106    }
    6091107
    610     /**
    611      * Returns all nodes for given ways
    612      * @param Collection<Way> The list of ways which nodes are to be returned
    613      * @return Collection<Node> The list of nodes the ways contain
    614      */
    615     private Collection<Node> getNodesFromWays(Collection<Way> ways) {
    616         Collection<Node> allNodes = new ArrayList<Node>();
    617         for(Way w: ways) {
    618             allNodes.addAll(w.getNodes());
    619         }
    620         return allNodes;
    621     }
    622 
    623     /**
    624      * Gets all inner ways given all ways and outer ways.
    625      * @param multigonWays
    626      * @param outerWays
    627      * @return list of inner ways.
    628      */
    629     private ArrayList<Way> findInnerWays(Collection<Way> multigonWays, Collection<WayInPath> outerWays) {
    630         ArrayList<Way> innerWays = new ArrayList<Way>();
    631         Set<Way> outerSet = new HashSet<Way>();
    632 
    633         for(WayInPath w: outerWays) {
    634             outerSet.add(w.way);
    635         }
    636 
    637         for(Way way: multigonWays) {
    638             if (!outerSet.contains(way)) {
    639                 innerWays.add(way);
    640             }
    641         }
    642 
    643         return innerWays;
    644     }
    645 
    646 
    647     /**
    648      * Finds all ways for a given list of Ways that form the outer hull.
    649      * This works by starting with one node and traversing the multigon clockwise, always picking the leftmost path.
    650      * Prerequisites - the ways must not intersect and have common end nodes where they meet.
    651      * @param Collection<Way> A list of (splitted) ways that form a multigon
    652      * @return Collection<Way> A list of ways that form the outer boundary of the multigon.
    653      */
    654     private static ArrayList<WayInPath> findOuterWays(Collection<Way> multigonWays) {
    655 
    656         //find the node with minimum lat - it's guaranteed to be outer. (What about the south pole?)
    657         Way bestWay = null;
    658         Node topNode = null;
    659         int topIndex = 0;
    660         double minLat = Double.POSITIVE_INFINITY;
    661 
    662         for(Way way: multigonWays) {
    663             for (int pos = 0; pos < way.getNodesCount(); pos ++) {
    664                 Node node = way.getNode(pos);
    665 
    666                 if (node.getCoor().lat() < minLat) {
    667                     minLat = node.getCoor().lat();
    668                     bestWay = way;
    669                     topNode = node;
    670                     topIndex = pos;
    671                 }
    672             }
    673         }
    674 
    675         //get two final nodes from best way to mark as starting point and orientation.
    676         Node headNode = null;
    677         Node prevNode = null;
    678 
    679         if (topNode.equals(bestWay.firstNode()) || topNode.equals(bestWay.lastNode())) {
    680             //node is in split point
    681             headNode = topNode;
    682             //make a fake node that is downwards from head node (smaller latitude). It will be a division point between paths.
    683             prevNode = new Node(new LatLon(headNode.getCoor().lat() - 1000, headNode.getCoor().lon()));
    684         } else {
    685             //node is inside way - pick the clockwise going end.
    686             Node prev = bestWay.getNode(topIndex - 1);
    687             Node next = bestWay.getNode(topIndex + 1);
    688 
    689             if (angleIsClockwise(prev, topNode, next)) {
    690                 headNode = bestWay.lastNode();
    691                 prevNode = bestWay.getNode(bestWay.getNodesCount() - 2);
    692             }
    693             else {
    694                 headNode = bestWay.firstNode();
    695                 prevNode = bestWay.getNode(1);
    696             }
    697         }
    698 
    699         Set<Way> outerWays = new HashSet<Way>();
    700         ArrayList<WayInPath> result = new ArrayList<WayInPath>();
    701 
    702         //iterate till full circle is reached
    703         while (true) {
    704 
    705             bestWay = null;
    706             Node bestWayNextNode = null;
    707             boolean bestWayReverse = false;
    708 
    709             for (Way way: multigonWays) {
    710                 boolean wayReverse;
    711                 Node nextNode;
    712 
    713                 if (way.firstNode().equals(headNode)) {
    714                     //start adjacent to headNode
    715                     nextNode = way.getNode(1);
    716                     wayReverse = false;
    717 
    718                     if (nextNode.equals(prevNode)) {
    719                         //this is the path we came from - ignore it.
    720                     } else if (bestWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) {
    721                         //the new way is better
    722                         bestWay = way;
    723                         bestWayReverse = wayReverse;
    724                         bestWayNextNode = nextNode;
     1108
     1109    /**
     1110     * This method finds witch ways are outer and witch are inner.
     1111     * @param boundaryWays
     1112     * @return
     1113     */
     1114    private List<AssembledMultipolygon> findPolygons(Collection<AssembledPolygon> boundaries) {
     1115
     1116        List<PolygonLevel> list = findOuterWaysImpl(0, boundaries);
     1117        List<AssembledMultipolygon> result = new ArrayList<AssembledMultipolygon>();
     1118
     1119        //take every other level
     1120        for (PolygonLevel pol : list) {
     1121            if (pol.level % 2 == 0) {
     1122                result.add(pol.pol);
     1123            }
     1124        }
     1125
     1126        return result;
     1127    }
     1128
     1129    /**
     1130     * Collects outer way and corresponding inner ways from all boundaries.
     1131     * @param boundaryWays
     1132     * @return the outermostWay.
     1133     */
     1134    private List<PolygonLevel> findOuterWaysImpl(int level, Collection<AssembledPolygon> boundaryWays) {
     1135
     1136        //TODO: bad performance for deep nestings...
     1137        List<PolygonLevel> result = new ArrayList<PolygonLevel>();
     1138
     1139        for (AssembledPolygon outerWay : boundaryWays) {
     1140
     1141            boolean outerGood = true;
     1142            List<AssembledPolygon> innerCandidates = new ArrayList<AssembledPolygon>();
     1143
     1144            for (AssembledPolygon innerWay : boundaryWays) {
     1145                if (innerWay == outerWay) {
     1146                    continue;
     1147                }
     1148
     1149                if (wayInsideWay(outerWay, innerWay)) {
     1150                    outerGood = false;
     1151                    break;
     1152                } else if (wayInsideWay(innerWay, outerWay)) {
     1153                    innerCandidates.add(innerWay);
     1154                }
     1155            }
     1156
     1157            if (!outerGood) {
     1158                continue;
     1159            }
     1160
     1161            //add new outer polygon
     1162            AssembledMultipolygon pol = new AssembledMultipolygon(outerWay);
     1163            PolygonLevel polLev = new PolygonLevel(pol, level);
     1164
     1165            //process inner ways
     1166            if (innerCandidates.size() > 0) {
     1167                List<PolygonLevel> innerList = findOuterWaysImpl(level + 1, innerCandidates);
     1168                result.addAll(innerList);
     1169
     1170                for (PolygonLevel pl : innerList) {
     1171                    if (pl.level == level + 1) {
     1172                        pol.innerWays.add(pl.pol.outerWay);
    7251173                    }
    7261174                }
    727 
    728                 if (way.lastNode().equals(headNode)) {
    729                     //end adjacent to headNode
    730                     nextNode = way.getNode(way.getNodesCount() - 2);
    731                     wayReverse = true;
    732 
    733                     if (nextNode.equals(prevNode)) {
    734                         //this is the path we came from - ignore it.
    735                     } else if (bestWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) {
    736                         //the new way is better
    737                         bestWay = way;
    738                         bestWayReverse = wayReverse;
    739                         bestWayNextNode = nextNode;
    740                     }
    741                 }
    742             }
    743 
    744             if (bestWay == null)
    745                 throw new RuntimeException();
    746             else if (outerWays.contains(bestWay)) {
    747                 break; //full circle reached, terminate.
     1175            }
     1176
     1177            result.add(polLev);
     1178        }
     1179
     1180        return result;
     1181    }
     1182
     1183
     1184
     1185    /**
     1186     * Finds all ways that form inner or outer boundaries.
     1187     * @param Collection<Way> A list of (splitted) ways that form a multigon and share common end nodes on intersections.
     1188     * @param Collection<Way> this list is filled with ways that are to be discarded
     1189     * @return Collection<Collection<Way>> A list of ways that form the outer and inner boundaries of the multigon.
     1190     */
     1191    public static List<AssembledPolygon> findBoundaryPolygons(Collection<WayInPolygon> multigonWays, List<Way> discardedResult) {
     1192        //first find all discardable ways, by getting outer shells.
     1193        //this will produce incorrect boundaries in some cases, but second pass will fix it.
     1194
     1195        List<WayInPolygon> discardedWays = new ArrayList<WayInPolygon>();
     1196        Set<WayInPolygon> processedWays = new HashSet<WayInPolygon>();
     1197        WayTraverser traverser = new WayTraverser(multigonWays);
     1198
     1199        for (WayInPolygon startWay : multigonWays) {
     1200            if (processedWays.contains(startWay)) {
     1201                continue;
     1202            }
     1203
     1204            traverser.startNewWay(startWay);
     1205
     1206            List<WayInPolygon> boundary = new ArrayList<WayInPolygon>();
     1207            WayInPolygon lastWay = startWay;
     1208
     1209            while (true) {
     1210                boundary.add(lastWay);
     1211
     1212                WayInPolygon bestWay = traverser.advanceNextLeftmostWay();
     1213                boolean wayInsideToTheRight = bestWay == null ? false : traverser.isLastWayInsideToTheRight();
     1214
     1215                if (bestWay == null || processedWays.contains(bestWay) || !wayInsideToTheRight) {
     1216                    //bad segment chain - proceed to discard it
     1217                    lastWay = null;
     1218                    break;
     1219                } else if (boundary.contains(bestWay)) {
     1220                    //traversed way found - close the way
     1221                    lastWay = bestWay;
     1222                    break;
     1223                } else {
     1224                    //proceed to next segment
     1225                    lastWay = bestWay;
     1226                }
     1227            }
     1228
     1229            if (lastWay != null) {
     1230                //way good
     1231                processedWays.addAll(boundary);
     1232
     1233                //remove junk segments at the start
     1234                while (boundary.get(0) != lastWay) {
     1235                    discardedWays.add(boundary.get(0));
     1236                    boundary.remove(0);
     1237                }
    7481238            } else {
    749                 //add to outer ways, repeat.
    750                 outerWays.add(bestWay);
    751                 result.add(new WayInPath(bestWay, bestWayReverse));
    752                 headNode = bestWayReverse ? bestWay.firstNode() : bestWay.lastNode();
    753                 prevNode = bestWayReverse ? bestWay.getNode(1) : bestWay.getNode(bestWay.getNodesCount() - 2);
    754             }
    755         }
     1239                //way bad
     1240                discardedWays.addAll(boundary);
     1241                processedWays.addAll(boundary);
     1242            }
     1243        }
     1244
     1245        //now we have removed junk segments, collect the real result ways
     1246
     1247        traverser.removeWays(discardedWays);
     1248
     1249        List<AssembledPolygon> result = new ArrayList<AssembledPolygon>();
     1250
     1251        while (traverser.hasWays()) {
     1252
     1253            WayInPolygon startWay = traverser.startNewWay();
     1254            List<WayInPolygon> boundary = new ArrayList<WayInPolygon>();
     1255            WayInPolygon curWay = startWay;
     1256
     1257            do {
     1258                boundary.add(curWay);
     1259                curWay = traverser.advanceNextRightmostWay();
     1260
     1261                //should not happen
     1262                if (curWay == null || !traverser.isLastWayInsideToTheRight())
     1263                    throw new RuntimeException("Join areas internal error.");
     1264
     1265            } while (curWay != startWay);
     1266
     1267            //build result
     1268            traverser.removeWays(boundary);
     1269            result.add(new AssembledPolygon(boundary));
     1270        }
     1271
     1272        for (WayInPolygon way : discardedWays) {
     1273            discardedResult.add(way.way);
     1274        }
     1275
     1276        //split inner polygons that have several touching parts.
     1277        result = fixTouchingPolygons(result);
    7561278
    7571279        return result;
    7581280    }
     1281
     1282
     1283    /**
     1284     * This method checks if polygons have several touching parts and splits them in several polygons.
     1285     * @param polygon the polygon to process.
     1286     */
     1287    public static List<AssembledPolygon> fixTouchingPolygons(List<AssembledPolygon> polygons)
     1288    {
     1289        List<AssembledPolygon> newPolygons = new ArrayList<AssembledPolygon>();
     1290
     1291        for (AssembledPolygon innerPart : polygons) {
     1292            WayTraverser traverser = new WayTraverser(innerPart.ways);
     1293
     1294            while (traverser.hasWays()) {
     1295
     1296                WayInPolygon startWay = traverser.startNewWay();
     1297                List<WayInPolygon> boundary = new ArrayList<WayInPolygon>();
     1298                WayInPolygon curWay = startWay;
     1299
     1300                Node startNode = traverser.getLastWayStartNode();
     1301                boundary.add(curWay);
     1302
     1303                while (startNode != traverser.getLastWayEndNode()) {
     1304                    curWay = traverser.advanceNextLeftmostWay();
     1305                    boundary.add(curWay);
     1306
     1307                    //should not happen
     1308                    if (curWay == null || !traverser.isLastWayInsideToTheRight())
     1309                        throw new RuntimeException("Join areas internal error.");
     1310                }
     1311
     1312                //build result
     1313                traverser.removeWays(boundary);
     1314                newPolygons.add(new AssembledPolygon(boundary));
     1315            }
     1316        }
     1317
     1318        return newPolygons;
     1319    }
     1320
    7591321
    7601322    /**
     
    7661328     * @return true if to the right side, false otherwise
    7671329     */
    768     public static boolean isToTheRightSideOfLine(Node lineP1, Node lineP2, Node lineP3, Node testPoint)
    769     {
     1330    public static boolean isToTheRightSideOfLine(Node lineP1, Node lineP2, Node lineP3, Node testPoint) {
    7701331        boolean pathBendToRight = angleIsClockwise(lineP1, lineP2, lineP3);
    7711332        boolean rightOfSeg1 = angleIsClockwise(lineP1, lineP2, testPoint);
     
    7851346     * @return true if first vector is clockwise before second vector.
    7861347     */
    787     public static boolean angleIsClockwise(Node commonNode, Node firstNode, Node secondNode)
    788     {
    789         double dla1 = (firstNode.getCoor().lat() - commonNode.getCoor().lat());
    790         double dla2 = (secondNode.getCoor().lat() - commonNode.getCoor().lat());
    791         double dlo1 = (firstNode.getCoor().lon() - commonNode.getCoor().lon());
    792         double dlo2 = (secondNode.getCoor().lon() - commonNode.getCoor().lon());
    793 
    794         return dla1 * dlo2 - dlo1 * dla2 > 0;
     1348
     1349    public static boolean angleIsClockwise(Node commonNode, Node firstNode, Node secondNode) {
     1350        double dy1 = (firstNode.getEastNorth().getY() - commonNode.getEastNorth().getY());
     1351        double dy2 = (secondNode.getEastNorth().getY() - commonNode.getEastNorth().getY());
     1352        double dx1 = (firstNode.getEastNorth().getX() - commonNode.getEastNorth().getX());
     1353        double dx2 = (secondNode.getEastNorth().getX() - commonNode.getEastNorth().getX());
     1354
     1355        return dy1 * dx2 - dx1 * dy2 > 0;
     1356    }
     1357
     1358
     1359    /**
     1360     * Tests if way is inside other way
     1361     * @param outside
     1362     * @param inside
     1363     * @return
     1364     */
     1365    public static boolean wayInsideWay(AssembledPolygon inside, AssembledPolygon outside) {
     1366        Set<Node> outsideNodes = new HashSet<Node>(outside.getNodes());
     1367
     1368        for (Node insideNode : inside.getNodes()) {
     1369
     1370            if (!outsideNodes.contains(insideNode))
     1371                //simply test the one node
     1372                return nodeInsidePolygon(insideNode, outside.getNodes());
     1373        }
     1374
     1375        //all nodes shared.
     1376        return false;
    7951377    }
    7961378
     
    8021384     * FIXME: this should probably be moved to tools..
    8031385     */
    804     public static boolean nodeInsidePolygon(ArrayList<Node> polygonNodes, Node point)
    805     {
     1386    public static boolean nodeInsidePolygon(Node point, List<Node> polygonNodes) {
    8061387        if (polygonNodes.size() < 3)
    8071388            return false;
     
    8131394        Node oldPoint = polygonNodes.get(polygonNodes.size() - 1);
    8141395
    815         for(Node newPoint: polygonNodes)
    816         {
     1396        for (Node newPoint : polygonNodes) {
    8171397            //skip duplicate points
    8181398            if (newPoint.equals(oldPoint)) {
     
    8211401
    8221402            //order points so p1.lat <= p2.lat;
    823             if (newPoint.getCoor().lat() > oldPoint.getCoor().lat())
    824             {
     1403            if (newPoint.getEastNorth().getY() > oldPoint.getEastNorth().getY()) {
    8251404                p1 = oldPoint;
    8261405                p2 = newPoint;
    827             }
    828             else
    829             {
     1406            } else {
    8301407                p1 = newPoint;
    8311408                p2 = oldPoint;
     
    8331410
    8341411            //test if the line is crossed and if so invert the inside flag.
    835             if ((newPoint.getCoor().lat() < point.getCoor().lat()) == (point.getCoor().lat() <= oldPoint.getCoor().lat())
    836                     && (point.getCoor().lon() - p1.getCoor().lon()) * (p2.getCoor().lat() - p1.getCoor().lat())
    837                     < (p2.getCoor().lon() - p1.getCoor().lon()) * (point.getCoor().lat() - p1.getCoor().lat()))
     1412            if ((newPoint.getEastNorth().getY() < point.getEastNorth().getY()) == (point.getEastNorth().getY() <= oldPoint.getEastNorth().getY())
     1413                    && (point.getEastNorth().getX() - p1.getEastNorth().getX()) * (p2.getEastNorth().getY() - p1.getEastNorth().getY())
     1414                    < (p2.getEastNorth().getX() - p1.getEastNorth().getX()) * (point.getEastNorth().getY() - p1.getEastNorth().getY()))
    8381415            {
    8391416                inside = !inside;
     
    8451422        return inside;
    8461423    }
     1424
     1425
     1426    /**
     1427     * Joins the lists of ways.
     1428     * @param Collection<Way> The list of outer ways that belong to that multigon.
     1429     * @return Way The newly created outer way
     1430     */
     1431    private Multipolygon  joinPolygon(AssembledMultipolygon polygon) throws UserCancelException {
     1432        Multipolygon result = new Multipolygon(joinWays(polygon.outerWay.ways));
     1433
     1434        for (AssembledPolygon pol : polygon.innerWays) {
     1435            result.innerWays.add(joinWays(pol.ways));
     1436        }
     1437
     1438        return result;
     1439    }
     1440
    8471441
    8481442    /**
     
    8511445     * @return Way The newly created outer way
    8521446     */
    853     private Way joinOuterWays(ArrayList<WayInPath> outerWays) {
     1447    private Way joinWays(List<WayInPolygon> ways) throws UserCancelException {
    8541448
    8551449        //leave original orientation, if all paths are reverse.
    8561450        boolean allReverse = true;
    857         for(WayInPath way: outerWays) {
    858             allReverse &= way.insideToTheLeft;
     1451        for (WayInPolygon way : ways) {
     1452            allReverse &= !way.insideToTheRight;
    8591453        }
    8601454
    8611455        if (allReverse) {
    862             for(WayInPath way: outerWays){
    863                 way.insideToTheLeft = !way.insideToTheLeft;
    864             }
    865         }
    866 
    867         commitCommands(marktr("Join Areas: Remove Short Ways"));
    868         Way joinedWay = joinOrientedWays(outerWays);
    869         if (joinedWay != null)
    870             return closeWay(joinedWay);
    871         else
    872             return null;
    873     }
    874 
    875     /**
    876      * Ensures a way is closed. If it isn't, last and first node are connected.
    877      * @param Way the way to ensure it's closed
    878      * @return Way The joined way.
    879      */
    880     private Way closeWay(Way w) {
    881         if(w.isClosed())
    882             return w;
    883         Main.main.getCurrentDataSet().setSelected(w);
    884         Way wnew = new Way(w);
    885         wnew.addNode(wnew.firstNode());
    886         cmds.add(new ChangeCommand(w, wnew));
    887         commitCommands(marktr("Closed Way"));
    888         return (Way)(Main.main.getCurrentDataSet().getSelectedWays().toArray())[0];
    889     }
     1456            for (WayInPolygon way : ways) {
     1457                way.insideToTheRight = !way.insideToTheRight;
     1458            }
     1459        }
     1460
     1461        Way joinedWay = joinOrientedWays(ways);
     1462
     1463        //should not happen
     1464        if (joinedWay == null || !joinedWay.isClosed())
     1465            throw new RuntimeException("Join areas internal error.");
     1466
     1467        return joinedWay;
     1468    }
     1469
    8901470
    8911471    /**
     
    8941474     * @return Way The newly created way
    8951475     */
    896     private Way joinOrientedWays(ArrayList<WayInPath> ways) {
    897         if(ways.size() < 2)
     1476    private Way joinOrientedWays(List<WayInPolygon> ways) throws UserCancelException{
     1477        if (ways.size() < 2)
    8981478            return ways.get(0).way;
    8991479
     
    9011481        // the user about this.
    9021482
     1483        //TODO: ReverseWay and Combine way are really slow and we use them a lot here. This slows down large joins.
    9031484        List<Way> actionWays = new ArrayList<Way>(ways.size());
    9041485
    905         for(WayInPath way : ways) {
     1486        for (WayInPolygon way : ways) {
    9061487            actionWays.add(way.way);
    9071488
    908             if (way.insideToTheLeft) {
    909                 Main.main.getCurrentDataSet().setSelected(way.way);
    910                 new ReverseWayAction().actionPerformed(null);
     1489            if (!way.insideToTheRight) {
     1490                ReverseWayResult res = ReverseWayAction.reverseWay(way.way);
     1491                Main.main.undoRedo.add(res.getReverseCommand());
    9111492                cmdsCount++;
    9121493            }
    9131494        }
    9141495
    915         Way result = new CombineWayAction().combineWays(actionWays);
    916 
    917         if(result != null) {
    918             cmdsCount++;
    919         }
     1496        Pair<Way, Command> result = CombineWayAction.combineWaysWorker(actionWays);
     1497
     1498        Main.main.undoRedo.add(result.b);
     1499        cmdsCount ++;
     1500
     1501        return result.a;
     1502    }
     1503
     1504
     1505    /**
     1506     * This method analyzes multipolygon relationships of given ways and collects addition inner ways to consider.
     1507     * @param selectedWays the selected ways
     1508     * @return list of polygons, or null if too complex relation encountered.
     1509     */
     1510    private List<Multipolygon> collectMultipolygons(List<Way> selectedWays) {
     1511
     1512        List<Multipolygon> result = new ArrayList<Multipolygon>();
     1513
     1514        //prepare the lists, to minimize memory allocation.
     1515        List<Way> outerWays = new ArrayList<Way>();
     1516        List<Way> innerWays = new ArrayList<Way>();
     1517
     1518        Set<Way> processedOuterWays = new LinkedHashSet<Way>();
     1519        Set<Way> processedInnerWays = new LinkedHashSet<Way>();
     1520
     1521        for (Relation r : CombineWayAction.getParentRelations(selectedWays)) {
     1522            if (r.isDeleted() ||
     1523                    r.get("type") == null ||
     1524                    !r.get("type").equalsIgnoreCase("multipolygon")) {
     1525                continue;
     1526            }
     1527
     1528            boolean hasKnownOuter = false;
     1529            outerWays.clear();
     1530            innerWays.clear();
     1531
     1532            for (RelationMember rm : r.getMembers()) {
     1533                if (rm.getRole().equalsIgnoreCase("outer")) {
     1534                    outerWays.add(rm.getWay());
     1535                    hasKnownOuter |= selectedWays.contains(rm.getWay());
     1536                }
     1537                else if (rm.getRole().equalsIgnoreCase("inner")) {
     1538                    innerWays.add(rm.getWay());
     1539                }
     1540            }
     1541
     1542            if (!hasKnownOuter) {
     1543                continue;
     1544            }
     1545
     1546            if (outerWays.size() > 1) {
     1547                JOptionPane.showMessageDialog(Main.parent, tr("Sorry. Cannot handle multipolygon relations with multiple outer ways."));
     1548                return null;
     1549            }
     1550
     1551            Way outerWay = outerWays.get(0);
     1552
     1553            //retain only selected inner ways
     1554            innerWays.retainAll(selectedWays);
     1555
     1556            if (processedOuterWays.contains(outerWay)) {
     1557                JOptionPane.showMessageDialog(Main.parent, tr("Sorry. Cannot handle way that is outer in multiple multipolygon relations."));
     1558                return null;
     1559            }
     1560
     1561            if (processedInnerWays.contains(outerWay)) {
     1562                JOptionPane.showMessageDialog(Main.parent, tr("Sorry. Cannot handle way that is both inner and outer in multipolygon relations."));
     1563                return null;
     1564            }
     1565
     1566            for (Way way :innerWays)
     1567            {
     1568                if (processedOuterWays.contains(way)) {
     1569                    JOptionPane.showMessageDialog(Main.parent, tr("Sorry. Cannot handle way that is both inner and outer in multipolygon relations."));
     1570                    return null;
     1571                }
     1572
     1573                if (processedInnerWays.contains(way)) {
     1574                    JOptionPane.showMessageDialog(Main.parent, tr("Sorry. Cannot handle way that is inner in multiple multipolygon relations."));
     1575                    return null;
     1576                }
     1577            }
     1578
     1579            processedOuterWays.add(outerWay);
     1580            processedInnerWays.addAll(innerWays);
     1581
     1582            Multipolygon pol = new Multipolygon(outerWay);
     1583            pol.innerWays.addAll(innerWays);
     1584            pol.relation = r;
     1585
     1586            result.add(pol);
     1587        }
     1588
     1589        //add remaining ways, not in relations
     1590        for (Way way : selectedWays) {
     1591            if (processedOuterWays.contains(way) || processedInnerWays.contains(way)) {
     1592                continue;
     1593            }
     1594
     1595            result.add(new Multipolygon(way));
     1596        }
     1597
    9201598        return result;
    9211599    }
    9221600
    923     /**
    924      * Joins a list of ways (using CombineWayAction and ReverseWayAction if necessary to quiet the former)
    925      * @param ArrayList<Way> The list of ways to join
    926      * @return Way The newly created way
    927      */
    928     private Way joinWays(ArrayList<Way> ways) {
    929         if(ways.size() < 2)
    930             return ways.get(0);
    931 
    932         // This will turn ways so all of them point in the same direction and CombineAction won't bug
    933         // the user about this.
    934         Way a = null;
    935         for (Way b : ways) {
    936             if(a == null) {
    937                 a = b;
    938                 continue;
    939             }
    940             if(a.getNode(0).equals(b.getNode(0)) ||
    941                     a.getNode(a.getNodesCount()-1).equals(b.getNode(b.getNodesCount()-1))) {
    942                 Main.main.getCurrentDataSet().setSelected(b);
    943                 new ReverseWayAction().actionPerformed(null);
    944                 cmdsCount++;
    945             }
    946             a = b;
    947         }
    948         if ((a = new CombineWayAction().combineWays(ways)) != null) {
    949             cmdsCount++;
    950         }
    951         return a;
    952     }
    953 
    954     /**
    955      * Finds all ways that may be part of a multipolygon relation and removes them from the given list.
    956      * It will automatically combine "good" ways
    957      * @param Collection<Way> The list of inner ways to check
    958      * @param Way The newly created outer way
    959      * @return ArrayList<Way> The List of newly created inner ways
    960      */
    961     private ArrayList<Way> fixMultipolygons(Collection<Way> uninterestingWays, Way outerWay, boolean selfintersect) {
    962         Collection<Node> innerNodes = getNodesFromWays(uninterestingWays);
    963         Collection<Node> outerNodes = outerWay.getNodes();
    964 
    965         // The newly created inner ways. uninterestingWays is passed by reference and therefore modified in-place
    966         ArrayList<Way> newInnerWays = new ArrayList<Way>();
    967 
    968         // Now we need to find all inner ways that contain a remaining node, but no outer nodes
    969         // Remaining nodes are those that contain to more than one way. All nodes that belong to an
    970         // inner multigon part will have at least two ways, so we can use this to find which ways do
    971         // belong to the multigon.
    972         ArrayList<Way> possibleWays = new ArrayList<Way>();
    973         wayIterator: for(Way w : uninterestingWays) {
    974             boolean hasInnerNodes = false;
    975             for(Node n : w.getNodes()) {
    976                 if(outerNodes.contains(n)) {
    977                     if(!selfintersect) { // allow outer point for self intersection
    978                         continue wayIterator;
    979                     }
    980                 }
    981                 else if(!hasInnerNodes && innerNodes.contains(n)) {
    982                     hasInnerNodes = true;
    983                 }
    984             }
    985             if(!hasInnerNodes || w.getNodesCount() < 2) {
    986                 continue;
    987             }
    988             possibleWays.add(w);
    989         }
    990 
    991         // This removes unnecessary ways that might have been added.
    992         removeAlmostAlikeWays(possibleWays);
    993 
    994         // loop twice
    995         // in k == 0 prefer ways which allow no Y-joining (i.e. which have only 1 solution)
    996         for(int k = 0; k < 2; ++k)
    997         {
    998             // Join all ways that have one start/ending node in common
    999             Way joined = null;
    1000             outerIterator: do {
    1001                 removePartlyUnconnectedWays(possibleWays);
    1002                 joined = null;
    1003                 for(Way w1 : possibleWays) {
    1004                     if(w1.isClosed()) {
    1005                         if(!wayIsCollapsed(w1)) {
    1006                             uninterestingWays.remove(w1);
    1007                             newInnerWays.add(w1);
    1008                         }
    1009                         joined = w1;
    1010                         possibleWays.remove(w1);
    1011                         continue outerIterator;
    1012                     }
    1013                     ArrayList<Way> secondary = new ArrayList<Way>();
    1014                     for(Way w2 : possibleWays) {
    1015                         int i = 0;
    1016                         // w2 cannot be closed, otherwise it would have been removed above
    1017                         if(w1.equals(w2)) {
    1018                             continue;
    1019                         }
    1020                         if(w2.isFirstLastNode(w1.firstNode())) {
    1021                             ++i;
    1022                         }
    1023                         if(w2.isFirstLastNode(w1.lastNode())) {
    1024                             ++i;
    1025                         }
    1026                         if(i == 2) // this way closes w1 - take it!
    1027                         {
    1028                             if(secondary.size() > 0) {
    1029                                 secondary.clear();
    1030                             }
    1031                             secondary.add(w2);
    1032                             break;
    1033                         }
    1034                         else if(i > 0) {
    1035                             secondary.add(w2);
    1036                         }
    1037                     }
    1038                     if(k == 0 ? secondary.size() == 1 : secondary.size() > 0)
    1039                     {
    1040                         ArrayList<Way> joinThem = new ArrayList<Way>();
    1041                         joinThem.add(w1);
    1042                         joinThem.add(secondary.get(0));
    1043                         // Although we joined the ways, we cannot simply assume that they are closed
    1044                         if((joined = joinWays(joinThem)) != null)
    1045                         {
    1046                             uninterestingWays.removeAll(joinThem);
    1047                             possibleWays.removeAll(joinThem);
    1048 
    1049                             //List<Node> nodes = joined.getNodes();
    1050                             // check if we added too much
    1051                             /*for(int i = 1; i < nodes.size()-2; ++i)
    1052                             {
    1053                                 if(nodes.get(i) == nodes.get(nodes.size()-1))
    1054                                     System.out.println("Joining of ways produced unexpecteded result\n");
    1055                             }*/
    1056                             uninterestingWays.add(joined);
    1057                             possibleWays.add(joined);
    1058                             continue outerIterator;
    1059                         }
    1060                     }
    1061                 }
    1062             } while(joined != null);
    1063         }
    1064         return newInnerWays;
    1065     }
    1066 
    1067     /**
    1068      * Removes almost alike ways (= ways that are on top of each other for all nodes)
    1069      * @param ArrayList<Way> the ways to remove almost-duplicates from
    1070      */
    1071     private void removeAlmostAlikeWays(ArrayList<Way> ways) {
    1072         Collection<Way> removables = new ArrayList<Way>();
    1073         outer: for(int i=0; i < ways.size(); i++) {
    1074             Way a = ways.get(i);
    1075             for(int j=i+1; j < ways.size(); j++) {
    1076                 Way b = ways.get(j);
    1077                 List<Node> revNodes = new ArrayList<Node>(b.getNodes());
    1078                 Collections.reverse(revNodes);
    1079                 if(a.getNodes().equals(b.getNodes()) || a.getNodes().equals(revNodes)) {
    1080                     removables.add(a);
    1081                     continue outer;
    1082                 }
    1083             }
    1084         }
    1085         ways.removeAll(removables);
    1086     }
    1087 
    1088     /**
    1089      * Removes ways from the given list whose starting or ending node doesn't
    1090      * connect to other ways from the same list (it's like removing spikes).
    1091      * @param ArrayList<Way> The list of ways to remove "spikes" from
    1092      */
    1093     private void removePartlyUnconnectedWays(ArrayList<Way> ways) {
    1094         List<Way> removables = new ArrayList<Way>();
    1095         for(Way a : ways) {
    1096             if(a.isClosed()) {
    1097                 continue;
    1098             }
    1099             boolean connectedStart = false;
    1100             boolean connectedEnd = false;
    1101             for(Way b : ways) {
    1102                 if(a.equals(b)) {
    1103                     continue;
    1104                 }
    1105                 if(b.isFirstLastNode(a.firstNode())) {
    1106                     connectedStart = true;
    1107                 }
    1108                 if(b.isFirstLastNode(a.lastNode())) {
    1109                     connectedEnd = true;
    1110                 }
    1111             }
    1112             if(!connectedStart || !connectedEnd) {
    1113                 removables.add(a);
    1114             }
    1115         }
    1116         ways.removeAll(removables);
    1117     }
    1118 
    1119     /**
    1120      * Checks if a way is collapsed (i.e. looks like <---->)
    1121      * @param Way A *closed* way to check if it is collapsed
    1122      * @return boolean If the closed way is collapsed or not
    1123      */
    1124     private boolean wayIsCollapsed(Way w) {
    1125         if(w.getNodesCount() <= 3) return true;
    1126 
    1127         // If a way contains more than one node twice, it must be collapsed (only start/end node may be the same)
    1128         Way x = new Way(w);
    1129         int count = 0;
    1130         for(Node n : w.getNodes()) {
    1131             x.removeNode(n);
    1132             if(x.containsNode(n)) {
    1133                 count++;
    1134             }
    1135             if(count == 2) return true;
    1136         }
    1137         return false;
    1138     }
     1601
     1602    /**
     1603     * This method filters the list of relations that form the multipolygons.
     1604     * @param relations
     1605     * @param polygons
     1606     * @return
     1607     */
     1608    private List<Relation> filterOwnMultipolygonRelations(Collection<Relation> relations, List<Multipolygon> polygons) {
     1609
     1610        List<Relation> relationsToRemove = new ArrayList<Relation>();
     1611
     1612        for (Multipolygon m : polygons) {
     1613            if (m.relation != null) {
     1614                relationsToRemove.add(m.relation);
     1615            }
     1616        }
     1617
     1618        List<Relation> result = new ArrayList<Relation>();
     1619
     1620        result.addAll(relations);
     1621        result.removeAll(relationsToRemove);
     1622        return result;
     1623    }
     1624
    11391625
    11401626    /**
     
    11451631     */
    11461632    private void addOwnMultigonRelation(Collection<Way> inner, Way outer, ArrayList<RelationRole> rels) {
    1147         if(inner.size() == 0) return;
     1633        if (inner.size() == 0) return;
    11481634        // Create new multipolygon relation and add all inner ways to it
    11491635        Relation newRel = new Relation();
    11501636        newRel.put("type", "multipolygon");
    1151         for(Way w : inner) {
     1637        for (Way w : inner) {
    11521638            newRel.addMember(new RelationMember("inner", w));
    11531639        }
     
    11611647    }
    11621648
     1649
     1650    /**
     1651     * Removes a given OsmPrimitive from all relations
     1652     * @param OsmPrimitive Element to remove from all relations
     1653     * @return ArrayList<RelationRole> List of relations with roles the primitives was part of
     1654     */
     1655    private ArrayList<RelationRole> removeFromAllRelations(OsmPrimitive osm) {
     1656        ArrayList<RelationRole> result = new ArrayList<RelationRole>();
     1657
     1658        for (Relation r : Main.main.getCurrentDataSet().getRelations()) {
     1659            if (r.isDeleted()) {
     1660                continue;
     1661            }
     1662            for (RelationMember rm : r.getMembers()) {
     1663                if (rm.getMember() != osm) {
     1664                    continue;
     1665                }
     1666
     1667                Relation newRel = new Relation(r);
     1668                List<RelationMember> members = newRel.getMembers();
     1669                members.remove(rm);
     1670                newRel.setMembers(members);
     1671
     1672                cmds.add(new ChangeCommand(r, newRel));
     1673                RelationRole saverel =  new RelationRole(r, rm.getRole());
     1674                if (!result.contains(saverel)) {
     1675                    result.add(saverel);
     1676                }
     1677                break;
     1678            }
     1679        }
     1680
     1681        commitCommands(marktr("Removed Element from Relations"));
     1682        return result;
     1683    }
     1684
     1685
    11631686    /**
    11641687     * Adds the previously removed relations again to the outer way. If there are multiple multipolygon
     
    11701693    private void fixRelations(ArrayList<RelationRole> rels, Way outer) {
    11711694        ArrayList<RelationRole> multiouters = new ArrayList<RelationRole>();
    1172         for(RelationRole r : rels) {
    1173             if( r.rel.get("type") != null &&
     1695        for (RelationRole r : rels) {
     1696            if (r.rel.get("type") != null &&
    11741697                    r.rel.get("type").equalsIgnoreCase("multipolygon") &&
    11751698                    r.role.equalsIgnoreCase("outer")
     
    11851708
    11861709        Relation newRel = null;
    1187         switch(multiouters.size()) {
     1710        switch (multiouters.size()) {
    11881711        case 0:
    11891712            return;
     
    11971720            // Create a new relation with all previous members and (Way)outer as outer.
    11981721            newRel = new Relation();
    1199             for(RelationRole r : multiouters) {
     1722            for (RelationRole r : multiouters) {
    12001723                // Add members
    1201                 for(RelationMember rm : r.rel.getMembers())
    1202                     if(!newRel.getMembers().contains(rm)) {
     1724                for (RelationMember rm : r.rel.getMembers())
     1725                    if (!newRel.getMembers().contains(rm)) {
    12031726                        newRel.addMember(rm);
    12041727                    }
     
    12191742     */
    12201743    private void stripTags(Collection<Way> ways) {
    1221         for(Way w: ways) {
     1744        for (Way w : ways) {
    12221745            stripTags(w);
    12231746        }
     
    12291752     */
    12301753    private void stripTags(Way x) {
    1231         if(x.getKeys() == null) return;
     1754        if (x.getKeys() == null)
     1755            return;
    12321756        Way y = new Way(x);
    12331757        for (String key : x.keySet()) {
     
    12461770        cmds.clear();
    12471771        int i = Math.max(ur.commands.size() - cmdsCount, 0);
    1248         for(; i < ur.commands.size(); i++) {
     1772        for (; i < ur.commands.size(); i++) {
    12491773            cmds.add(ur.commands.get(i));
    12501774        }
    12511775
    1252         for(i = 0; i < cmds.size(); i++) {
     1776        for (i = 0; i < cmds.size(); i++) {
    12531777            ur.undo();
    12541778        }
Note: See TracChangeset for help on using the changeset viewer.