Index: src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java	(revision 10819)
+++ src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java	(working copy)
@@ -15,6 +15,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.ForkJoinTask;
 import java.util.concurrent.RecursiveTask;
@@ -40,6 +41,65 @@
             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 long countCheck;
+        private long countMiss;
+        private final Map<JoinedPolygon, Map<JoinedPolygon, PolygonIntersection>> results;
+
+        IntersectionMatrix(Collection<JoinedPolygon> polygons) {
+            results = new ConcurrentHashMap<>(polygons.size());
+        }
+
+        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;
+        }
+
+        private void updateMap(JoinedPolygon pa, JoinedPolygon pb, PolygonIntersection intersection) {
+            Map<JoinedPolygon, PolygonIntersection> subMap = results.get(pa);
+            if (subMap == null) {
+                subMap = new ConcurrentHashMap<>();
+                results.put(pa, subMap);
+            }
+            subMap.put(pb, intersection);
+        }
+
+        /**
+         * Store the result of intersection between two polygons
+         * @param pa first polygon
+         * @param pb second polygon
+         * @param intersection result of {@code Geometry.polygonIntersection(pa,pb)}
+         */
+        public void update(JoinedPolygon pa, JoinedPolygon pb, PolygonIntersection intersection) {
+            updateMap(pa, pb, intersection);
+            updateMap(pb, pa, getReverseIntersectionResult(intersection));
+        }
+
+        public PolygonIntersection getIntersection(JoinedPolygon pa, JoinedPolygon pb) {
+            countCheck++;
+            Map<JoinedPolygon, PolygonIntersection> subMap = results.get(pa);
+            if (subMap == null) {
+                countMiss++;
+                return null;
+            }
+            return subMap.get(pb);
+        }
+
+        @Override
+        public String toString() {
+            return "Tests: " + countCheck + " hit/miss " + (countCheck - countMiss) + "/" + countMiss;
+        }
+    }
+
+    /**
      * Represents one polygon that consists of multiple ways.
      */
     public static class JoinedPolygon {
@@ -291,7 +351,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 +364,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.getIntersection(outerWay, innerWay);
+                if (intersection == null) {
+                    intersection = Geometry.polygonIntersection(outerWay.area, innerWay.area);
+                    cache.update(outerWay, innerWay, intersection);
+                }
 
                 if (intersection == PolygonIntersection.FIRST_INSIDE_SECOND) {
                     outerGood = false;  // outer is inside another polygon
@@ -326,7 +391,8 @@
      * @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);
+        return THREAD_POOL.invoke(new Worker(im, boundaryWays, 0, boundaryWays.size(), new ArrayList<PolygonLevel>(),
                 Math.max(32, boundaryWays.size() / THREAD_POOL.getParallelism() / 3)));
     }
 
@@ -340,8 +406,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 +420,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 +437,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 +451,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 +477,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 +493,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;
                 }
             }
