Index: .classpath
===================================================================
--- .classpath	(revision 3503)
+++ .classpath	(working copy)
@@ -1,22 +1,22 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="src" path="test/unit"/>
-	<classpathentry kind="src" path="test/functional"/>
-	<classpathentry excluding="build/|data_nodist/|dist/|doc/|lib/|macosx/|src/|test/|test/build/|test/functional/|test/performance/|test/unit/|tools/|utils/" kind="src" path=""/>
-	<classpathentry kind="src" path="test/performance"/>
-	<classpathentry kind="lib" path="lib/metadata-extractor-2.3.1-nosun.jar"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
-	<classpathentry kind="lib" path="test/lib/fest/debug-1.0.jar"/>
-	<classpathentry kind="lib" path="test/lib/fest/fest-assert-1.0.jar"/>
-	<classpathentry kind="lib" path="test/lib/fest/fest-reflect-1.1.jar"/>
-	<classpathentry kind="lib" path="test/lib/fest/fest-swing-1.1.jar"/>
-	<classpathentry kind="lib" path="test/lib/fest/fest-util-1.0.jar"/>
-	<classpathentry kind="lib" path="test/lib/fest/jcip-annotations-1.0.jar"/>
-	<classpathentry kind="lib" path="test/lib/fest/MRJToolkitStubs-1.0.jar"/>
-	<classpathentry kind="lib" path="test/lib/jfcunit.jar"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JDK 6"/>
-	<classpathentry exported="true" kind="con" path="GROOVY_SUPPORT"/>
-	<classpathentry kind="lib" path="lib/signpost-core-1.2.1.1.jar"/>
-	<classpathentry kind="output" path="bin"/>
-</classpath>
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="test/unit"/>
+	<classpathentry kind="src" path="test/functional"/>
+	<classpathentry excluding="build/|data_nodist/|dist/|doc/|lib/|macosx/|src/|test/|test/build/|test/functional/|test/performance/|test/unit/|tools/|utils/" kind="src" path=""/>
+	<classpathentry kind="src" path="test/performance"/>
+	<classpathentry kind="lib" path="lib/metadata-extractor-2.3.1-nosun.jar"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
+	<classpathentry kind="lib" path="test/lib/fest/debug-1.0.jar"/>
+	<classpathentry kind="lib" path="test/lib/fest/fest-assert-1.0.jar"/>
+	<classpathentry kind="lib" path="test/lib/fest/fest-reflect-1.1.jar"/>
+	<classpathentry kind="lib" path="test/lib/fest/fest-swing-1.1.jar"/>
+	<classpathentry kind="lib" path="test/lib/fest/fest-util-1.0.jar"/>
+	<classpathentry kind="lib" path="test/lib/fest/jcip-annotations-1.0.jar"/>
+	<classpathentry kind="lib" path="test/lib/fest/MRJToolkitStubs-1.0.jar"/>
+	<classpathentry kind="lib" path="test/lib/jfcunit.jar"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry exported="true" kind="con" path="GROOVY_SUPPORT"/>
+	<classpathentry kind="lib" path="lib/signpost-core-1.2.1.1.jar"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
Index: src/org/openstreetmap/josm/actions/CombineWayAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/CombineWayAction.java	(revision 3503)
+++ src/org/openstreetmap/josm/actions/CombineWayAction.java	(working copy)
@@ -99,7 +99,7 @@
      *
      * @return the set of referring relations
      */
-    protected Set<Relation> getParentRelations(Collection<Way> ways) {
+    public static Set<Relation> getParentRelations(Collection<Way> ways) {
         HashSet<Relation> ret = new HashSet<Relation>();
         for (Way w: ways) {
             ret.addAll(OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class));
Index: src/org/openstreetmap/josm/actions/JoinAreasAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/JoinAreasAction.java	(revision 3503)
+++ src/org/openstreetmap/josm/actions/JoinAreasAction.java	(working copy)
@@ -1,11 +1,13 @@
 // License: GPL. Copyright 2007 by Immanuel Scholz and others
 package org.openstreetmap.josm.actions;
 
+import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.combineTigerTags;
+import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.completeTagCollectionForEditing;
+import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing;
 import static org.openstreetmap.josm.tools.I18n.marktr;
 import static org.openstreetmap.josm.tools.I18n.tr;
 import static org.openstreetmap.josm.tools.I18n.trn;
 
-import java.awt.GridBagLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.KeyEvent;
 import java.awt.geom.Area;
@@ -13,21 +15,17 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeMap;
-import java.util.TreeSet;
 
-import javax.swing.Box;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
 import javax.swing.JOptionPane;
-import javax.swing.JPanel;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.SplitWayAction.SplitWayResult;
@@ -44,10 +42,9 @@
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.RelationMember;
-import org.openstreetmap.josm.data.osm.TigerUtils;
+import org.openstreetmap.josm.data.osm.TagCollection;
 import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.gui.ExtendedDialog;
-import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog;
 import org.openstreetmap.josm.tools.Shortcut;
 
 public class JoinAreasAction extends JosmAction {
@@ -55,51 +52,29 @@
     private LinkedList<Command> cmds = new LinkedList<Command>();
     private int cmdsCount = 0;
 
+
     /**
      * This helper class describes join ares action result.
      * @author viesturs
      *
      */
-    public static class JoinAreasResult {
+    public static class JoinAreasResult{
 
-        public Way outerWay;
-        public List<Way> innerWays;
-
         public boolean mergeSuccessful;
         public boolean hasChanges;
         public boolean hasRelationProblems;
+
+        public List<ComplexPolygon> polygons;
     }
 
-    // HelperClass
-    // Saves a node and two positions where to insert the node into the ways
-    private static class NodeToSegs implements Comparable<NodeToSegs> {
-        public int pos;
-        public Node n;
-        public double dis;
-        public NodeToSegs(int pos, Node n, LatLon dis) {
-            this.pos = pos;
-            this.n = n;
-            this.dis = n.getCoor().greatCircleDistance(dis);
-        }
+    public static class ComplexPolygon{
+        public Way outerWay;
+        public List<Way> innerWays;
 
-        public int compareTo(NodeToSegs o) {
-            if(this.pos == o.pos)
-                return (this.dis - o.dis) > 0 ? 1 : -1;
-                return this.pos - o.pos;
+        public ComplexPolygon(Way way){
+            outerWay = way;
+            innerWays = new ArrayList<Way>();
         }
-
-        @Override
-        public int hashCode() {
-            return pos;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o instanceof NodeToSegs)
-                return compareTo((NodeToSegs) o) == 0;
-            else
-                return false;
-        }
     }
 
     // HelperClass
@@ -125,19 +100,17 @@
         }
     }
 
-    /**
-     * HelperClass
-     * saves a way and the "inside" side
-     * insideToTheLeft: if true left side is "in", false -right side is "in".
-     * Left and right are determined along the orientation of way.
-     */
-    private static class WayInPath {
+
+    //HelperClass
+    //saves a way and the "inside" side
+    // insideToTheLeft: if true left side is "in", false -right side is "in". Left and right are determined along the orientation of way.
+    private static class WayInPath{
         public final Way way;
-        public boolean insideToTheLeft;
+        public boolean insideToTheRight;
 
-        public WayInPath(Way way, boolean insideLeft) {
-            this.way = way;
-            this.insideToTheLeft = insideLeft;
+        public WayInPath(Way _way, boolean _insideRight){
+            this.way = _way;
+            this.insideToTheRight = _insideRight;
         }
 
         @Override
@@ -147,13 +120,164 @@
 
         @Override
         public boolean equals(Object other) {
-            if (!(other instanceof WayInPath))
-                return false;
+            if (!(other instanceof WayInPath)) return false;
             WayInPath otherMember = (WayInPath) other;
-            return otherMember.way.equals(this.way) && otherMember.insideToTheLeft == this.insideToTheLeft;
+            return otherMember.way.equals(this.way) && otherMember.insideToTheRight == this.insideToTheRight;
         }
     }
 
+    /**
+     * This hepler class implements algorithm traversing trough connected ways.
+     * Assumes you are going in clockwise orientation.
+     * @author viesturs
+     * 
+     */
+    private static class WayTraverser {
+
+        private Set<WayInPath> availableWays;
+        private WayInPath lastWay;
+        private boolean lastWayReverse;
+
+        public WayTraverser(Collection<WayInPath> ways){
+
+            availableWays = new HashSet<WayInPath>(ways);
+            lastWay = null;
+        }
+
+        public void removeWays(Collection<WayInPath> ways){
+            availableWays.removeAll(ways);
+        }
+
+        public boolean hasWays(){
+            return availableWays.size() > 0;
+        }
+
+        public WayInPath startNewWay(WayInPath way) {
+            lastWay = way;
+            lastWayReverse = !lastWay.insideToTheRight;
+
+            return lastWay;
+        }
+
+        public WayInPath startNewWay() {
+            if (availableWays.size() == 0) {
+                lastWay = null;
+            } else {
+                lastWay = availableWays.iterator().next();
+                lastWayReverse = !lastWay.insideToTheRight;
+            }
+
+            return lastWay;
+        }
+
+
+        public  WayInPath advanceNextLeftmostWay(){
+            return advanceNextWay(false);
+        }
+
+        public  WayInPath advanceNextRightmostWay(){
+            return advanceNextWay(true);
+        }
+
+        private WayInPath advanceNextWay(boolean rightmost){
+
+            Node headNode = !lastWayReverse ? lastWay.way.lastNode() : lastWay.way.firstNode();
+            Node prevNode = !lastWayReverse ? lastWay.way.getNode(lastWay.way.getNodesCount() - 2) : lastWay.way.getNode(1);
+
+            //find best next way
+            WayInPath bestWay = null;
+            Node bestWayNextNode = null;
+            boolean bestWayReverse = false;
+
+            for(WayInPath way: availableWays)
+            {
+                if (way.way.firstNode().equals(headNode)){
+                    //start adjacent to headNode
+                    Node nextNode = way.way.getNode(1);
+
+                    if (nextNode.equals(prevNode))
+                    {
+                        //this is the path we came from - ignore it.
+                    }
+                    else if (bestWay == null || (isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost) )
+                    {
+                        //the new way is better
+                        bestWay = way;
+                        bestWayReverse = false;
+                        bestWayNextNode = nextNode;
+                    }
+                }
+
+                if (way.way.lastNode().equals(headNode))
+                {
+                    //end adjacent to headNode
+                    Node nextNode = way.way.getNode(way.way.getNodesCount() - 2);
+
+                    if (nextNode.equals(prevNode))
+                    {
+                        //this is the path we came from - ignore it.
+                    }
+                    else if (bestWay == null || (isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost))
+                    {
+                        //the new way is better
+                        bestWay = way;
+                        bestWayReverse = true;
+                        bestWayNextNode = nextNode;
+                    }
+                }
+            }
+
+            lastWay = bestWay;
+            lastWayReverse = bestWayReverse;
+
+            return lastWay;
+        }
+
+        public boolean isLastWayInsideToTheRight(){
+            return lastWayReverse != lastWay.insideToTheRight;
+        }
+    }
+
+    /**
+     * Provides some node order , based on coordinates, nodes with equal coordinates are equal.
+     * @author viesturs
+     *
+     */
+    private class NodePositionComparator implements Comparator<Node>{
+
+        @Override
+        public int compare(Node n1, Node n2) {
+
+            double dLat = n1.getCoor().lat() - n2.getCoor().lat();
+            double dLon = n1.getCoor().lon() - n2.getCoor().lon();
+
+            if (dLat > 0)
+                return 1;
+            else if (dLat < 0)
+                return -1;
+            else if (dLon == 0) //dlat is 0 here
+                return 0;
+            else
+                return dLon > 0 ? 1: -1;
+        }
+    }
+
+
+    /**
+     * Helper storage class for finding findOuterWays
+     * @author viesturs
+     */
+    static class PolygonLevel{
+        public final int level;
+        public final ComplexPolygon pol;
+
+        public PolygonLevel(ComplexPolygon _pol, int _level){
+            pol = _pol;
+            level = _level;
+        }
+    }
+
+
     // Adds the menu entry, Shortcuts, etc.
     public JoinAreasAction() {
         super(tr("Join overlapping Areas"), "joinareas", tr("Joins areas that overlap each other"), Shortcut.registerShortcut("tools:joinareas", tr("Tool: {0}", tr("Join overlapping Areas")),
@@ -172,12 +296,6 @@
             return;
         }
 
-        // Too many ways
-        if(ways.size() > 2) {
-            JOptionPane.showMessageDialog(Main.parent, tr("Only up to two areas can be joined at the moment."));
-            return;
-        }
-
         List<Node> allNodes = new ArrayList<Node>();
         for (Way way: ways) {
             if(!way.isClosed()) {
@@ -208,14 +326,23 @@
             }
         }
 
-        if (checkForTagConflicts(ways.getFirst(), ways.getLast()))
-            //there was conflicts and user canceled abort the action.
+        if(!resolveTagConflicts(ways))
             return;
+        //user cancelled, do nothing.
 
+        //collect ways and analyze multipolygon relations
+        List<ComplexPolygon> areas = new ArrayList<ComplexPolygon>();
 
-        JoinAreasResult result = joinAreas(ways.getFirst(), ways.getLast());
+        for (Way way : ways)
+        {
+            //TODO: analyze multipolygon relations...
+            areas.add(new ComplexPolygon(way));
+        }
 
-        if (result.hasChanges) {
+        JoinAreasResult result = joinAreas(areas);
+
+        if (result.hasChanges){
+
             Main.map.mapView.repaint();
             DataSet ds = Main.main.getCurrentDataSet();
             ds.fireSelectionChanged();
@@ -224,83 +351,98 @@
         }
     }
 
+
     /**
-     * Will join two overlapping areas
-     * @param Way First way/area
-     * @param Way Second way/area
+     * Will join two or more overlapping areas
+     * @param areas - list of areas to join
+     * @return new area formed.
      */
-    private JoinAreasResult joinAreas(Way a, Way b) {
+    private JoinAreasResult joinAreas(List<ComplexPolygon> areas) {
 
         JoinAreasResult result = new JoinAreasResult();
         result.hasChanges = false;
 
-        // Fix self-overlapping first or other errors
-        boolean same = a.equals(b);
-        if(!same) {
-            int i = 0;
+        List<Way> allStartingWays = new ArrayList<Way>();
+        List<Way> innerStartingWays = new ArrayList<Way>();
+        List<Way> outerStartingWays = new ArrayList<Way>();
 
-            //join each area with itself, fixing self-crossings.
-            JoinAreasResult resultA = joinAreas(a, a);
-            JoinAreasResult resultB = joinAreas(b, b);
+        for(ComplexPolygon area: areas)
+        {
+            outerStartingWays.add(area.outerWay);
+            innerStartingWays.addAll(area.innerWays);
+        }
 
-            if (resultA.mergeSuccessful) {
-                a = resultA.outerWay;
-                ++i;
-            }
-            if(resultB.mergeSuccessful) {
-                b = resultB.outerWay;
-                ++i;
-            }
+        allStartingWays.addAll(innerStartingWays);
+        allStartingWays.addAll(outerStartingWays);
 
-            result.hasChanges = i > 0;
-            cmdsCount = i;
+        //first remove nodes in the same coordinate
+        boolean removedDuplicates = false;
+        removedDuplicates |= removeDuplicateNodes(allStartingWays);
+
+        if (removedDuplicates)
+        {
+            result.hasChanges = true;
+            commitCommands(marktr("Removed duplicate nodes"));
         }
 
-        ArrayList<Node> nodes = addIntersections(a, b);
+        //find intersection points
+        ArrayList<Node> nodes = addIntersections(allStartingWays);
 
         //no intersections, return.
         if(nodes.size() == 0) return result;
         commitCommands(marktr("Added node on all intersections"));
 
+        ArrayList<RelationRole> relations = new ArrayList<RelationRole>();
+
         // Remove ways from all relations so ways can be combined/split quietly
-        ArrayList<RelationRole> relations = removeFromRelations(a);
-        if(!same) {
-            relations.addAll(removeFromRelations(b));
+        for(Way way : allStartingWays)
+        {
+            relations.addAll(removeFromRelations(way));
         }
 
         // Don't warn now, because it will really look corrupted
         boolean warnAboutRelations = relations.size() > 0;
 
-        ArrayList<Way> allWays = splitWaysOnNodes(a, b, nodes);
+        ArrayList<WayInPath> preparedWays = new ArrayList<WayInPath>();
 
+        for (Way way: outerStartingWays){
+            ArrayList<Way> splitWays = splitWayOnNodes(way, nodes);
+            preparedWays.addAll(markWayInsideSide(splitWays, false));
+        }
+
+        for (Way way: innerStartingWays){
+            ArrayList<Way> splitWays = splitWayOnNodes(way, nodes);
+            preparedWays.addAll(markWayInsideSide(splitWays, true));
+        }
+
         // Find inner ways save them to a list
-        ArrayList<WayInPath> outerWays = findOuterWays(allWays);
-        ArrayList<Way> innerWays = findInnerWays(allWays, outerWays);
+        ArrayList<Way> discardedWays = new ArrayList<Way>();
+        List<List<WayInPath>> boundryParts = findBoundaryWays(preparedWays, discardedWays);
+        List<Way> boundaries = joinBoundaries(boundryParts);
 
-        // Join outer ways
-        Way outerWay = joinOuterWays(outerWays);
+        List<ComplexPolygon> polygons = findPolygons(boundaries);
 
-        // Fix Multipolygons if there are any
-        List<Way> newInnerWays = fixMultipolygons(innerWays, outerWay, same);
-
-        // Delete the remaining inner ways
-        if(innerWays != null && innerWays.size() > 0) {
-            cmds.add(DeleteCommand.delete(Main.map.mapView.getEditLayer(), innerWays, true));
+        // Delete the discarded inner ways
+        if(discardedWays.size() > 0) {
+            cmds.add(DeleteCommand.delete(Main.map.mapView.getEditLayer(), discardedWays, true));
         }
         commitCommands(marktr("Delete Ways that are not part of an inner multipolygon"));
 
         // We can attach our new multipolygon relation and pretend it has always been there
-        addOwnMultigonRelation(newInnerWays, outerWay, relations);
-        fixRelations(relations, outerWay);
+        for(ComplexPolygon pol: polygons)
+        {
+            addOwnMultigonRelation(pol.innerWays, pol.outerWay, relations);
+            fixRelations(relations, pol.outerWay);
+        }
+
         commitCommands(marktr("Fix relations"));
 
-        stripTags(newInnerWays);
+        for(ComplexPolygon pol: polygons)
+        {
+            stripTags(pol.innerWays);
+        }
 
-        makeCommitsOneAction(
-                same
-                ? marktr("Joined self-overlapping area")
-                        : marktr("Joined overlapping areas")
-        );
+        makeCommitsOneAction(marktr("Joined overlapping areas"));
 
         if(warnAboutRelations) {
             JOptionPane.showMessageDialog(Main.parent, tr("Some of the ways were part of relations that have been modified. Please verify no errors have been introduced."));
@@ -308,9 +450,7 @@
 
         result.hasChanges = true;
         result.mergeSuccessful = true;
-        result.outerWay = outerWay;
-        result.innerWays = newInnerWays;
-
+        result.polygons = polygons;
         return result;
     }
 
@@ -318,134 +458,298 @@
      * Checks if tags of two given ways differ, and presents the user a dialog to solve conflicts
      * @param Way First way to check
      * @param Way Second Way to check
-     * @return boolean True if not all conflicts could be resolved, False if everything's fine
+     * @return boolean True if all conflicts are resolved, False if conflicts remain.
      */
-    private boolean checkForTagConflicts(Way a, Way b) {
-        ArrayList<Way> ways = new ArrayList<Way>();
-        ways.add(a);
-        ways.add(b);
+    private boolean resolveTagConflicts(List<Way> ways) {
+        //mostly copied from CombineWayAction.java.
 
-        // FIXME: This is mostly copied and pasted from CombineWayAction.java and one day should be moved into tools
-        // We have TagCollection handling for that now - use it here as well
-        Map<String, Set<String>> props = new TreeMap<String, Set<String>>();
-        for (Way w : ways) {
-            for (String key: w.keySet()) {
-                if (!props.containsKey(key)) {
-                    props.put(key, new TreeSet<String>());
-                }
-                props.get(key).add(w.get(key));
-            }
+        TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways);
+        TagCollection completeWayTags = new TagCollection(wayTags);
+        combineTigerTags(completeWayTags);
+        normalizeTagCollectionBeforeEditing(completeWayTags, ways);
+        TagCollection tagsToEdit = new TagCollection(completeWayTags);
+        completeTagCollectionForEditing(tagsToEdit);
+
+        CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
+        dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues());
+        dialog.setTargetPrimitive(ways.get(0));
+        Set<Relation> parentRelations = CombineWayAction.getParentRelations(ways);
+        dialog.getRelationMemberConflictResolverModel().populate(
+                parentRelations,
+                ways
+        );
+        dialog.prepareDefaultDecisions();
+
+        // resolve tag conflicts if necessary
+        //
+        if (!completeWayTags.isApplicableToPrimitive() || !parentRelations.isEmpty()) {
+            dialog.setVisible(true);
+            if (dialog.isCancelled())
+                return false;
         }
 
-        Way ax = new Way(a);
-        Way bx = new Way(b);
+        for(Way way: ways)
+        {
+            dialog.setTargetPrimitive(way);
+            cmds.addAll(dialog.buildResolutionCommands());
+        }
 
-        Map<String, JComboBox> components = new HashMap<String, JComboBox>();
-        JPanel p = new JPanel(new GridBagLayout());
-        for (Entry<String, Set<String>> e : props.entrySet()) {
-            if (TigerUtils.isTigerTag(e.getKey())) {
-                String combined = TigerUtils.combineTags(e.getKey(), e.getValue());
-                ax.put(e.getKey(), combined);
-                bx.put(e.getKey(), combined);
-            } else if (e.getValue().size() > 1) {
-                if("created_by".equals(e.getKey()))
-                {
-                    ax.remove("created_by");
-                    bx.remove("created_by");
-                } else {
-                    JComboBox c = new JComboBox(e.getValue().toArray());
-                    c.setEditable(true);
-                    p.add(new JLabel(e.getKey()), GBC.std());
-                    p.add(Box.createHorizontalStrut(10), GBC.std());
-                    p.add(c, GBC.eol());
-                    components.put(e.getKey(), c);
+        commitCommands(marktr("Fix tag conflicts"));
+        return true;
+    }
+
+
+    /**
+     * This method removes duplicate points (if any) from the input way.
+     * @param way the way to process
+     * @return true if any changes where made
+     */
+    private boolean removeDuplicateNodes(List<Way> ways)
+    {
+        //TODO: maybe join nodes with JoinNodesAction, rather than reconnect the ways.
+
+        Map<Node, Node> nodeMap = new TreeMap<Node, Node>(new NodePositionComparator());
+        int totalNodesRemoved = 0;
+
+        for(Way way:ways)
+        {
+            if (way.getNodes().size() < 2) {
+                continue;
+            }
+
+            int nodesRemoved = 0;
+            List<Node> newNodes = new ArrayList<Node>();
+            Node prevNode = null;
+
+            for(Node node: way.getNodes())
+            {
+                if (!nodeMap.containsKey(node)){
+                    //new node
+                    nodeMap.put(node, node);
+
+                    //avoid duplicate nodes
+                    if (prevNode != node)
+                    {
+                        newNodes.add(node);
+                    }
+                    else
+                    {
+                        nodesRemoved ++;
+                    }
                 }
-            } else {
-                String val = e.getValue().iterator().next();
-                ax.put(e.getKey(), val);
-                bx.put(e.getKey(), val);
+                else{
+                    //node with same coordinates already exists, substitute with existing node
+                    Node representator = nodeMap.get(node);
+
+                    if (representator != node){
+                        nodesRemoved ++;
+                    }
+
+                    //avoid duplicate node
+                    if (prevNode != representator)
+                    {
+                        newNodes.add(representator);
+                    }
+                }
+
+                prevNode = node;
             }
-        }
 
-        if (components.isEmpty())
-            return false; // No conflicts found
+            if (nodesRemoved > 0)
+            {
 
-        ExtendedDialog ed = new ExtendedDialog(Main.parent,
-                tr("Enter values for all conflicts."),
-                new String[] {tr("Solve Conflicts"), tr("Cancel")});
-        ed.setButtonIcons(new String[] {"dialogs/conflict.png", "cancel.png"});
-        ed.setContent(p);
-        ed.showDialog();
+                if (newNodes.size() == 1) {//all nodes in the same coordinate - add one more node, to have closed way.
+                    newNodes.add(newNodes.get(0));
+                }
 
-        if (ed.getValue() != 1) return true; // user cancel, unresolvable conflicts
-
-        for (Entry<String, JComboBox> e : components.entrySet()) {
-            String val = e.getValue().getEditor().getItem().toString();
-            ax.put(e.getKey(), val);
-            bx.put(e.getKey(), val);
+                Way newWay=new Way(way);
+                newWay.setNodes(newNodes);
+                cmds.add(new ChangeCommand(way, newWay));
+                totalNodesRemoved += nodesRemoved;
+            }
         }
 
-        cmds.add(new ChangeCommand(a, ax));
-        cmds.add(new ChangeCommand(b, bx));
-        commitCommands(marktr("Fix tag conflicts"));
-        return false;
+        return totalNodesRemoved > 0;
     }
 
+
+
     /**
-     * Will find all intersection and add nodes there for two given ways
-     * @param Way First way
-     * @param Way Second way
-     * @return ArrayList<OsmPrimitive> List of new nodes
+     * Will find all intersection and add nodes there for list of given ways. Handles self-intersections too.
+     * And make commands to add the intersection points to ways.
+     * @param List<Way> - a list of ways to test
+     * @return ArrayList<Node> List of new nodes
+     * Prerequisite: no two nodes have the same coordinates.
      */
-    private ArrayList<Node> addIntersections(Way a, Way b) {
-        boolean same = a.equals(b);
-        int nodesSizeA = a.getNodesCount();
-        int nodesSizeB = b.getNodesCount();
+    private ArrayList<Node> addIntersections(List<Way> ways) {
+        //TODO: this is a bit slow - O( (number of nodes)^2 + numberOfIntersections * numberOfNodes )
 
-        ArrayList<Node> nodes = new ArrayList<Node>();
-        ArrayList<NodeToSegs> nodesA = new ArrayList<NodeToSegs>();
-        ArrayList<NodeToSegs> nodesB = new ArrayList<NodeToSegs>();
+        //stupid java, cannot instantiate array of generic classes..
+        @SuppressWarnings("unchecked")
+        ArrayList<Node>[] newNodes = new ArrayList[ways.size()];
+        boolean[] changedWays = new boolean[ways.size()];
 
-        for (int i = (same ? 1 : 0); i < nodesSizeA - 1; i++) {
-            for (int j = (same ? i + 2 : 0); j < nodesSizeB - 1; j++) {
-                // Avoid re-adding nodes that already exist on (some) intersections
-                if(a.getNode(i).equals(b.getNode(j)) || a.getNode(i+1).equals(b.getNode(j)))   {
-                    nodes.add(b.getNode(j));
-                    continue;
-                } else
-                    if(a.getNode(i).equals(b.getNode(j+1)) || a.getNode(i+1).equals(b.getNode(j+1))) {
-                        nodes.add(b.getNode(j+1));
-                        continue;
+        Set<Node> intersectionNodes = new LinkedHashSet<Node>();
+
+        for (int pos = 0; pos < ways.size(); pos ++)
+        {
+            newNodes[pos] = new ArrayList<Node>(ways.get(pos).getNodes());
+            changedWays[pos] = false;
+        }
+
+        //iterate over all segment pairs and introduce the intersections
+
+        Comparator<Node> coordsComparator = new NodePositionComparator();
+
+        int seg1Way = 0;
+        int seg1Pos = -1;
+
+        while (true)
+        {
+            //advance to next segment
+            seg1Pos++;
+            if (seg1Pos > newNodes[seg1Way].size() - 2)
+            {
+                seg1Way++;
+                seg1Pos = 0;
+
+                if (seg1Way == ways.size()) { //finished
+                    break;
+                }
+            }
+
+
+            //iterate over secondary segment
+
+            int seg2Way = seg1Way;
+            int seg2Pos = seg1Pos + 1;//skip the adjacent segment
+
+            while (true)
+            {
+
+                //advance to next segment
+                seg2Pos++;
+                if (seg2Pos > newNodes[seg2Way].size() - 2)
+                {
+                    seg2Way++;
+                    seg2Pos = 0;
+
+                    if (seg2Way == ways.size()) { //finished
+                        break;
                     }
-                LatLon intersection = getLineLineIntersection(
-                        a.getNode(i)  .getEastNorth().east(), a.getNode(i)  .getEastNorth().north(),
-                        a.getNode(i+1).getEastNorth().east(), a.getNode(i+1).getEastNorth().north(),
-                        b.getNode(j)  .getEastNorth().east(), b.getNode(j)  .getEastNorth().north(),
-                        b.getNode(j+1).getEastNorth().east(), b.getNode(j+1).getEastNorth().north());
-                if(intersection == null) {
-                    continue;
                 }
 
-                // Create the node. Adding them to the ways must be delayed because we still loop over them
-                Node n = new Node(intersection);
-                cmds.add(new AddCommand(n));
-                nodes.add(n);
-                // The distance is needed to sort and add the nodes in direction of the way
-                nodesA.add(new NodeToSegs(i,  n, a.getNode(i).getCoor()));
-                if(same) {
-                    nodesA.add(new NodeToSegs(j,  n, a.getNode(j).getCoor()));
-                } else {
-                    nodesB.add(new NodeToSegs(j,  n, b.getNode(j).getCoor()));
+                //need to get them again every time, because other segments may be changed
+                Node seg1Node1 = newNodes[seg1Way].get(seg1Pos);
+                Node seg1Node2 = newNodes[seg1Way].get(seg1Pos + 1);
+                Node seg2Node1 = newNodes[seg2Way].get(seg2Pos);
+                Node seg2Node2 = newNodes[seg2Way].get(seg2Pos + 1);
+
+                int commonCount = 0;
+                //test if we have common nodes to add.
+                if (seg1Node1 == seg2Node1 || seg1Node1 == seg2Node2)
+                {
+                    commonCount ++;
+
+                    if (seg1Way == seg2Way &&
+                            seg1Pos == 0 &&
+                            seg2Pos == newNodes[seg2Way].size() -2)
+                    {
+                        //do not add - this is first and last segment of the same way.
+                    }
+                    else
+                    {
+                        intersectionNodes.add(seg1Node1);
+                    }
                 }
+
+                if (seg1Node2 == seg2Node1 || seg1Node2 == seg2Node2)
+                {
+                    commonCount ++;
+
+                    intersectionNodes.add(seg1Node2);
+                }
+
+                //no common nodes - find intersection
+                if (commonCount == 0)
+                {
+                    LatLon intersection = getLineLineIntersection(
+                            seg1Node1.getEastNorth().east(), seg1Node1.getEastNorth().north(),
+                            seg1Node2.getEastNorth().east(), seg1Node2.getEastNorth().north(),
+                            seg2Node1.getEastNorth().east(), seg2Node1.getEastNorth().north(),
+                            seg2Node2.getEastNorth().east(), seg2Node2.getEastNorth().north());
+
+                    if (intersection != null)
+                    {
+                        Node newNode = new Node(intersection);
+                        Node intNode = newNode;
+                        boolean insertInSeg1 = false;
+                        boolean insertInSeg2 = false;
+
+                        //find if the intersection point is at end point of one of the segments, if so use that point
+
+                        //segment 1
+                        if (coordsComparator.compare(newNode, seg1Node1) == 0) {
+                            intNode = seg1Node1;
+                        } else if (coordsComparator.compare(newNode, seg1Node2) == 0) {
+                            intNode = seg1Node2;
+                        } else {
+                            insertInSeg1 = true;
+                        }
+
+                        //segment 2
+                        if (coordsComparator.compare(newNode, seg2Node1) == 0) {
+                            intNode = seg2Node1;
+                        } else if (coordsComparator.compare(newNode, seg2Node2) == 0) {
+                            intNode = seg2Node2;
+                        } else {
+                            insertInSeg2 = true;
+                        }
+
+                        if (insertInSeg1)
+                        {
+                            newNodes[seg1Way].add(seg1Pos +1, intNode);
+                            changedWays[seg1Way] = true;
+
+                            //fix seg2 position, as indexes have changed, seg2Pos is always bigger than seg1Pos on the same segment.
+                            if (seg2Way == seg1Way) {
+                                seg2Pos ++;
+                            }
+                        }
+
+                        if (insertInSeg2){
+                            newNodes[seg2Way].add(seg2Pos +1, intNode);
+                            changedWays[seg2Way] = true;
+
+                            //Do not need to compare again to already split segment
+                            seg2Pos ++;
+                        }
+
+                        intersectionNodes.add(intNode);
+
+                        if (intNode == newNode)
+                        {
+                            cmds.add(new AddCommand(intNode));
+                        }
+                    }
+                }
             }
         }
 
-        addNodesToWay(a, nodesA);
-        if(!same) {
-            addNodesToWay(b, nodesB);
+        for (int pos = 0; pos < ways.size(); pos ++)
+        {
+            if (changedWays[pos] == false) {
+                continue;
+            }
+
+            Way way = ways.get(pos);
+            Way newWay = new Way(way);
+            newWay.setNodes(newNodes[pos]);
+
+            cmds.add(new ChangeCommand(way, newWay));
         }
 
-        return nodes;
+        return new ArrayList<Node>(intersectionNodes);
     }
 
     /**
@@ -477,26 +781,8 @@
         ));
     }
 
-    /**
-     * Inserts given nodes with positions into the given ways
-     * @param Way The way to insert the nodes into
-     * @param Collection<NodeToSegs> The list of nodes with positions to insert
-     */
-    private void addNodesToWay(Way a, ArrayList<NodeToSegs> nodes) {
-        if(nodes.size() == 0)
-            return;
-        Way ax=new Way(a);
-        Collections.sort(nodes);
 
-        int numOfAdds = 1;
-        for(NodeToSegs n : nodes) {
-            ax.addNode(n.pos + numOfAdds, n.n);
-            numOfAdds++;
-        }
 
-        cmds.add(new ChangeCommand(a, ax));
-    }
-
     /**
      * Commits the command list with a description
      * @param String The description of what the commands do
@@ -552,38 +838,222 @@
         return result;
     }
 
+
     /**
-     * This method splits ways into smaller parts, using the prepared nodes list as split points.
-     * Uses SplitWayAction.splitWay for the heavy lifting.
+     * This method analyzes the way and assigns each part what direction polygon "inside" is.
+     * @param parts the split parts of the way
+     * @param isInner - if true, reverts the direction (for multipolygon islands)
+     * @return list of parts, marked with the inside orientation.
+     */
+    private ArrayList<WayInPath> markWayInsideSide(List<Way> parts, boolean isInner){
+
+        ArrayList<WayInPath> result = new ArrayList<WayInPath>();
+
+        //prepare prev and next maps
+        Map<Way, Way> nextWayMap = new HashMap<Way, Way>();
+        Map<Way, Way> prevWayMap = new HashMap<Way, Way>();
+
+        for (int pos = 0; pos < parts.size(); pos ++){
+
+            if (!parts.get(pos).lastNode().equals(parts.get((pos + 1) % parts.size()).firstNode()))
+                throw new RuntimeException("Way not circular");
+
+            nextWayMap.put(parts.get(pos), parts.get((pos + 1) % parts.size()));
+            prevWayMap.put(parts.get(pos), parts.get((pos + parts.size() - 1) % parts.size()));
+        }
+
+        //find the node with minimum y - it's guaranteed to be outer. (What about the south pole?)
+        Way topWay = null;
+        Node topNode = null;
+        int topIndex = 0;
+        double minY = Double.POSITIVE_INFINITY;
+
+        for(Way way: parts) {
+            for (int pos = 0; pos < way.getNodesCount(); pos ++){
+                Node node = way.getNode(pos);
+
+                if (node.getEastNorth().getY() < minY){
+                    minY = node.getEastNorth().getY();
+                    topWay = way;
+                    topNode = node;
+                    topIndex = pos;
+                }
+            }
+        }
+
+        //get the upper way and it's orientation.
+
+        boolean wayClockwise; // orientation of the top way.
+
+        if (topNode.equals(topWay.firstNode()) || topNode.equals(topWay.lastNode()))
+        {
+            Node headNode = null; // the node at junction
+            Node prevNode = null; // last node from previous path
+            wayClockwise = false;
+
+            //node is in split point - find the outermost way from this point
+
+            headNode = topNode;
+            //make a fake node that is downwards from head node (smaller Y). It will be a division point between paths.
+            prevNode = new Node(new EastNorth(headNode.getEastNorth().getX(), headNode.getEastNorth().getY() - 1e5));
+
+            topWay = null;
+            wayClockwise = false;
+            Node bestWayNextNode = null;
+
+            for(Way way: parts)
+            {
+                if (way.firstNode().equals(headNode))
+                {
+                    Node nextNode = way.getNode(1);
+
+                    if (topWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode))
+                    {
+                        //the new way is better
+                        topWay = way;
+                        wayClockwise = true;
+                        bestWayNextNode = nextNode;
+                    }
+                }
+
+                if (way.lastNode().equals(headNode))
+                {
+                    //end adjacent to headNode
+                    Node nextNode = way.getNode(way.getNodesCount() - 2);
+
+                    if (topWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode))
+                    {
+                        //the new way is better
+                        topWay = way;
+                        wayClockwise = false;
+                        bestWayNextNode = nextNode;
+                    }
+                }
+            }
+        }
+        else
+        {
+            //node is inside way - pick the clockwise going end.
+            Node prev = topWay.getNode(topIndex - 1);
+            Node next = topWay.getNode(topIndex + 1);
+
+            //there will be no parallel segments in the middle of way, so all fine.
+            wayClockwise = angleIsClockwise(prev, topNode, next);
+        }
+
+        Way curWay = topWay;
+        boolean curWayInsideToTheRight = wayClockwise ^ isInner;
+
+        //iterate till full circle is reached
+        while (true){
+
+            //add cur way
+            WayInPath resultWay = new WayInPath(curWay, curWayInsideToTheRight);
+            result.add(resultWay);
+
+            //process next way
+            Way nextWay = nextWayMap.get(curWay);
+            Node prevNode = curWay.getNode(curWay.getNodesCount() - 2);
+            Node headNode = curWay.lastNode();
+            Node nextNode = nextWay.getNode(1);
+
+            if (nextWay == topWay)
+            {
+                //full loop traversed - all done.
+                break;
+            }
+
+
+            //find intersecting segments
+            // the intersections will look like this:
+            //
+            //                       ^
+            //                       |
+            //                       X wayBNode
+            //                       |
+            //                  wayB |
+            //                       |
+            //             curWay    |       nextWay
+            //----X----------------->X----------------------X---->
+            //    prevNode           ^headNode              nextNode
+            //                       |
+            //                       |
+            //                  wayA |
+            //                       |
+            //                       X wayANode
+            //                       |
+
+            int intersectionCount = 0;
+
+            for (Way wayA: parts){
+
+                if (wayA == curWay){
+                    continue;
+                }
+
+                if (wayA.lastNode().equals(headNode)){
+
+                    Way wayB = nextWayMap.get(wayA);
+
+                    //test if wayA is opposite wayB relative to curWay and nextWay
+
+                    Node wayANode = wayA.getNode(wayA.getNodesCount() - 2);
+                    Node wayBNode = wayB.getNode(1);
+
+                    boolean wayAToTheRight = isToTheRightSideOfLine(prevNode, headNode, nextNode, wayANode);
+                    boolean wayBToTheRight = isToTheRightSideOfLine(prevNode, headNode, nextNode, wayBNode);
+
+                    if (wayAToTheRight != wayBToTheRight){
+                        intersectionCount ++;
+                    }
+                }
+            }
+
+            //if odd number of crossings, invert orientation
+            if (intersectionCount % 2 == 1){
+                curWayInsideToTheRight = !curWayInsideToTheRight;
+            }
+
+            curWay = nextWay;
+        }
+
+        return result;
+    }
+
+    /**
+     * This is a method splits way into smaller parts, using the prepared nodes list as split points.
+     * Uses  SplitWayAction.splitWay for the heavy lifting.
      * @return list of split ways (or original ways if no splitting is done).
      */
-    private ArrayList<Way> splitWaysOnNodes(Way a, Way b, Collection<Node> nodes) {
+    private ArrayList<Way> splitWayOnNodes(Way way, Collection<Node> nodes) {
 
         ArrayList<Way> result = new ArrayList<Way>();
-        List<Way> ways = new ArrayList<Way>();
-        ways.add(a);
-        ways.add(b);
+        List<List<Node>> chunks = buildNodeChunks(way, nodes);
 
-        for (Way way: ways) {
-            List<List<Node>> chunks = buildNodeChunks(way, nodes);
+        if (chunks.size() > 1)
+        {
             SplitWayResult split = SplitWayAction.splitWay(Main.map.mapView.getEditLayer(), way, chunks, Collections.<OsmPrimitive>emptyList());
 
             //execute the command, we need the results
-            Main.main.undoRedo.add(split.getCommand());
-            cmdsCount ++;
+            cmds.add(split.getCommand());
+            commitCommands(marktr("Split ways into fragments"));
 
             result.add(split.getOriginalWay());
             result.addAll(split.getNewWays());
+        } else {
+            //nothing to split
+            result.add(way);
         }
 
         return result;
     }
 
+
     /**
      * Simple chunking version. Does not care about circular ways and result being proper, we will glue it all back together later on.
      * @param way the way to chunk
      * @param splitNodes the places where to cut.
-     * @return list of node segments to produce.
+     * @return list of node paths to produce.
      */
     private List<List<Node>> buildNodeChunks(Way way, Collection<Node> splitNodes)
     {
@@ -607,156 +1077,186 @@
         return result;
     }
 
-    /**
-     * Returns all nodes for given ways
-     * @param Collection<Way> The list of ways which nodes are to be returned
-     * @return Collection<Node> The list of nodes the ways contain
-     */
-    private Collection<Node> getNodesFromWays(Collection<Way> ways) {
-        Collection<Node> allNodes = new ArrayList<Node>();
-        for(Way w: ways) {
-            allNodes.addAll(w.getNodes());
+
+    private List<ComplexPolygon> findPolygons(Collection<Way> boundaryWays) {
+
+        List<PolygonLevel> list = findOuterWaysImpl(0, boundaryWays);
+        List<ComplexPolygon> result = new ArrayList<ComplexPolygon>();
+
+        //take every other level
+        for(PolygonLevel pol: list){
+            if (pol.level %2 == 0){
+                result.add(pol.pol);
+            }
         }
-        return allNodes;
+
+        return result;
     }
 
     /**
-     * Gets all inner ways given all ways and outer ways.
-     * @param multigonWays
-     * @param outerWays
-     * @return list of inner ways.
+     * Collects outer way and corresponding inner ways from all boundaries.
+     * @param boundaryWays
+     * @return the outermostWay.
      */
-    private ArrayList<Way> findInnerWays(Collection<Way> multigonWays, Collection<WayInPath> outerWays) {
-        ArrayList<Way> innerWays = new ArrayList<Way>();
-        Set<Way> outerSet = new HashSet<Way>();
+    private List<PolygonLevel> findOuterWaysImpl(int level, Collection<Way> boundaryWays) {
 
-        for(WayInPath w: outerWays) {
-            outerSet.add(w.way);
-        }
+        //TODO: bad performance for deep nestings...
+        List<PolygonLevel> result = new ArrayList<PolygonLevel>();
 
-        for(Way way: multigonWays) {
-            if (!outerSet.contains(way)) {
-                innerWays.add(way);
+        for(Way outerWay: boundaryWays){
+
+            boolean outerGood = true;
+            List<Way> innerCandidates = new ArrayList<Way>();
+
+            for (Way innerWay: boundaryWays)
+            {
+                if (innerWay == outerWay) {
+                    continue;
+                }
+
+                if (wayInsideWay(outerWay, innerWay))
+                {
+                    outerGood = false;
+                    break;
+                } else if (wayInsideWay(innerWay, outerWay)){
+                    innerCandidates.add(innerWay);
+                }
             }
+
+            if (!outerGood)
+            {
+                continue;
+            }
+
+            //add new outer polygon
+            ComplexPolygon pol = new ComplexPolygon(outerWay);
+            PolygonLevel polLev = new PolygonLevel(pol, level);
+
+            //process inner ways
+            if (innerCandidates.size() > 0)
+            {
+                List<PolygonLevel> innerList = findOuterWaysImpl(level + 1, innerCandidates);
+                result.addAll(innerList);
+
+                for (PolygonLevel pl: innerList)
+                {
+                    if (pl.level == level + 1)
+                    {
+                        pol.innerWays.add(pl.pol.outerWay);
+                    }
+                }
+            }
+
+            result.add(polLev);
         }
 
-        return innerWays;
+        return result;
     }
 
 
+
     /**
-     * Finds all ways for a given list of Ways that form the outer hull.
-     * This works by starting with one node and traversing the multigon clockwise, always picking the leftmost path.
-     * Prerequisites - the ways must not intersect and have common end nodes where they meet.
-     * @param Collection<Way> A list of (splitted) ways that form a multigon
-     * @return Collection<Way> A list of ways that form the outer boundary of the multigon.
+     * Finds all ways that form inner or outer boundaries.
+     * @param Collection<Way> A list of (splitted) ways that form a multigon and share common end nodes on intersections.
+     * @param Collection<Way> this list is filled with ways that are to be discarded
+     * @return Collection<Collection<Way>> A list of ways that form the outer and inner boundaries of the multigon.
      */
-    private static ArrayList<WayInPath> findOuterWays(Collection<Way> multigonWays) {
+    public static List<List<WayInPath>> findBoundaryWays(Collection<WayInPath> multigonWays, List<Way> discardedResult)
+    {
+        //first find all discardable ways, by getting outer shells.
+        //this will produce incorrect boundaries in some cases, but second pass will fix it.
 
-        //find the node with minimum lat - it's guaranteed to be outer. (What about the south pole?)
-        Way bestWay = null;
-        Node topNode = null;
-        int topIndex = 0;
-        double minLat = Double.POSITIVE_INFINITY;
+        List<WayInPath> discardedWays = new ArrayList<WayInPath>();
+        Set<WayInPath> processedWays = new HashSet<WayInPath>();
+        WayTraverser traverser = new WayTraverser(multigonWays);
 
-        for(Way way: multigonWays) {
-            for (int pos = 0; pos < way.getNodesCount(); pos ++) {
-                Node node = way.getNode(pos);
 
-                if (node.getCoor().lat() < minLat) {
-                    minLat = node.getCoor().lat();
-                    bestWay = way;
-                    topNode = node;
-                    topIndex = pos;
-                }
+        for( WayInPath startWay: multigonWays)
+        {
+            if (processedWays.contains(startWay)) {
+                continue;
             }
-        }
 
-        //get two final nodes from best way to mark as starting point and orientation.
-        Node headNode = null;
-        Node prevNode = null;
+            traverser.startNewWay(startWay);
 
-        if (topNode.equals(bestWay.firstNode()) || topNode.equals(bestWay.lastNode())) {
-            //node is in split point
-            headNode = topNode;
-            //make a fake node that is downwards from head node (smaller latitude). It will be a division point between paths.
-            prevNode = new Node(new LatLon(headNode.getCoor().lat() - 1000, headNode.getCoor().lon()));
-        } else {
-            //node is inside way - pick the clockwise going end.
-            Node prev = bestWay.getNode(topIndex - 1);
-            Node next = bestWay.getNode(topIndex + 1);
+            List<WayInPath> boundary = new ArrayList<WayInPath>();
+            WayInPath lastWay = startWay;
 
-            if (angleIsClockwise(prev, topNode, next)) {
-                headNode = bestWay.lastNode();
-                prevNode = bestWay.getNode(bestWay.getNodesCount() - 2);
+            while (true) {
+                boundary.add(lastWay);
+
+                WayInPath bestWay = traverser.advanceNextLeftmostWay();
+                boolean wayInsideToTheRight = bestWay == null ? false: traverser.isLastWayInsideToTheRight();
+
+                if (bestWay == null || processedWays.contains(bestWay) || !wayInsideToTheRight) {
+                    //bad segment chain - proceed to discard it
+                    lastWay = null;
+                    break;
+                } else if (boundary.contains(bestWay)){
+                    //traversed way found - close the way
+                    lastWay = bestWay;
+                    break;
+                } else {
+                    //proceed to next segment
+                    lastWay = bestWay;
+                }
             }
-            else {
-                headNode = bestWay.firstNode();
-                prevNode = bestWay.getNode(1);
+
+            if (lastWay != null)
+            {
+                //way good
+                processedWays.addAll(boundary);
+
+                //remove junk segments at the start
+                while (boundary.get(0) != lastWay)
+                {
+                    discardedWays.add(boundary.get(0));
+                    boundary.remove(0);
+                }
             }
+            else
+            {
+                //way bad
+                discardedWays.addAll(boundary);
+                processedWays.addAll(boundary);
+            }
         }
 
-        Set<Way> outerWays = new HashSet<Way>();
-        ArrayList<WayInPath> result = new ArrayList<WayInPath>();
+        //now we have removed junk segments, collect the real result ways
 
-        //iterate till full circle is reached
-        while (true) {
+        traverser.removeWays(discardedWays);
 
-            bestWay = null;
-            Node bestWayNextNode = null;
-            boolean bestWayReverse = false;
+        List<List<WayInPath>> result = new ArrayList<List<WayInPath>>();
 
-            for (Way way: multigonWays) {
-                boolean wayReverse;
-                Node nextNode;
+        while(traverser.hasWays()){
 
-                if (way.firstNode().equals(headNode)) {
-                    //start adjacent to headNode
-                    nextNode = way.getNode(1);
-                    wayReverse = false;
+            WayInPath startWay = traverser.startNewWay();
+            List<WayInPath> boundary = new ArrayList<WayInPath>();
+            WayInPath curWay = startWay;
 
-                    if (nextNode.equals(prevNode)) {
-                        //this is the path we came from - ignore it.
-                    } else if (bestWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) {
-                        //the new way is better
-                        bestWay = way;
-                        bestWayReverse = wayReverse;
-                        bestWayNextNode = nextNode;
-                    }
-                }
+            do{
+                boundary.add(curWay);
+                curWay = traverser.advanceNextRightmostWay();
 
-                if (way.lastNode().equals(headNode)) {
-                    //end adjacent to headNode
-                    nextNode = way.getNode(way.getNodesCount() - 2);
-                    wayReverse = true;
+                //should not happen
+                if (curWay == null || !traverser.isLastWayInsideToTheRight())
+                    throw new RuntimeException("Join areas internal error.");
 
-                    if (nextNode.equals(prevNode)) {
-                        //this is the path we came from - ignore it.
-                    } else if (bestWay == null || !isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) {
-                        //the new way is better
-                        bestWay = way;
-                        bestWayReverse = wayReverse;
-                        bestWayNextNode = nextNode;
-                    }
-                }
-            }
+            } while (curWay != startWay);
 
-            if (bestWay == null)
-                throw new RuntimeException();
-            else if (outerWays.contains(bestWay)) {
-                break; //full circle reached, terminate.
-            } else {
-                //add to outer ways, repeat.
-                outerWays.add(bestWay);
-                result.add(new WayInPath(bestWay, bestWayReverse));
-                headNode = bestWayReverse ? bestWay.firstNode() : bestWay.lastNode();
-                prevNode = bestWayReverse ? bestWay.getNode(1) : bestWay.getNode(bestWay.getNodesCount() - 2);
-            }
+            //build result
+            traverser.removeWays(boundary);
+            result.add(boundary);
         }
 
+        for(WayInPath way:discardedWays){
+            discardedResult.add(way.way);
+        }
+
         return result;
     }
 
+
     /**
      * Tests if given point is to the right side of path consisting of 3 points.
      * @param lineP1 first point in path
@@ -784,24 +1284,45 @@
      * @param secondNode second vector end node
      * @return true if first vector is clockwise before second vector.
      */
+
     public static boolean angleIsClockwise(Node commonNode, Node firstNode, Node secondNode)
     {
-        double dla1 = (firstNode.getCoor().lat() - commonNode.getCoor().lat());
-        double dla2 = (secondNode.getCoor().lat() - commonNode.getCoor().lat());
-        double dlo1 = (firstNode.getCoor().lon() - commonNode.getCoor().lon());
-        double dlo2 = (secondNode.getCoor().lon() - commonNode.getCoor().lon());
+        double dy1 = (firstNode.getEastNorth().getY() - commonNode.getEastNorth().getY());
+        double dy2 = (secondNode.getEastNorth().getY() - commonNode.getEastNorth().getY());
+        double dx1 = (firstNode.getEastNorth().getX() - commonNode.getEastNorth().getX());
+        double dx2 = (secondNode.getEastNorth().getX() - commonNode.getEastNorth().getX());
 
-        return dla1 * dlo2 - dlo1 * dla2 > 0;
+        return dy1 * dx2 - dx1 * dy2 > 0;
     }
 
+
     /**
+     * Tests if way is inside other way
+     * @param outside
+     * @param inside
+     * @return
+     */
+    public static boolean wayInsideWay(Way inside, Way outside)
+    {
+        for(Node insideNode: inside.getNodes()){
+
+            if (!outside.getNodes().contains(insideNode))
+                //simply test the one node
+                return nodeInsidePolygon(insideNode, outside.getNodes());
+        }
+
+        //all nodes shared.
+        return false;
+    }
+
+    /**
      * Tests if point is inside a polygon. The polygon can be self-intersecting. In such case the contains function works in xor-like manner.
      * @param polygonNodes list of nodes from polygon path.
      * @param point the point to test
      * @return true if the point is inside polygon.
      * FIXME: this should probably be moved to tools..
      */
-    public static boolean nodeInsidePolygon(ArrayList<Node> polygonNodes, Node point)
+    public static boolean nodeInsidePolygon(Node point, List<Node> polygonNodes)
     {
         if (polygonNodes.size() < 3)
             return false;
@@ -820,7 +1341,7 @@
             }
 
             //order points so p1.lat <= p2.lat;
-            if (newPoint.getCoor().lat() > oldPoint.getCoor().lat())
+            if (newPoint.getEastNorth().getY() > oldPoint.getEastNorth().getY())
             {
                 p1 = oldPoint;
                 p2 = newPoint;
@@ -832,9 +1353,9 @@
             }
 
             //test if the line is crossed and if so invert the inside flag.
-            if ((newPoint.getCoor().lat() < point.getCoor().lat()) == (point.getCoor().lat() <= oldPoint.getCoor().lat())
-                    && (point.getCoor().lon() - p1.getCoor().lon()) * (p2.getCoor().lat() - p1.getCoor().lat())
-                    < (p2.getCoor().lon() - p1.getCoor().lon()) * (point.getCoor().lat() - p1.getCoor().lat()))
+            if ((newPoint.getEastNorth().getY() < point.getEastNorth().getY()) == (point.getEastNorth().getY() <= oldPoint.getEastNorth().getY())
+                    && (point.getEastNorth().getX() - p1.getEastNorth().getX()) * (p2.getEastNorth().getY() - p1.getEastNorth().getY())
+                    < (p2.getEastNorth().getX() - p1.getEastNorth().getX()) * (point.getEastNorth().getY() - p1.getEastNorth().getY()))
             {
                 inside = !inside;
             }
@@ -845,26 +1366,46 @@
         return inside;
     }
 
+
     /**
+     * Joins the lists of ways.
+     * @param Collection<Way> The list of outer ways that belong to that multigon.
+     * @return Way The newly created outer way
+     */
+    private ArrayList<Way> joinBoundaries(List<List<WayInPath>> outerWays) {
+        ArrayList<Way> result = new ArrayList<Way>();
+
+        for(List<WayInPath> ways: outerWays)
+        {
+            Way boundary = joinOuterWays(ways);
+            result.add(boundary);
+        }
+
+        return result;
+    }
+
+
+    /**
      * Joins the outer ways and deletes all short ways that can't be part of a multipolygon anyway.
      * @param Collection<Way> The list of outer ways that belong to that multigon.
      * @return Way The newly created outer way
      */
-    private Way joinOuterWays(ArrayList<WayInPath> outerWays) {
+    private Way joinOuterWays(List<WayInPath> outerWays) {
 
         //leave original orientation, if all paths are reverse.
         boolean allReverse = true;
-        for(WayInPath way: outerWays) {
-            allReverse &= way.insideToTheLeft;
+        for(WayInPath way: outerWays){
+            allReverse &= !way.insideToTheRight;
         }
 
-        if (allReverse) {
+        if (allReverse){
             for(WayInPath way: outerWays){
-                way.insideToTheLeft = !way.insideToTheLeft;
+                way.insideToTheRight = !way.insideToTheRight;
             }
         }
 
         commitCommands(marktr("Join Areas: Remove Short Ways"));
+
         Way joinedWay = joinOrientedWays(outerWays);
         if (joinedWay != null)
             return closeWay(joinedWay);
@@ -893,19 +1434,21 @@
      * @param ArrayList<Way> The list of ways to join and reverse
      * @return Way The newly created way
      */
-    private Way joinOrientedWays(ArrayList<WayInPath> ways) {
-        if(ways.size() < 2)
-            return ways.get(0).way;
+    private Way joinOrientedWays(List<WayInPath> ways) {
+        if(ways.size() < 2) return ways.get(0).way;
 
         // This will turn ways so all of them point in the same direction and CombineAction won't bug
         // the user about this.
 
+        //TODO: ReverseWay and Combine way are really slow and we use them a lot here. This slows down large joins.
+
         List<Way> actionWays = new ArrayList<Way>(ways.size());
 
         for(WayInPath way : ways) {
             actionWays.add(way.way);
 
-            if (way.insideToTheLeft) {
+            if (!way.insideToTheRight)
+            {
                 Main.main.getCurrentDataSet().setSelected(way.way);
                 new ReverseWayAction().actionPerformed(null);
                 cmdsCount++;
@@ -920,224 +1463,8 @@
         return result;
     }
 
-    /**
-     * Joins a list of ways (using CombineWayAction and ReverseWayAction if necessary to quiet the former)
-     * @param ArrayList<Way> The list of ways to join
-     * @return Way The newly created way
-     */
-    private Way joinWays(ArrayList<Way> ways) {
-        if(ways.size() < 2)
-            return ways.get(0);
 
-        // This will turn ways so all of them point in the same direction and CombineAction won't bug
-        // the user about this.
-        Way a = null;
-        for (Way b : ways) {
-            if(a == null) {
-                a = b;
-                continue;
-            }
-            if(a.getNode(0).equals(b.getNode(0)) ||
-                    a.getNode(a.getNodesCount()-1).equals(b.getNode(b.getNodesCount()-1))) {
-                Main.main.getCurrentDataSet().setSelected(b);
-                new ReverseWayAction().actionPerformed(null);
-                cmdsCount++;
-            }
-            a = b;
-        }
-        if ((a = new CombineWayAction().combineWays(ways)) != null) {
-            cmdsCount++;
-        }
-        return a;
-    }
-
     /**
-     * Finds all ways that may be part of a multipolygon relation and removes them from the given list.
-     * It will automatically combine "good" ways
-     * @param Collection<Way> The list of inner ways to check
-     * @param Way The newly created outer way
-     * @return ArrayList<Way> The List of newly created inner ways
-     */
-    private ArrayList<Way> fixMultipolygons(Collection<Way> uninterestingWays, Way outerWay, boolean selfintersect) {
-        Collection<Node> innerNodes = getNodesFromWays(uninterestingWays);
-        Collection<Node> outerNodes = outerWay.getNodes();
-
-        // The newly created inner ways. uninterestingWays is passed by reference and therefore modified in-place
-        ArrayList<Way> newInnerWays = new ArrayList<Way>();
-
-        // Now we need to find all inner ways that contain a remaining node, but no outer nodes
-        // Remaining nodes are those that contain to more than one way. All nodes that belong to an
-        // inner multigon part will have at least two ways, so we can use this to find which ways do
-        // belong to the multigon.
-        ArrayList<Way> possibleWays = new ArrayList<Way>();
-        wayIterator: for(Way w : uninterestingWays) {
-            boolean hasInnerNodes = false;
-            for(Node n : w.getNodes()) {
-                if(outerNodes.contains(n)) {
-                    if(!selfintersect) { // allow outer point for self intersection
-                        continue wayIterator;
-                    }
-                }
-                else if(!hasInnerNodes && innerNodes.contains(n)) {
-                    hasInnerNodes = true;
-                }
-            }
-            if(!hasInnerNodes || w.getNodesCount() < 2) {
-                continue;
-            }
-            possibleWays.add(w);
-        }
-
-        // This removes unnecessary ways that might have been added.
-        removeAlmostAlikeWays(possibleWays);
-
-        // loop twice
-        // in k == 0 prefer ways which allow no Y-joining (i.e. which have only 1 solution)
-        for(int k = 0; k < 2; ++k)
-        {
-            // Join all ways that have one start/ending node in common
-            Way joined = null;
-            outerIterator: do {
-                removePartlyUnconnectedWays(possibleWays);
-                joined = null;
-                for(Way w1 : possibleWays) {
-                    if(w1.isClosed()) {
-                        if(!wayIsCollapsed(w1)) {
-                            uninterestingWays.remove(w1);
-                            newInnerWays.add(w1);
-                        }
-                        joined = w1;
-                        possibleWays.remove(w1);
-                        continue outerIterator;
-                    }
-                    ArrayList<Way> secondary = new ArrayList<Way>();
-                    for(Way w2 : possibleWays) {
-                        int i = 0;
-                        // w2 cannot be closed, otherwise it would have been removed above
-                        if(w1.equals(w2)) {
-                            continue;
-                        }
-                        if(w2.isFirstLastNode(w1.firstNode())) {
-                            ++i;
-                        }
-                        if(w2.isFirstLastNode(w1.lastNode())) {
-                            ++i;
-                        }
-                        if(i == 2) // this way closes w1 - take it!
-                        {
-                            if(secondary.size() > 0) {
-                                secondary.clear();
-                            }
-                            secondary.add(w2);
-                            break;
-                        }
-                        else if(i > 0) {
-                            secondary.add(w2);
-                        }
-                    }
-                    if(k == 0 ? secondary.size() == 1 : secondary.size() > 0)
-                    {
-                        ArrayList<Way> joinThem = new ArrayList<Way>();
-                        joinThem.add(w1);
-                        joinThem.add(secondary.get(0));
-                        // Although we joined the ways, we cannot simply assume that they are closed
-                        if((joined = joinWays(joinThem)) != null)
-                        {
-                            uninterestingWays.removeAll(joinThem);
-                            possibleWays.removeAll(joinThem);
-
-                            //List<Node> nodes = joined.getNodes();
-                            // check if we added too much
-                            /*for(int i = 1; i < nodes.size()-2; ++i)
-                            {
-                                if(nodes.get(i) == nodes.get(nodes.size()-1))
-                                    System.out.println("Joining of ways produced unexpecteded result\n");
-                            }*/
-                            uninterestingWays.add(joined);
-                            possibleWays.add(joined);
-                            continue outerIterator;
-                        }
-                    }
-                }
-            } while(joined != null);
-        }
-        return newInnerWays;
-    }
-
-    /**
-     * Removes almost alike ways (= ways that are on top of each other for all nodes)
-     * @param ArrayList<Way> the ways to remove almost-duplicates from
-     */
-    private void removeAlmostAlikeWays(ArrayList<Way> ways) {
-        Collection<Way> removables = new ArrayList<Way>();
-        outer: for(int i=0; i < ways.size(); i++) {
-            Way a = ways.get(i);
-            for(int j=i+1; j < ways.size(); j++) {
-                Way b = ways.get(j);
-                List<Node> revNodes = new ArrayList<Node>(b.getNodes());
-                Collections.reverse(revNodes);
-                if(a.getNodes().equals(b.getNodes()) || a.getNodes().equals(revNodes)) {
-                    removables.add(a);
-                    continue outer;
-                }
-            }
-        }
-        ways.removeAll(removables);
-    }
-
-    /**
-     * Removes ways from the given list whose starting or ending node doesn't
-     * connect to other ways from the same list (it's like removing spikes).
-     * @param ArrayList<Way> The list of ways to remove "spikes" from
-     */
-    private void removePartlyUnconnectedWays(ArrayList<Way> ways) {
-        List<Way> removables = new ArrayList<Way>();
-        for(Way a : ways) {
-            if(a.isClosed()) {
-                continue;
-            }
-            boolean connectedStart = false;
-            boolean connectedEnd = false;
-            for(Way b : ways) {
-                if(a.equals(b)) {
-                    continue;
-                }
-                if(b.isFirstLastNode(a.firstNode())) {
-                    connectedStart = true;
-                }
-                if(b.isFirstLastNode(a.lastNode())) {
-                    connectedEnd = true;
-                }
-            }
-            if(!connectedStart || !connectedEnd) {
-                removables.add(a);
-            }
-        }
-        ways.removeAll(removables);
-    }
-
-    /**
-     * Checks if a way is collapsed (i.e. looks like <---->)
-     * @param Way A *closed* way to check if it is collapsed
-     * @return boolean If the closed way is collapsed or not
-     */
-    private boolean wayIsCollapsed(Way w) {
-        if(w.getNodesCount() <= 3) return true;
-
-        // If a way contains more than one node twice, it must be collapsed (only start/end node may be the same)
-        Way x = new Way(w);
-        int count = 0;
-        for(Node n : w.getNodes()) {
-            x.removeNode(n);
-            if(x.containsNode(n)) {
-                count++;
-            }
-            if(count == 2) return true;
-        }
-        return false;
-    }
-
-    /**
      * Will add own multipolygon relation to the "previously existing" relations. Fixup is done by fixRelations
      * @param Collection<Way> List of already closed inner ways
      * @param Way The outer way
@@ -1270,4 +1597,5 @@
     protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
         setEnabled(selection != null && !selection.isEmpty());
     }
+
 }
Index: test/unit/actions/JoinAreasActionTest.java
===================================================================
--- test/unit/actions/JoinAreasActionTest.java	(revision 0)
+++ test/unit/actions/JoinAreasActionTest.java	(revision 0)
@@ -0,0 +1,36 @@
+// License: GPL. For details, see LICENSE file.
+package actions;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openstreetmap.josm.actions.JoinAreasAction;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Node;
+
+
+public class JoinAreasActionTest {
+
+    private Node makeNode(double lat, double lon)
+    {
+        Node node = new Node(new LatLon(lat, lon));
+        return node;
+    }
+
+    @Test
+    public void testAngleIsClockwise()
+    {
+        Assert.assertTrue(JoinAreasAction.angleIsClockwise(makeNode(0,0), makeNode(1,1), makeNode(0,1)));
+        Assert.assertTrue(JoinAreasAction.angleIsClockwise(makeNode(1,1), makeNode(0,1), makeNode(0,0)));
+        Assert.assertTrue(!JoinAreasAction.angleIsClockwise(makeNode(1,1), makeNode(0,1), makeNode(1,0)));
+    }
+
+    @Test
+    public void testisToTheRightSideOfLine()
+    {
+        Assert.assertTrue(JoinAreasAction.isToTheRightSideOfLine(makeNode(0,0), makeNode(1,1), makeNode(0,1), makeNode(0, 0.5)));
+        Assert.assertTrue(!JoinAreasAction.isToTheRightSideOfLine(makeNode(0,0), makeNode(1,1), makeNode(0,1), makeNode(1, 0)));
+        Assert.assertTrue(!JoinAreasAction.isToTheRightSideOfLine(makeNode(1,1), makeNode(0,1), makeNode(1,0), makeNode(0,0)));
+        Assert.assertTrue(JoinAreasAction.isToTheRightSideOfLine(makeNode(1,1), makeNode(0,1), makeNode(1,0), makeNode(2, 0)));
+    }
+
+}
