Index: src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java
===================================================================
--- src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java	(revision 10743)
+++ src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java	(working copy)
@@ -36,6 +36,54 @@
     private static final ForkJoinPool THREAD_POOL =
             Utils.newForkJoinPool("multipolygon_creation.numberOfThreads", "multipolygon-builder-%d", Thread.NORM_PRIORITY);
 
+    private static class ResultCache {
+        Geometry.PolygonIntersection[] results;
+        private final int dim;
+        private long countCheck;
+        private long countMiss;
+
+        public ResultCache(Collection<JoinedPolygon> polygons) {
+            int id = 0;
+            for (JoinedPolygon p : polygons)
+                p.setCacheId(id++);
+
+            this.dim = id;
+            results = new Geometry.PolygonIntersection[dim*dim];
+        }
+
+
+        private synchronized PolygonIntersection getCachedResult(JoinedPolygon pa, JoinedPolygon pb){
+            if (pa.id < 0 || pa.id >= dim ){
+                throw new JoinedPolygonCreationException(tr("Internal error: unexpected id in 1st polygon", pa.id));
+            }
+            if (pb.id < 0 || pb.id >= dim ){
+                throw new JoinedPolygonCreationException(tr("Internal error: unexpected id in 2nd polygon", pb.id));
+            }
+            countCheck++;
+            int posAB = pa.id * dim + pb.id;
+            if (results[posAB] == null){
+                countMiss++;
+                PolygonIntersection intersection = Geometry.polygonIntersection(pa.area, pb.area);
+                results[posAB] = intersection;
+                // set the results for exchanged parameters (a is outer b also means b is outer a etc.)
+                int posBA = pb.id * dim + pa.id;
+                if (intersection == PolygonIntersection.OUTSIDE || intersection == PolygonIntersection.CROSSING){
+                    results[posBA] = intersection;
+                } else if (intersection == PolygonIntersection.FIRST_INSIDE_SECOND){
+                    results[posBA] = PolygonIntersection.SECOND_INSIDE_FIRST;
+                } else if (intersection == PolygonIntersection.SECOND_INSIDE_FIRST)
+                    results[posBA] = PolygonIntersection.FIRST_INSIDE_SECOND;
+
+            }
+            return results[posAB];
+        }
+
+        @Override
+        public String toString() {
+            return "Tests: " + countCheck + " hit/miss " + (countCheck - countMiss) + "/" + countMiss;
+        }
+    }
+
     /**
      * Represents one polygon that consists of multiple ways.
      */
@@ -45,7 +93,9 @@
         public final List<Node> nodes;
         public final Area area;
         public final Rectangle bounds;
+        private int id;
 
+
         /**
          * Constructs a new {@code JoinedPolygon} from given list of ways.
          * @param ways The ways used to build joined polygon
@@ -57,6 +107,7 @@
             this.nodes = this.getNodes();
             this.area = Geometry.getArea(nodes);
             this.bounds = area.getBounds();
+            this.id = -1;
         }
 
         /**
@@ -91,6 +142,14 @@
 
             return nodes;
         }
+
+        /**
+         * Set id that is used in ResultCache
+         * @param id
+         */
+        public void setCacheId(int id) {
+            this.id = id;
+        }
     }
 
     /**
@@ -240,7 +299,6 @@
             usedWays.addAll(collectedWays);
             joinedWays.add(new JoinedPolygon(collectedWays, collectedWaysReverse));
         }
-
         return joinedWays;
     }
 
@@ -271,7 +329,7 @@
         return null;
     }
 
-    private static Pair<Boolean, List<JoinedPolygon>> findInnerWaysCandidates(JoinedPolygon outerWay, Collection<JoinedPolygon> boundaryWays) {
+    private static Pair<Boolean, List<JoinedPolygon>> findInnerWaysCandidates(ResultCache cache, JoinedPolygon outerWay, Collection<JoinedPolygon> boundaryWays) {
         boolean outerGood = true;
         List<JoinedPolygon> innerCandidates = new ArrayList<>();
 
@@ -283,7 +341,8 @@
             // 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.getCachedResult(outerWay, innerWay);
+//                PolygonIntersection intersection = Geometry.polygonIntersection(outerWay.area, innerWay.area);
 
                 if (intersection == PolygonIntersection.FIRST_INSIDE_SECOND) {
                     outerGood = false;  // outer is inside another polygon
@@ -305,9 +364,13 @@
      * @param boundaryWays boundary ways
      * @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>(),
+    private List<PolygonLevel> findOuterWaysMultiThread(List<JoinedPolygon> boundaryWays) {
+        ResultCache cache = new ResultCache(boundaryWays);
+        List<PolygonLevel> res = THREAD_POOL.invoke(new Worker(cache, boundaryWays, 0, boundaryWays.size(), new ArrayList<PolygonLevel>(),
                 Math.max(32, boundaryWays.size() / THREAD_POOL.getParallelism() / 3)));
+        if (!boundaryWays.isEmpty())
+            Main.debug("mp cache: " + cache.toString());
+        return res;
     }
 
     private static class Worker extends RecursiveTask<List<PolygonLevel>> {
@@ -321,7 +384,10 @@
         private final transient List<PolygonLevel> output;
         private final int directExecutionTaskSize;
 
-        Worker(List<JoinedPolygon> input, int from, int to, List<PolygonLevel> output, int directExecutionTaskSize) {
+        private ResultCache cache;
+
+        Worker(ResultCache 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;
@@ -332,15 +398,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, ResultCache 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;
                 }
             }
@@ -348,9 +415,9 @@
             return result;
         }
 
-        private static List<PolygonLevel> processOuterWay(int level, List<JoinedPolygon> boundaryWays,
+        private static List<PolygonLevel> processOuterWay(int level, ResultCache 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;
@@ -362,7 +429,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
                     }
@@ -388,7 +455,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)) {
@@ -404,7 +471,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;
                 }
             }
Index: src/org/openstreetmap/josm/tools/Geometry.java
===================================================================
--- src/org/openstreetmap/josm/tools/Geometry.java	(revision 10743)
+++ src/org/openstreetmap/josm/tools/Geometry.java	(working copy)
@@ -545,7 +545,9 @@
      * @since 6841
      */
     public static PolygonIntersection polygonIntersection(Area a1, Area a2) {
-        return polygonIntersection(a1, a2, 1.0);
+        PolygonIntersection res = polygonIntersection(a1, a2, 1.0);
+//        System.out.println(res + " " + a1.getBounds() + " " + a2.getBounds());
+        return res;
     }
 
     /**
