Index: src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java	(revision 10877)
+++ src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java	(working copy)
@@ -11,6 +11,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -40,6 +41,86 @@
             Utils.newForkJoinPool("multipolygon_creation.numberOfThreads", "multipolygon-builder-%d", Thread.NORM_PRIORITY);
 
     /**
+     * Helper class to avoid unneeded costly intersection calculations.
+     * If the intersection between polygons a and b was calculated we also know
+     * the result of intersection between b and a. The lookup in the hash tables is
+     * much faster than the intersection calculation.
+     */
+    private static class IntersectionMatrix {
+        private final Map<JoinedPolygon, Map<JoinedPolygon, PolygonIntersection>> results;
+        private long countTest;
+        private long countMiss;
+
+        IntersectionMatrix(Collection<JoinedPolygon> polygons) {
+            results = new HashMap<>(polygons.size());
+        }
+
+        /**
+         * Compute the reverse result of the intersection test done by {@code
+        Geometry.polygonIntersection(Area a1, Area a2)}
+         * @param intersection the intersection result for polygons a1 and a2 (in that order)
+         * @return the intersection result for a2 and a1
+         */
+        private PolygonIntersection getReverseIntersectionResult(PolygonIntersection intersection) {
+            if (intersection == PolygonIntersection.FIRST_INSIDE_SECOND)
+                return PolygonIntersection.SECOND_INSIDE_FIRST;
+            else if (intersection == PolygonIntersection.SECOND_INSIDE_FIRST)
+                return PolygonIntersection.FIRST_INSIDE_SECOND;
+            return intersection;
+        }
+
+        /**
+         * Store the result of the intersection test done by {@code
+        Geometry.polygonIntersection(Area a1, Area a2)}
+         * @param a1 first polygon
+         * @param a2 second polygon
+         * @param intersection result of {@code Geometry.polygonIntersection(a1,a2)}
+         */
+        private synchronized void updateMap(JoinedPolygon a1, JoinedPolygon a2, PolygonIntersection intersection) {
+            Map<JoinedPolygon, PolygonIntersection> subMap = results.get(a1);
+            if (subMap == null) {
+                subMap = new HashMap<>();
+                results.put(a1, subMap);
+            }
+            subMap.put(a2, intersection);
+        }
+
+        /**
+         * Store the result of intersection between two polygons
+         * @param a1 first polygon
+         * @param a2 second polygon
+         * @param intersection result of {@code Geometry.polygonIntersection(a1,a2)}
+         */
+        public void storeResult(JoinedPolygon a1, JoinedPolygon a2, PolygonIntersection intersection) {
+            updateMap(a1, a2, intersection);
+            updateMap(a2, a1, getReverseIntersectionResult(intersection));
+        }
+
+        /**
+         * Check if the intersection result is available.
+         * @param a1 first polygon
+         * @param a2 second polygon
+         * @return the result {@code Geometry.polygonIntersection(a1,a2)} or null if this was not yet stored
+         */
+        public synchronized PolygonIntersection checkIfKnownIntersection(JoinedPolygon a1, JoinedPolygon a2) {
+            countTest++;
+            Map<JoinedPolygon, PolygonIntersection> subMap = results.get(a1);
+            PolygonIntersection res = null;
+            if (subMap != null)
+                res = subMap.get(a2);
+            if (res == null)
+                countMiss++;
+            return res;
+        }
+
+        @Override
+        public String toString() {
+            return "IntersectionMatrix [countTest=" + countTest + ", countMiss=" + countMiss + "]";
+        }
+
+    }
+
+    /**
      * Represents one polygon that consists of multiple ways.
      */
     public static class JoinedPolygon {
@@ -291,7 +372,8 @@
         return null;
     }
 
-    private static Pair<Boolean, List<JoinedPolygon>> findInnerWaysCandidates(JoinedPolygon outerWay, Collection<JoinedPolygon> boundaryWays) {
+    private static Pair<Boolean, List<JoinedPolygon>> findInnerWaysCandidates(IntersectionMatrix cache,
+            JoinedPolygon outerWay, Collection<JoinedPolygon> boundaryWays) {
         boolean outerGood = true;
         List<JoinedPolygon> innerCandidates = new ArrayList<>();
 
@@ -303,7 +385,11 @@
             // Preliminary computation on bounds. If bounds do not intersect, no need to do a costly area intersection
             if (outerWay.bounds.intersects(innerWay.bounds)) {
                 // Bounds intersection, let's see in detail
-                PolygonIntersection intersection = Geometry.polygonIntersection(outerWay.area, innerWay.area);
+                PolygonIntersection intersection = cache.checkIfKnownIntersection(outerWay, innerWay);
+                if (intersection == null) {
+                    intersection = Geometry.polygonIntersection(outerWay.area, innerWay.area);
+                    cache.storeResult(outerWay, innerWay, intersection);
+                }
 
                 if (intersection == PolygonIntersection.FIRST_INSIDE_SECOND) {
                     outerGood = false;  // outer is inside another polygon
@@ -326,8 +412,12 @@
      * @return the outermostWay, or {@code null} if intersection found.
      */
     private static List<PolygonLevel> findOuterWaysMultiThread(List<JoinedPolygon> boundaryWays) {
-        return THREAD_POOL.invoke(new Worker(boundaryWays, 0, boundaryWays.size(), new ArrayList<PolygonLevel>(),
+        IntersectionMatrix im = new IntersectionMatrix(boundaryWays);
+        List<PolygonLevel> res = THREAD_POOL.invoke(new Worker(im, boundaryWays, 0, boundaryWays.size(), new ArrayList<PolygonLevel>(),
                 Math.max(32, boundaryWays.size() / THREAD_POOL.getParallelism() / 3)));
+        if (!boundaryWays.isEmpty())
+            Main.trace(im.toString());
+        return res;
     }
 
     private static class Worker extends RecursiveTask<List<PolygonLevel>> {
@@ -340,8 +430,10 @@
         private final int to;
         private final transient List<PolygonLevel> output;
         private final int directExecutionTaskSize;
+        private final IntersectionMatrix cache;
 
-        Worker(List<JoinedPolygon> input, int from, int to, List<PolygonLevel> output, int directExecutionTaskSize) {
+        Worker(IntersectionMatrix cache, List<JoinedPolygon> input, int from, int to, List<PolygonLevel> output, int directExecutionTaskSize) {
+            this.cache = cache;
             this.input = input;
             this.from = from;
             this.to = to;
@@ -352,15 +444,16 @@
         /**
          * Collects outer way and corresponding inner ways from all boundaries.
          * @param level nesting level
+         * @param cache cache that tracks previously calculated results
          * @param boundaryWays boundary ways
          * @return the outermostWay, or {@code null} if intersection found.
          */
-        private static List<PolygonLevel> findOuterWaysRecursive(int level, List<JoinedPolygon> boundaryWays) {
+        private static List<PolygonLevel> findOuterWaysRecursive(int level, IntersectionMatrix cache, List<JoinedPolygon> boundaryWays) {
 
             final List<PolygonLevel> result = new ArrayList<>();
 
             for (JoinedPolygon outerWay : boundaryWays) {
-                if (processOuterWay(level, boundaryWays, result, outerWay) == null) {
+                if (processOuterWay(level, cache, boundaryWays, result, outerWay) == null) {
                     return null;
                 }
             }
@@ -368,9 +461,9 @@
             return result;
         }
 
-        private static List<PolygonLevel> processOuterWay(int level, List<JoinedPolygon> boundaryWays,
+        private static List<PolygonLevel> processOuterWay(int level, IntersectionMatrix cache, List<JoinedPolygon> boundaryWays,
                 final List<PolygonLevel> result, JoinedPolygon outerWay) {
-            Pair<Boolean, List<JoinedPolygon>> p = findInnerWaysCandidates(outerWay, boundaryWays);
+            Pair<Boolean, List<JoinedPolygon>> p = findInnerWaysCandidates(cache, outerWay, boundaryWays);
             if (p == null) {
                 // ways intersect
                 return null;
@@ -382,7 +475,7 @@
 
                 //process inner ways
                 if (!p.b.isEmpty()) {
-                    List<PolygonLevel> innerList = findOuterWaysRecursive(level + 1, p.b);
+                    List<PolygonLevel> innerList = findOuterWaysRecursive(level + 1, cache, p.b);
                     if (innerList == null) {
                         return null; //intersection found
                     }
@@ -408,7 +501,7 @@
             } else {
                 final Collection<ForkJoinTask<List<PolygonLevel>>> tasks = new ArrayList<>();
                 for (int fromIndex = from; fromIndex < to; fromIndex += directExecutionTaskSize) {
-                    tasks.add(new Worker(input, fromIndex, Math.min(fromIndex + directExecutionTaskSize, to),
+                    tasks.add(new Worker(cache, input, fromIndex, Math.min(fromIndex + directExecutionTaskSize, to),
                             new ArrayList<PolygonLevel>(), directExecutionTaskSize));
                 }
                 for (ForkJoinTask<List<PolygonLevel>> task : ForkJoinTask.invokeAll(tasks)) {
@@ -424,7 +517,7 @@
 
         List<PolygonLevel> computeDirectly() {
             for (int i = from; i < to; i++) {
-                if (processOuterWay(0, input, output, input.get(i)) == null) {
+                if (processOuterWay(0, cache, input, output, input.get(i)) == null) {
                     return null;
                 }
             }
