Index: openstreetmap/josm/command/RemoveRepeatedRelationMembersCommand.java
===================================================================
--- openstreetmap/josm/command/RemoveRepeatedRelationMembersCommand.java	(nonexistent)
+++ openstreetmap/josm/command/RemoveRepeatedRelationMembersCommand.java	(working copy)
@@ -0,0 +1,113 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.command;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+
+import javax.swing.Icon;
+
+import org.openstreetmap.josm.Main;
+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.gui.DefaultNameFormatter;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Command that removes repeated relation members (first occurrence is kept)
+ *
+ * @author Gerd Petermann
+ */
+public class RemoveRepeatedRelationMembersCommand extends Command {
+
+    // The relation to be changed
+    private final Relation relation;
+    // Old value of modified
+    private Boolean oldModified;
+    private final List<OsmPrimitive> repeatedPrims;
+    private List<RelationMember> oldMembers;
+
+
+    /**
+     * @param r the relation
+     * @param membersToRemove list of members to remove
+     */
+    public RemoveRepeatedRelationMembersCommand(Relation r, List<OsmPrimitive> membersToRemove) {
+        this.relation = r;
+        this.repeatedPrims = membersToRemove;
+    }
+
+    @Override
+    public boolean executeCommand() {
+        boolean executed = false;
+        long t1 = System.currentTimeMillis();
+        oldMembers = relation.getMembers();
+
+        List<RelationMember> newMembers = new ArrayList<>();
+        HashSet<OsmPrimitive> toRemove = new HashSet<>(repeatedPrims);
+        HashSet<OsmPrimitive> found = new HashSet<>(repeatedPrims.size());
+        for (RelationMember rm : oldMembers) {
+            if (toRemove.contains(rm.getMember())) {
+                if (found.contains(rm.getMember()) == false) {
+                    found.add(rm.getMember());
+                    newMembers.add(rm);
+                }
+            } else
+                newMembers.add(rm);
+        }
+        oldModified = relation.isModified();
+        relation.setMembers(newMembers);
+        long t2 = System.currentTimeMillis();
+        Main.debug("remove repeated relation members action took " + (t2 - t1) + " ms, removed"
+                + (oldMembers.size() - newMembers.size()) + " members");
+        return executed;
+    }
+
+    @Override
+    public void undoCommand() {
+        if (oldMembers != null) {
+            relation.setMembers(oldMembers);
+            if (oldModified != null) {
+                relation.setModified(oldModified);
+            }
+        }
+    }
+
+    @Override
+    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
+        modified.add(relation);
+    }
+
+    @Override
+    public String getDescriptionText() {
+        return tr("Remove relation member(s) from {0}",
+                relation.getDisplayName(DefaultNameFormatter.getInstance()));
+    }
+
+    @Override
+    public Icon getDescriptionIcon() {
+        return ImageProvider.get(relation.getDisplayType());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), relation, repeatedPrims.hashCode());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+        if (!super.equals(obj)) return false;
+        RemoveRepeatedRelationMembersCommand that = (RemoveRepeatedRelationMembersCommand) obj;
+        return
+                Objects.equals(relation, that.relation) &&
+                repeatedPrims.size() == that.repeatedPrims.size() &&
+                repeatedPrims.containsAll(that.repeatedPrims);
+    }
+}
Index: openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java
===================================================================
--- openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java	(revision 10962)
+++ openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java	(working copy)
@@ -9,6 +9,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -175,7 +176,7 @@
          */
         public JoinedWay(List<Node> nodes, Collection<Long> wayIds, boolean selected) {
             this.nodes = new ArrayList<>(nodes);
-            this.wayIds = new ArrayList<>(wayIds);
+            this.wayIds = new LinkedHashSet<>(wayIds);
             this.selected = selected;
         }
 
Index: openstreetmap/josm/data/osm/WaySegment.java
===================================================================
--- openstreetmap/josm/data/osm/WaySegment.java	(revision 10962)
+++ openstreetmap/josm/data/osm/WaySegment.java	(working copy)
@@ -114,6 +114,20 @@
                 s2.getSecondNode().getEastNorth().east(), s2.getSecondNode().getEastNorth().north());
     }
 
+    /**
+     * Checks whether this segment and another way segment share the same points
+     * @param s2 The other segment
+     * @return true if other way segment is the same or reverse
+     * @since r10819
+     */
+    public boolean isSimilar(WaySegment s2) {
+        if (getFirstNode().equals(s2.getFirstNode()) && getSecondNode().equals(s2.getSecondNode()))
+            return true;
+        if (getFirstNode().equals(s2.getSecondNode()) && getSecondNode().equals(s2.getFirstNode()))
+            return true;
+        return false;
+    }
+
     @Override
     public String toString() {
         return "WaySegment [way=" + way.getUniqueId() + ", lowerIndex=" + lowerIndex + ']';
Index: openstreetmap/josm/data/validation/tests/MultipolygonTest.java
===================================================================
--- openstreetmap/josm/data/validation/tests/MultipolygonTest.java	(revision 10962)
+++ openstreetmap/josm/data/validation/tests/MultipolygonTest.java	(working copy)
@@ -4,27 +4,35 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 import static org.openstreetmap.josm.tools.I18n.trn;
 
-import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.CreateMultipolygonAction;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.RemoveRepeatedRelationMembersCommand;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.coor.LatLon;
 import org.openstreetmap.josm.data.osm.Node;
 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.Way;
+import org.openstreetmap.josm.data.osm.WaySegment;
 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
-import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData.Intersection;
 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
 import org.openstreetmap.josm.data.validation.OsmValidator;
 import org.openstreetmap.josm.data.validation.Severity;
@@ -67,10 +75,18 @@
     public static final int NO_STYLE_POLYGON = 1611;
     /** Area style on outer way */
     public static final int OUTER_STYLE = 1613;
+    /** Multipolygon member repeated (same primitive, same role */
+    public static final int REPEATED_MEMBER_SAME_ROLE = 1614;
+    /** Multipolygon member repeated (same primitive, different role) */
+    public static final int REPEATED_MEMBER_DIFF_ROLE = 1615;
 
     private static volatile ElemStyles styles;
 
     private final Set<String> keysCheckedByAnotherTest = new HashSet<>();
+    /** All way segments, grouped by cells */
+    private final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000);
+    /** The already detected ways in error */
+    private final Map<List<Way>, List<WaySegment>> crossingWays = new HashMap<>(50);
 
     /**
      * Constructs a new {@code MultipolygonTest}.
@@ -103,18 +119,18 @@
         super.endTest();
     }
 
-    private static GeneralPath createPath(List<Node> nodes) {
-        GeneralPath result = new GeneralPath();
-        result.moveTo((float) nodes.get(0).getCoor().lat(), (float) nodes.get(0).getCoor().lon());
+    private static Path2D.Double createPath(List<Node> nodes) {
+        Path2D.Double result = new Path2D.Double();
+        result.moveTo(nodes.get(0).getCoor().lon(), nodes.get(0).getCoor().lat());
         for (int i = 1; i < nodes.size(); i++) {
             Node n = nodes.get(i);
-            result.lineTo((float) n.getCoor().lat(), (float) n.getCoor().lon());
+            result.lineTo(n.getCoor().lon(), n.getCoor().lat());
         }
         return result;
     }
 
-    private static List<GeneralPath> createPolygons(List<Multipolygon.PolyData> joinedWays) {
-        List<GeneralPath> result = new ArrayList<>();
+    private static List<Path2D.Double> createPolygons(List<Multipolygon.PolyData> joinedWays) {
+        List<Path2D.Double> result = new ArrayList<>();
         for (Multipolygon.PolyData way : joinedWays) {
             result.add(createPath(way.getNodes()));
         }
@@ -121,22 +137,6 @@
         return result;
     }
 
-    private static Intersection getPolygonIntersection(GeneralPath outer, List<Node> inner) {
-        boolean inside = false;
-        boolean outside = false;
-
-        for (Node n : inner) {
-            boolean contains = outer.contains(n.getCoor().lat(), n.getCoor().lon());
-            inside = inside | contains;
-            outside = outside | !contains;
-            if (inside & outside) {
-                return Intersection.CROSSING;
-            }
-        }
-
-        return inside ? Intersection.INSIDE : Intersection.OUTSIDE;
-    }
-
     @Override
     public void visit(Way w) {
         if (!w.isArea() && ElemStyles.hasOnlyAreaElemStyle(w)) {
@@ -155,8 +155,13 @@
     @Override
     public void visit(Relation r) {
         if (r.isMultipolygon()) {
+            crossingWays.clear();
+            cellSegments.clear();
+
             checkMembersAndRoles(r);
+            checkRepeatedMembers(r);
             checkOuterWay(r);
+            checkIntersectionAtNodes(r);
 
             // Rest of checks is only for complete multipolygons
             if (!r.hasIncompleteMembers()) {
@@ -163,9 +168,9 @@
                 Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, r);
 
                 // Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match.
-                checkMemberRoleCorrectness(r);
+                boolean rolesWereChecked = checkMemberRoleCorrectness(r);
                 checkStyleConsistency(r, polygon);
-                checkGeometry(r, polygon);
+                checkGeometry(r, polygon, rolesWereChecked);
             }
         }
     }
@@ -194,8 +199,9 @@
      * <li>{@link #WRONG_MEMBER_ROLE}: Role for ''{0}'' should be ''{1}''</li>
      * </ul>
      * @param r relation
+     * @return true if member roles were checked
      */
-    private void checkMemberRoleCorrectness(Relation r) {
+    private boolean checkMemberRoleCorrectness(Relation r) {
         final Pair<Relation, Relation> newMP = CreateMultipolygonAction.createMultipolygonRelation(r.getMemberPrimitives(Way.class), false);
         if (newMP != null) {
             for (RelationMember member : r.getMembers()) {
@@ -216,6 +222,7 @@
                 }
             }
         }
+        return newMP != null;
     }
 
     /**
@@ -285,6 +292,16 @@
         }
     }
 
+    private static class LatLonPolyData {
+        final PolyData pd;
+        final Path2D.Double latLonPath;
+
+        LatLonPolyData(PolyData polyData, Path2D.Double path) {
+            this.pd = polyData;
+            this.latLonPath = path;
+        }
+    }
+
     /**
      * Various geometry-related checks:<ul>
      * <li>{@link #NON_CLOSED_WAY}: Multipolygon is not closed</li>
@@ -293,58 +310,101 @@
      * </ul>
      * @param r relation
      * @param polygon multipolygon
+     * @param rolesWereChecked might be used to skip most of the tests below
      */
-    private void checkGeometry(Relation r, Multipolygon polygon) {
+    private void checkGeometry(Relation r, Multipolygon polygon, boolean rolesWereChecked) {
         List<Node> openNodes = polygon.getOpenEnds();
         if (!openNodes.isEmpty()) {
             List<OsmPrimitive> primitives = new LinkedList<>();
             primitives.add(r);
             primitives.addAll(openNodes);
-            addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY, primitives, openNodes));
+            addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY,
+                    primitives, openNodes));
         }
+        List<PolyData> innerPolygons = polygon.getInnerPolygons();
+        List<PolyData> outerPolygons = polygon.getOuterPolygons();
 
+        HashMap<PolyData, List<PolyData>> crossingPolyMap = checkCrossingWays(r, innerPolygons, outerPolygons);
+
+        //Polygons may intersect without crossing ways when one polygon lies completely inside the other
+        List<LatLonPolyData> inner = calcLatLonPolygons(innerPolygons);
+        List<LatLonPolyData> outer = calcLatLonPolygons(outerPolygons);
         // For painting is used Polygon class which works with ints only. For validation we need more precision
-        List<PolyData> innerPolygons = polygon.getInnerPolygons();
-        List<PolyData> outerPolygons = polygon.getOuterPolygons();
-        List<GeneralPath> innerPolygonsPaths = innerPolygons.isEmpty() ? Collections.<GeneralPath>emptyList() : createPolygons(innerPolygons);
-        List<GeneralPath> outerPolygonsPaths = createPolygons(outerPolygons);
-        for (int i = 0; i < outerPolygons.size(); i++) {
-            PolyData pdOuter = outerPolygons.get(i);
-            // Check for intersection between outer members
-            for (int j = i+1; j < outerPolygons.size(); j++) {
-                checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdOuter, j);
+        for (int i = 0; i + 1 < outer.size(); i++) {
+            // report outer polygons which lie inside another outer
+            LatLonPolyData outer1 = outer.get(i);
+            for (int j = 0; j < outer.size(); j++) {
+                if (i != j && !crossing(crossingPolyMap, outer1.pd, outer.get(j).pd)) {
+                    LatLon c = outer.get(j).pd.getNodes().get(0).getCoor();
+                    if (outer1.latLonPath.contains(c.lon(), c.lat())) {
+                        addError(r,
+                                new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"),
+                                        CROSSING_WAYS, Collections.singletonList(r),
+                                        Arrays.asList(outer1.pd.getNodes(), outer.get(j).pd.getNodes())));
+                    }
+                }
             }
         }
-        for (int i = 0; i < innerPolygons.size(); i++) {
-            PolyData pdInner = innerPolygons.get(i);
-            // Check for intersection between inner members
-            for (int j = i+1; j < innerPolygons.size(); j++) {
-                checkCrossingWays(r, innerPolygons, innerPolygonsPaths, pdInner, j);
+        for (int i = 0; i < inner.size(); i++) {
+            LatLonPolyData inner1 = inner.get(i);
+            LatLon innerPoint = inner1.pd.getNodes().get(0).getCoor();
+            for (int j = 0; j < inner.size(); j++) {
+                LatLonPolyData inner2 = inner.get(j);
+                if (i != j && !crossing(crossingPolyMap, inner1.pd, inner2.pd)) {
+                    if (inner.get(j).latLonPath.contains(innerPoint.lon(), innerPoint.lat())) {
+                        addError(r,
+                                new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"),
+                                        CROSSING_WAYS, Collections.singletonList(r),
+                                        Arrays.asList(inner1.pd.getNodes(), inner2.pd.getNodes())));
+                    }
+                }
             }
-            // Check for intersection between inner and outer members
+
+            // Find inner polygons which are not inside any outer
             boolean outside = true;
-            for (int o = 0; o < outerPolygons.size(); o++) {
-                outside &= checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdInner, o) == Intersection.OUTSIDE;
+            boolean crossingWithOuter = false;
+
+            for (int o = 0; o < outer.size(); o++) {
+                if (crossing(crossingPolyMap, inner1.pd, outer.get(o).pd)) {
+                    crossingWithOuter = true;
+                    break;
+                }
+                outside &= outer.get(o).latLonPath.contains(innerPoint.lon(), innerPoint.lat()) == false;
+                if (!outside)
+                    break;
             }
-            if (outside) {
+            if (outside && !crossingWithOuter) {
                 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"),
-                        INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList(pdInner.getNodes())));
+                        INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList(inner1.pd.getNodes())));
             }
         }
     }
 
-    private Intersection checkCrossingWays(Relation r, List<PolyData> polygons, List<GeneralPath> polygonsPaths, PolyData pd, int idx) {
-        Intersection intersection = getPolygonIntersection(polygonsPaths.get(idx), pd.getNodes());
-        if (intersection == Intersection.CROSSING) {
-            PolyData pdOther = polygons.get(idx);
-            if (pdOther != null) {
-                addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"),
-                        CROSSING_WAYS, Collections.singletonList(r), Arrays.asList(pd.getNodes(), pdOther.getNodes())));
-            }
+    private boolean crossing(HashMap<PolyData, List<PolyData>> crossingPolyMap, PolyData pd1, PolyData pd2) {
+        List<PolyData> crossingWithFirst = crossingPolyMap.get(pd1);
+        if (crossingWithFirst != null) {
+            if (crossingWithFirst.contains(pd2))
+                return true;
         }
-        return intersection;
+        List<PolyData> crossingWith2nd = crossingPolyMap.get(pd2);
+        if (crossingWith2nd != null) {
+            if (crossingWith2nd.contains(pd1))
+                return true;
+        }
+        return false;
     }
 
+    private List<LatLonPolyData> calcLatLonPolygons(List<PolyData> polygons) {
+        if (polygons == null || polygons.isEmpty())
+            return Collections.emptyList();
+        List<LatLonPolyData> latLonPolygons = new ArrayList<>();
+        List<Path2D.Double> polygonsPaths = createPolygons(polygons);
+        for (int i = 0; i < polygons.size(); i++) {
+            latLonPolygons.add(new LatLonPolyData(polygons.get(i), polygonsPaths.get(i)));
+        }
+        return latLonPolygons;
+    }
+
     /**
      * Check for:<ul>
      * <li>{@link #WRONG_MEMBER_ROLE}: No useful role for multipolygon member</li>
@@ -389,4 +449,227 @@
         addRelationIfNeeded(error, r);
         errors.add(error);
     }
+
+    /**
+     * Determine multipolygon ways which are intersecting. This is now allowed.
+     * See {@link CrossingWays}
+     * @param r the relation (for error reporting)
+     * @param innerPolygons list of inner polygons
+     * @param outerPolygons list of outer polygons
+     * @return map of crossing polygons (including polygons touching outer)
+     */
+    private HashMap<PolyData, List<PolyData>> checkCrossingWays(Relation r, List<PolyData> innerPolygons,
+            List<PolyData> outerPolygons) {
+        List<Way> innerWays = new ArrayList<>();
+        List<Way> outerWays = new ArrayList<>();
+        for (Way w : r.getMemberPrimitives(Way.class)) {
+            for (PolyData pd : innerPolygons) {
+                if (pd.getWayIds().contains(w.getUniqueId())) {
+                    innerWays.add(w);
+                    break;
+                }
+            }
+            for (PolyData pd : outerPolygons) {
+                if (pd.getWayIds().contains(w.getUniqueId())) {
+                    outerWays.add(w);
+                    break;
+                }
+            }
+        }
+        for (Way w : innerWays) {
+            checkCrossingWay(w, r, true /* allow shared ways */);
+        }
+        for (Way w : outerWays) {
+            checkCrossingWay(w, r, false/* don't allow shared ways */);
+        }
+        HashMap<PolyData, List<PolyData>> crossingPolygonsMap = new HashMap<>();
+        for (Entry<List<Way>, List<WaySegment>> entry : crossingWays.entrySet()) {
+            List<Way> ways = entry.getKey();
+            PolyData[] crossingPolys = new PolyData[2];
+            for (Way w : ways) {
+                for (int j = 0; j < crossingPolys.length; j++) {
+                    for (PolyData pd : innerPolygons) {
+                        if (pd.getWayIds().contains(w.getUniqueId())) {
+                            crossingPolys[j] = pd;
+                            break;
+                        }
+                    }
+                    if (crossingPolys[j] != null)
+                        break;
+                    for (PolyData pd : outerPolygons) {
+                        if (pd.getWayIds().contains(w.getUniqueId())) {
+                            crossingPolys[j] = pd;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (crossingPolys[0] != null && crossingPolys[1] != null) {
+                List<PolyData> x = crossingPolygonsMap.get(crossingPolys[0]);
+                if (x == null) {
+                    x = new ArrayList<>();
+                    crossingPolygonsMap.put(crossingPolys[0], x);
+                }
+                x.add(crossingPolys[1]);
+            }
+        }
+        return crossingPolygonsMap;
+    }
+
+    /**
+     *
+     * @param w way that is member of the relation
+     * @param r the relation (used for error messages)
+     * @param allowSharedWaySegment false: treat similar way segment as crossing
+     */
+    private void checkCrossingWay(Way w, Relation r, boolean allowSharedWaySegment) {
+
+        int nodesSize = w.getNodesCount();
+        for (int i = 0; i < nodesSize - 1; i++) {
+            final WaySegment es1 = new WaySegment(w, i);
+            final EastNorth en1 = es1.getFirstNode().getEastNorth();
+            final EastNorth en2 = es1.getSecondNode().getEastNorth();
+            if (en1 == null || en2 == null) {
+                Main.warn("Crossing ways test skipped " + es1);
+                continue;
+            }
+            for (List<WaySegment> segments : CrossingWays.getSegments(cellSegments, en1, en2)) {
+                for (WaySegment es2 : segments) {
+
+                    List<WaySegment> highlight;
+                    if (es2.way == w)
+                        continue; // reported by CrossingWays.SelfIntersection
+                    if (!es1.intersects(es2)) {
+                        if (allowSharedWaySegment || !es1.isSimilar(es2))
+                            continue;
+                    }
+
+                    List<Way> prims = Arrays.asList(es1.way, es2.way);
+                    if ((highlight = crossingWays.get(prims)) == null) {
+                        highlight = new ArrayList<>();
+                        highlight.add(es1);
+                        highlight.add(es2);
+
+                        addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"),
+                                CROSSING_WAYS, Arrays.asList(r, es1.way, es2.way), highlight));
+                        crossingWays.put(prims, highlight);
+                    } else {
+                        highlight.add(es1);
+                        highlight.add(es2);
+                    }
+                }
+                segments.add(es1);
+            }
+        }
+    }
+
+    /**
+     * Detect intersections of multipolygon ways at nodes. If any way node is used by more than two ways
+     * or two times in one way and at least once in another way we found an intersection.
+     * @param r the relation
+     */
+    private void checkIntersectionAtNodes(Relation r) {
+        Map<Node, List<Way>> nodeMap = new HashMap<>();
+        for (RelationMember rm : r.getMembers()) {
+            if (!rm.isWay())
+                continue;
+            int numNodes = rm.getWay().getNodesCount();
+            for (int i = 0; i < numNodes; i++) {
+                Node n = rm.getWay().getNode(i);
+                if (n.getReferrers().size() <= 1) {
+                    continue; // cannot be a problem node
+                }
+                List<Way> ways = nodeMap.get(n);
+                if (ways == null) {
+                    ways = new ArrayList<>();
+                    nodeMap.put(n, ways);
+                }
+                ways.add(rm.getWay());
+                if (ways.size() > 2 || (ways.size() == 2 && i != 0 && i + 1 != numNodes)) {
+                    errors.add(new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"),
+                            CROSSING_WAYS, new HashSet<>(ways), Arrays.asList(n)));
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Check for:<ul>
+     * <li>{@link #REPEATED_MEMBER_DIFF_ROLE}: Multipolygon member(s) repeated with different role</li>
+     * <li>{@link #REPEATED_MEMBER_SAME_ROLE}: Multipolygon member(s) repeated with same role</li>
+     * </ul>
+     * @param r relation
+     */
+    private void checkRepeatedMembers(Relation r) {
+        boolean hasDups = false;
+        Map<OsmPrimitive, List<RelationMember>> seenMemberPrimitives = new HashMap<>();
+        for (RelationMember rm : r.getMembers()) {
+            List<RelationMember> list = seenMemberPrimitives.get(rm.getMember());
+            if (list == null) {
+                list = new ArrayList<>(2);
+                seenMemberPrimitives.put(rm.getMember(), list);
+            } else
+                hasDups = true;
+            list.add(rm);
+        }
+        if (!hasDups)
+            return;
+
+        List<OsmPrimitive> repeatedSameRole = new ArrayList<>();
+        List<OsmPrimitive> repeatedDiffRole = new ArrayList<>();
+        for (Entry<OsmPrimitive, List<RelationMember>> e : seenMemberPrimitives.entrySet()) {
+            List<RelationMember> visited = e.getValue();
+            if (e.getValue().size() == 1)
+                continue;
+            // we found a duplicate member, check if the roles differ
+            boolean rolesDiffer = false;
+            RelationMember rm = visited.get(0);
+            List<OsmPrimitive> primitives = new ArrayList<>();
+            for (int i = 1; i < visited.size(); i++) {
+                RelationMember v = visited.get(i);
+                primitives.add(rm.getMember());
+                if (v.getRole().equals(rm.getRole()) == false) {
+                    rolesDiffer = true;
+                }
+            }
+            if (rolesDiffer)
+                repeatedDiffRole.addAll(primitives);
+            else
+                repeatedSameRole.addAll(primitives);
+        }
+        addRepeatedMemberError(r, repeatedDiffRole, REPEATED_MEMBER_DIFF_ROLE, tr("Multipolygon member(s) repeated with different role"));
+        addRepeatedMemberError(r, repeatedSameRole, REPEATED_MEMBER_SAME_ROLE, tr("Multipolygon member(s) repeated with same role"));
+    }
+
+    private void addRepeatedMemberError(Relation r, List<OsmPrimitive> repeatedMembers, int errorCode, String msg) {
+        if (!repeatedMembers.isEmpty()) {
+            List<OsmPrimitive> prims = new ArrayList<>(1 + repeatedMembers.size());
+            prims.add(r);
+            prims.addAll(repeatedMembers);
+            addError(r, new TestError(this, Severity.WARNING, msg, errorCode, prims, repeatedMembers));
+        }
+
+    }
+
+    @Override
+    public Command fixError(TestError testError) {
+        if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE) {
+            ArrayList<OsmPrimitive> primitives = new ArrayList<>(testError.getPrimitives());
+            if (primitives.size() >= 2) {
+                if (primitives.get(0) instanceof Relation) {
+                    Relation r = (Relation) primitives.get(0);
+                    return new RemoveRepeatedRelationMembersCommand(r, primitives.subList(1, primitives.size()));
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean isFixable(TestError testError) {
+        if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE)
+            return true;
+        return false;
+    }
 }
