Index: src/org/openstreetmap/josm/data/validation/OsmValidator.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 15008)
+++ src/org/openstreetmap/josm/data/validation/OsmValidator.java	(working copy)
@@ -55,6 +55,7 @@
 import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
 import org.openstreetmap.josm.data.validation.tests.NameMismatch;
 import org.openstreetmap.josm.data.validation.tests.OpeningHourTest;
+import org.openstreetmap.josm.data.validation.tests.OverlappingAreas;
 import org.openstreetmap.josm.data.validation.tests.OverlappingWays;
 import org.openstreetmap.josm.data.validation.tests.PowerLines;
 import org.openstreetmap.josm.data.validation.tests.PublicTransportRouteTest;
@@ -148,6 +149,7 @@
         LongSegment.class, // 3500 .. 3599
         PublicTransportRouteTest.class, // 3600 .. 3699
         RightAngleBuildingTest.class, // 3700 .. 3799
+        OverlappingAreas.class, // 3800 .. 3899
     };
 
     /**
Index: src/org/openstreetmap/josm/data/validation/tests/OverlappingAreas.java
===================================================================
--- src/org/openstreetmap/josm/data/validation/tests/OverlappingAreas.java	(nonexistent)
+++ src/org/openstreetmap/josm/data/validation/tests/OverlappingAreas.java	(working copy)
@@ -0,0 +1,280 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.validation.tests;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.geom.Area;
+import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openstreetmap.josm.data.coor.EastNorth;
+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.Way;
+import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
+import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
+import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
+import org.openstreetmap.josm.data.validation.Severity;
+import org.openstreetmap.josm.data.validation.Test;
+import org.openstreetmap.josm.data.validation.TestError;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor;
+import org.openstreetmap.josm.tools.Geometry;
+import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
+import org.openstreetmap.josm.tools.Pair;
+
+/**
+ * Tests if there are overlapping areas
+ *
+ * @author Gerd Petermann
+ * @since xxx
+ */
+public class OverlappingAreas extends Test {
+    protected static final int OVERLAPPING_AREA = 3800; // allows insideness
+    protected static final int OVERLAPPING_WATER = 3801;
+    protected static final int OVERLAPPING_IDENTICAL_NATURAL = 3802;
+    protected static final int OVERLAPPING_IDENTICAL_LANDLUSE = 3803;
+    protected static final int OVERLAPPING_BUILDINGS = 3804;
+    protected static final int OVERLAPPING_BUILDING_RESIDENTIAL= 3805;
+
+    private List<Way> areaWays;
+    private List<Relation> multipolygons;
+    private String reason;
+    private int code;
+
+    /** Constructor */
+    public OverlappingAreas() {
+        super(tr("Overlapping areas"),
+                tr("This test checks if two areas intersect."));
+    }
+
+    @Override
+    public void startTest(ProgressMonitor monitor) {
+        super.startTest(monitor);
+        areaWays = new ArrayList<>();
+        multipolygons = new ArrayList<>();
+    }
+
+    @Override
+    public void endTest() {
+        // way-way overlaps
+        for (int i = 0; i < areaWays.size(); i++) {
+            Way w1 = areaWays.get(i);
+            Area a1 = null;
+            for (int j = i + 1; j < areaWays.size(); j++) {
+                Way w2 = areaWays.get(j);
+                resetErrorDetails();
+                if (boundsIntersect(w1, w2) && !tagsAllowOverlap(w1, w2)) {
+                    if (a1 == null) {
+                        a1 = getEastNorthArea(w1);
+                    }
+                    checkIntersection(a1, getEastNorthArea(w2), w1, w2);
+                }
+            }
+        }
+        for (int i = 0; i < multipolygons.size(); i++) {
+            Relation r1 = multipolygons.get(i);
+            Area a1 = null;
+            // multipolygon -way overlaps
+            for (int j = 0; j < areaWays.size(); j++) {
+                Way way = areaWays.get(j);
+                resetErrorDetails();
+                if (boundsIntersect(r1, way) && !tagsAllowOverlap(r1, way)) {
+                    if (a1 == null) {
+                        a1 = getEastNorthArea(r1);
+                    }
+                    checkIntersection(a1, getEastNorthArea(way), r1, way);
+                }
+            }
+            for (int j = i+1; j < multipolygons.size(); j++) {
+                Relation r2 = multipolygons.get(j);
+                resetErrorDetails();
+                if (boundsIntersect(r1, r2) && !tagsAllowOverlap(r1, r2)) {
+                    if (a1 == null) {
+                        a1 = getEastNorthArea(r1);
+                    }
+                    checkIntersection(a1, getEastNorthArea(r2), r1, r2);
+                }
+            }
+
+        }
+        areaWays = null;
+        multipolygons = null;
+        super.endTest();
+    }
+
+    private static boolean boundsIntersect(OsmPrimitive p1, OsmPrimitive p2) {
+        return p1.getBBox().intersects(p2.getBBox());
+    }
+
+    private static Area getEastNorthArea(OsmPrimitive p) {
+        if (p instanceof Way) {
+            return Geometry.getArea(((Way) p).getNodes());
+        }
+        Multipolygon mp = MultipolygonCache.getInstance().get((Relation) p);
+        Path2D path = new Path2D.Double();
+        path.setWindingRule(Path2D.WIND_EVEN_ODD);
+        for (Multipolygon.PolyData pd : mp.getCombinedPolygons()) {
+            Geometry.buildPath2DEastNorth(pd.getNodes(), path);
+            for (Multipolygon.PolyData pdInner : pd.getInners()) {
+                Geometry.buildPath2DEastNorth(pdInner.getNodes(), path);
+            }
+        }
+        return new Area(path);
+
+    }
+
+    /**
+     * Check intersection between two areas. If empty or extremely small ignore it, else add error
+     * with highlighted area.
+     * @param a1 the first area (EastNorth)
+     * @param a2 the second area (EastNorth)
+     * @param p1 primitive describing 1st polygon
+     * @param p2 primitive describing 2nd polygon
+     */
+    private void checkIntersection(Area a1, Area a2, OsmPrimitive p1, OsmPrimitive p2) {
+        Pair<PolygonIntersection, Area> pair = Geometry.polygonIntersectionResult(a1, a2, Geometry.INTERSECTION_EPS_EAST_NORTH);
+        switch (pair.a) {
+        case OUTSIDE:
+            return;
+        case FIRST_INSIDE_SECOND:
+            if (code == OVERLAPPING_AREA || code == OVERLAPPING_BUILDING_RESIDENTIAL && isBuilding(p1))
+                return;
+            if (code == OVERLAPPING_WATER && p2.hasTag("natural","wetland"))
+                return;
+            break;
+        case SECOND_INSIDE_FIRST:
+            if (code == OVERLAPPING_AREA || code == OVERLAPPING_BUILDING_RESIDENTIAL && isBuilding(p2))
+                return;
+            if (code == OVERLAPPING_WATER && p1.hasTag("natural","wetland"))
+                return;
+            break;
+        case CROSSING:
+            break;
+        }
+
+        // we have an intersection, calculate the elements to highlight
+        PathIterator pit = pair.b.getPathIterator(null);
+        double[] res = new double[6];
+        List<List<Node>> hilite = new ArrayList<>();
+        List<Node> nodes = new ArrayList<>();
+        while (!pit.isDone()) {
+            int type = pit.currentSegment(res);
+            Node n = new Node(new EastNorth(res[0], res[1]));
+            switch (type) {
+            case PathIterator.SEG_MOVETO:
+                if (!nodes.isEmpty()) {
+                    hilite.add(nodes);
+                }
+                nodes = new ArrayList<>();
+                nodes.add(n);
+                break;
+            case PathIterator.SEG_LINETO:
+                nodes.add(n);
+                break;
+            case PathIterator.SEG_CLOSE:
+                if (!nodes.isEmpty()) {
+                    hilite.add(nodes);
+                    nodes = new ArrayList<>();
+                }
+                break;
+            default:
+                break;
+            }
+            pit.next();
+        }
+        if (nodes.size() > 1) {
+            hilite.add(nodes);
+        }
+
+        switch (code) {
+        case OVERLAPPING_WATER:
+            reason = tr("Overlapping Water Areas");
+            break;
+        case OVERLAPPING_IDENTICAL_NATURAL:
+            reason = tr("Overlapping identical natural areas");
+            break;
+        case OVERLAPPING_IDENTICAL_LANDLUSE:
+            reason = tr("Overlapping identical landuses");
+            break;
+        case OVERLAPPING_BUILDINGS:
+            reason = tr("Overlapping buildings");
+            break;
+        case OVERLAPPING_BUILDING_RESIDENTIAL:
+            reason = tr("Overlapping building/residential area");
+            break;
+        default:
+            reason = tr("Overlapping area");
+
+        }
+        errors.add(TestError
+                .builder(this, code == OVERLAPPING_AREA ? Severity.OTHER : Severity.WARNING, code)
+                .message("OA: " + reason).primitives(p1, p2)
+                .highlightNodePairs(hilite).build());
+    }
+
+    /**
+     * Check if the two objects are allowed to overlap regarding tags.
+     * @param p1 1st primitive
+     * @param p2 2nd primitive
+     * @return true if the tags of the objects allow that the areas overlap.
+     */
+    private boolean tagsAllowOverlap(OsmPrimitive p1, OsmPrimitive p2) {
+        if (isWaterArea(p1) && isWaterArea(p2)) {
+            code = OVERLAPPING_WATER;
+            return false;
+        }
+        if (isSetAndEqual(p1, p2, "natural")) {
+            code = OVERLAPPING_IDENTICAL_NATURAL;
+            return false;
+        }
+        if (isSetAndEqual(p1, p2, "landuse")) {
+            code = OVERLAPPING_IDENTICAL_LANDLUSE;
+            return false;
+        }
+        if (isBuilding(p1) && isBuilding(p2)) {
+            code = OVERLAPPING_BUILDINGS;
+            return false;
+        }
+        if (isBuilding(p1) && isResidentialArea(p2) || isBuilding(p2) && isResidentialArea(p1)) {
+            code = OVERLAPPING_BUILDING_RESIDENTIAL;
+            return false;
+        }
+        if (ValidatorPrefHelper.PREF_OTHER.get() && p1.concernsArea() && p2.concernsArea()) {
+            code = OVERLAPPING_AREA;
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean isWaterArea(OsmPrimitive p) {
+        return p.hasTag("natural", "water", "wetland", "coastline") || p.hasTag("landuse","reservoir");
+    }
+
+    private static boolean isSetAndEqual(OsmPrimitive p1, OsmPrimitive p2, String key) {
+        String v1 = p1.get(key);
+        String v2 = p2.get(key);
+        return v1 != null && v1.equals(v2);
+    }
+
+    private void resetErrorDetails() {
+        reason = null;
+        code = -1;
+    }
+
+    @Override
+    public void visit(Way w) {
+        if (w.isArea()) {
+            areaWays.add(w);
+        }
+    }
+
+    @Override
+    public void visit(Relation r) {
+        if (r.isMultipolygon()) {
+            multipolygons.add(r);
+        }
+    }
+}
Index: src/org/openstreetmap/josm/tools/Geometry.java
===================================================================
--- src/org/openstreetmap/josm/tools/Geometry.java	(revision 15008)
+++ src/org/openstreetmap/josm/tools/Geometry.java	(working copy)
@@ -555,6 +555,31 @@
     }
 
     /**
+     * Builds a path from a list of nodes
+     * @param polygon Nodes, forming a closed polygon
+     * @param path2d path to add to; can be null, then a new path is created
+     * @return the path (EastNorth coordinates)
+     * @since xxx
+     */
+    public static Path2D buildPath2DEastNorth(List<Node> polygon, Path2D path2d) {
+        Path2D path = path2d != null ? path2d : new Path2D.Double();
+        boolean begin = true;
+        for (INode n : polygon) {
+            EastNorth en = n.getEastNorth();
+            if (begin) {
+                path.moveTo(en.getX(), en.getY());
+                begin = false;
+            } else {
+                path.lineTo(en.getX(), en.getY());
+            }
+        }
+        if (!begin) {
+            path.closePath();
+        }
+        return path;
+    }
+
+    /**
      * Returns the Area of a polygon, from the multipolygon relation.
      * @param multipolygon the multipolygon relation
      * @return Area for the multipolygon (LatLon coordinates)
@@ -603,18 +628,29 @@
      * @return intersection kind
      */
     public static PolygonIntersection polygonIntersection(Area a1, Area a2, double eps) {
+        return polygonIntersectionResult(a1, a2, eps).a;
+    }
 
+    /**
+     * Calculate intersection area and kind of intersection between two polygons.
+     * @param a1 Area of first polygon
+     * @param a2 Area of second polygon
+     * @param eps an area threshold, everything below is considered an empty intersection
+     * @return pair with intersection kind and intersection area (never null, but maybe empty)
+     * @since xxx
+     */
+    public static Pair<PolygonIntersection, Area> polygonIntersectionResult(Area a1, Area a2, double eps) {
         Area inter = new Area(a1);
         inter.intersect(a2);
 
         if (inter.isEmpty() || !checkIntersection(inter, eps)) {
-            return PolygonIntersection.OUTSIDE;
+            return new Pair<>(PolygonIntersection.OUTSIDE, inter);
         } else if (a2.getBounds2D().contains(a1.getBounds2D()) && inter.equals(a1)) {
-            return PolygonIntersection.FIRST_INSIDE_SECOND;
+            return new Pair<>(PolygonIntersection.FIRST_INSIDE_SECOND, inter);
         } else if (a1.getBounds2D().contains(a2.getBounds2D()) && inter.equals(a2)) {
-            return PolygonIntersection.SECOND_INSIDE_FIRST;
+            return new Pair<>(PolygonIntersection.SECOND_INSIDE_FIRST, inter);
         } else {
-            return PolygonIntersection.CROSSING;
+            return new Pair<>(PolygonIntersection.CROSSING, inter);
         }
     }
 
