Changeset 15160 in josm


Ignore:
Timestamp:
2019-06-06T08:23:30+02:00 (6 weeks ago)
Author:
GerdP
Message:

fix #17768: Use code in MultipolygonTest to improve CreateMultipolygonAction

  • implement new method makeFromWays() in MultipolygonTest
  • let MultipolygonBuilder.makeFromWays() use the new method, it is much faster and detects more cases where the geometry is invalid.
  • remove now obsolete code in MultipolygonBuilder, add javadoc for some public fields
  • let CreateMultipolygonAction use the new method and add some code to improve user feedback when update removes members or when nothing was changed but the relation is still not a valid multipolygon.
  • let Geometry use final field JoinedPolygon.nodes instead of method JoinedPolygon.getNodes() as this is not a getter but a calculation routine which should be changed later.
Location:
trunk/src/org/openstreetmap/josm
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/CreateMultipolygonAction.java

    r15143 r15160  
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
     5import static org.openstreetmap.josm.tools.I18n.trn;
    56
    67import java.awt.event.ActionEvent;
     
    1213import java.util.HashMap;
    1314import java.util.HashSet;
     15import java.util.Iterator;
     16import java.util.LinkedHashSet;
    1417import java.util.List;
    1518import java.util.Map;
     
    2932import org.openstreetmap.josm.data.UndoRedoHandler;
    3033import org.openstreetmap.josm.data.osm.DataSet;
    31 import org.openstreetmap.josm.data.osm.MultipolygonBuilder;
    32 import org.openstreetmap.josm.data.osm.MultipolygonBuilder.JoinedPolygon;
    3334import org.openstreetmap.josm.data.osm.OsmPrimitive;
    3435import org.openstreetmap.josm.data.osm.OsmUtils;
     
    3637import org.openstreetmap.josm.data.osm.RelationMember;
    3738import org.openstreetmap.josm.data.osm.Way;
     39import org.openstreetmap.josm.data.validation.TestError;
     40import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
    3841import org.openstreetmap.josm.gui.MainApplication;
    3942import org.openstreetmap.josm.gui.Notification;
     
    205208     * @param selectedWays selected ways
    206209     * @param selectedMultipolygonRelation selected multipolygon relation
    207      * @return pair of old and new multipolygon relation
     210     * @return pair of old and new multipolygon relation if a difference was found, else the pair contains the old relation twice
    208211     */
    209212    public static Pair<Relation, Relation> updateMultipolygonRelation(Collection<Way> selectedWays, Relation selectedMultipolygonRelation) {
     
    213216        ways.addAll(selectedMultipolygonRelation.getMemberPrimitives(Way.class));
    214217
    215         final MultipolygonBuilder polygon = analyzeWays(ways, true);
    216         if (polygon == null) {
    217             return null; //could not make multipolygon.
    218         } else {
    219             return Pair.create(selectedMultipolygonRelation, createRelation(polygon, selectedMultipolygonRelation));
    220         }
     218        // even if no way was added the inner/outer roles might be different
     219        MultipolygonTest mpTest = new MultipolygonTest();
     220        Relation calculated = mpTest.makeFromWays(ways);
     221        if (mpTest.getErrors().isEmpty()) {
     222            return mergeRelationsMembers(selectedMultipolygonRelation, calculated);
     223        }
     224        showErrors(mpTest.getErrors());
     225        return null; //could not make multipolygon.
     226    }
     227
     228    /**
     229     * Merge members of multipolygon relation. Maintains the order of the old relation. May change roles,
     230     * removes duplicate and non-way members and adds new members found in {@code calculated}.
     231     * @param old old multipolygon relation
     232     * @param calculated calculated multipolygon relation
     233     * @return pair of old and new multipolygon relation if a difference was found, else the pair contains the old relation twice
     234     */
     235    private static Pair<Relation, Relation> mergeRelationsMembers(Relation old, Relation calculated) {
     236        Set<RelationMember> merged = new LinkedHashSet<>();
     237        boolean foundDiff = false;
     238        int nonWayMember = 0;
     239        // maintain order of members in updated relation
     240        for (RelationMember oldMem :old.getMembers()) {
     241            if (oldMem.isNode() || oldMem.isRelation()) {
     242                nonWayMember++;
     243                continue;
     244            }
     245            for (RelationMember newMem : calculated.getMembers()) {
     246                if (newMem.getMember().equals(oldMem.getMember())) {
     247                    if (!newMem.getRole().equals(oldMem.getRole())) {
     248                        foundDiff = true;
     249                    }
     250                    foundDiff |= !merged.add(newMem); // detect duplicate members in old relation
     251                    break;
     252                }
     253            }
     254        }
     255        if (nonWayMember > 0) {
     256            foundDiff = true;
     257            String msg = trn("Non-Way member removed from multipolygon", "Non-Way members removed from multipolygon", nonWayMember);
     258            GuiHelper.runInEDT(() -> new Notification(msg).setIcon(JOptionPane.WARNING_MESSAGE).show());
     259        }
     260        foundDiff |= merged.addAll(calculated.getMembers());
     261        if (!foundDiff) {
     262            return Pair.create(old, old); // unchanged
     263        }
     264        Relation toModify = new Relation(old);
     265        toModify.setMembers(new ArrayList<>(merged));
     266        return Pair.create(old, toModify);
    221267    }
    222268
     
    228274     */
    229275    public static Pair<Relation, Relation> createMultipolygonRelation(Collection<Way> selectedWays, boolean showNotif) {
    230 
    231         final MultipolygonBuilder polygon = analyzeWays(selectedWays, showNotif);
    232         if (polygon == null) {
    233             return null; //could not make multipolygon.
    234         } else {
    235             return Pair.create(null, createRelation(polygon, null));
     276        MultipolygonTest mpTest = new MultipolygonTest();
     277        Relation calculated = mpTest.makeFromWays(selectedWays);
     278        calculated.setMembers(RelationSorter.sortMembersByConnectivity(calculated.getMembers()));
     279        if (mpTest.getErrors().isEmpty())
     280            return Pair.create(null, calculated);
     281        if (showNotif) {
     282            showErrors(mpTest.getErrors());
     283        }
     284        return null; //could not make multipolygon.
     285    }
     286
     287    private static void showErrors(List<TestError> errors) {
     288        if (!errors.isEmpty()) {
     289            StringBuilder sb = new StringBuilder();
     290            Set<String> errorMessages = new LinkedHashSet<>();
     291            errors.forEach(e-> errorMessages.add(e.getMessage()));
     292            Iterator<String> iter = errorMessages.iterator();
     293            while (iter.hasNext()) {
     294                sb.append(iter.next());
     295                if (iter.hasNext())
     296                    sb.append('\n');
     297            }
     298            GuiHelper.runInEDT(() -> new Notification(sb.toString()).setIcon(JOptionPane.INFORMATION_MESSAGE).show());
    236299        }
    237300    }
     
    252315            return null;
    253316        }
     317        boolean unchanged = rr.a == rr.b;
    254318        final Relation existingRelation = rr.a;
    255319        final Relation relation = rr.b;
     
    261325            commandName = getName(false);
    262326        } else {
    263             list.add(new ChangeCommand(existingRelation, relation));
     327            if (!unchanged) {
     328                list.add(new ChangeCommand(existingRelation, relation));
     329            }
     330            if (list.isEmpty()) {
     331                if (unchanged) {
     332                    MultipolygonTest mpTest = new MultipolygonTest();
     333                    mpTest.visit(existingRelation);
     334                    if (!mpTest.getErrors().isEmpty()) {
     335                        showErrors(mpTest.getErrors());
     336                        return null;
     337                    }
     338                }
     339
     340                GuiHelper.runInEDT(() -> new Notification(tr("Nothing changed")).setDuration(Notification.TIME_SHORT).setIcon(JOptionPane.INFORMATION_MESSAGE).show());
     341                return null;
     342            }
    264343            commandName = getName(true);
    265344        }
     
    290369    }
    291370
    292     /**
    293      * This method analyzes ways and creates multipolygon.
    294      * @param selectedWays list of selected ways
    295      * @param showNotif if {@code true}, shows a notification if an error occurs
    296      * @return <code>null</code>, if there was a problem with the ways.
    297      */
    298     private static MultipolygonBuilder analyzeWays(Collection<Way> selectedWays, boolean showNotif) {
    299 
    300         MultipolygonBuilder pol = new MultipolygonBuilder();
    301         final String error = pol.makeFromWays(selectedWays);
    302 
    303         if (error != null) {
    304             if (showNotif) {
    305                 GuiHelper.runInEDT(() ->
    306                         new Notification(error)
    307                         .setIcon(JOptionPane.INFORMATION_MESSAGE)
    308                         .show());
    309             }
    310             return null;
    311         } else {
    312             return pol;
    313         }
    314     }
    315 
    316     /**
    317      * Builds a relation from polygon ways.
    318      * @param pol data storage class containing polygon information
    319      * @param clone relation to clone, can be null
    320      * @return multipolygon relation
    321      */
    322     private static Relation createRelation(MultipolygonBuilder pol, Relation clone) {
    323         // Create new relation
    324         Relation rel = clone != null ? new Relation(clone) : new Relation();
    325         rel.put("type", "multipolygon");
    326         // Add ways to it
    327         for (JoinedPolygon jway:pol.outerWays) {
    328             addMembers(jway, rel, "outer");
    329         }
    330 
    331         for (JoinedPolygon jway:pol.innerWays) {
    332             addMembers(jway, rel, "inner");
    333         }
    334 
    335         if (clone == null) {
    336             rel.setMembers(RelationSorter.sortMembersByConnectivity(rel.getMembers()));
    337         }
    338 
    339         return rel;
    340     }
    341 
    342     private static void addMembers(JoinedPolygon polygon, Relation rel, String role) {
    343         final int count = rel.getMembersCount();
    344         final Set<Way> ways = new HashSet<>(polygon.ways);
    345         for (int i = 0; i < count; i++) {
    346             final RelationMember m = rel.getMember(i);
    347             if (ways.contains(m.getMember()) && !role.equals(m.getRole())) {
    348                 rel.setMember(i, new RelationMember(role, m.getMember()));
    349             }
    350         }
    351         ways.removeAll(rel.getMemberPrimitivesList());
    352         for (final Way way : ways) {
    353             rel.addMember(new RelationMember(role, way));
    354         }
    355     }
    356 
    357371    private static final List<String> DEFAULT_LINEAR_TAGS = Arrays.asList("barrier", "fence_type", "source");
    358372
  • trunk/src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java

    r14436 r15160  
    66import java.awt.Rectangle;
    77import java.awt.geom.Area;
    8 import java.io.IOException;
    9 import java.io.ObjectInputStream;
    10 import java.io.ObjectOutputStream;
    118import java.util.ArrayList;
    129import java.util.Collection;
    1310import java.util.Collections;
    14 import java.util.HashMap;
    1511import java.util.HashSet;
    1612import java.util.List;
    1713import java.util.Map;
    1814import java.util.Set;
    19 import java.util.concurrent.ForkJoinPool;
    20 import java.util.concurrent.ForkJoinTask;
    21 import java.util.concurrent.RecursiveTask;
    22 import java.util.function.Supplier;
    2315import java.util.stream.Collectors;
    2416
     17import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
    2518import org.openstreetmap.josm.tools.CheckParameterUtil;
    2619import org.openstreetmap.josm.tools.Geometry;
    27 import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
    28 import org.openstreetmap.josm.tools.Logging;
    2920import org.openstreetmap.josm.tools.MultiMap;
    3021import org.openstreetmap.josm.tools.Pair;
    31 import org.openstreetmap.josm.tools.Utils;
    3222
    3323/**
     
    3929public class MultipolygonBuilder {
    4030
    41     private static final ForkJoinPool THREAD_POOL = newForkJoinPool();
    42 
    43     private static ForkJoinPool newForkJoinPool() {
    44         try {
    45             return Utils.newForkJoinPool(
    46                     "multipolygon_creation.numberOfThreads", "multipolygon-builder-%d", Thread.NORM_PRIORITY);
    47         } catch (SecurityException e) {
    48             Logging.log(Logging.LEVEL_ERROR, "Unable to create new ForkJoinPool", e);
    49             return null;
    50         }
    51     }
    52 
    53     /**
    54      * Helper class to avoid unneeded costly intersection calculations.
    55      * If the intersection between polygons a and b was calculated we also know
    56      * the result of intersection between b and a. The lookup in the hash tables is
    57      * much faster than the intersection calculation.
    58      */
    59     private static class IntersectionMatrix {
    60         private final Map<Pair<JoinedPolygon, JoinedPolygon>, PolygonIntersection> results;
    61 
    62         IntersectionMatrix(Collection<JoinedPolygon> polygons) {
    63             results = new HashMap<>(Utils.hashMapInitialCapacity(polygons.size() * polygons.size()));
    64         }
    65 
    66         /**
    67          * Compute the reverse result of the intersection test done by {@code Geometry.polygonIntersection(Area a1, Area a2)}
    68          *
    69          * @param intersection the intersection result for polygons a1 and a2 (in that order)
    70          * @return the intersection result for a2 and a1
    71          */
    72         private static PolygonIntersection getReverseIntersectionResult(PolygonIntersection intersection) {
    73             switch (intersection) {
    74                 case FIRST_INSIDE_SECOND:
    75                     return PolygonIntersection.SECOND_INSIDE_FIRST;
    76                 case SECOND_INSIDE_FIRST:
    77                     return PolygonIntersection.FIRST_INSIDE_SECOND;
    78                 default:
    79                     return intersection;
    80             }
    81         }
    82 
    83         /**
    84          * Returns the precomputed intersection between two polygons if known. Otherwise perform {@code computation}.
    85          *
    86          * @param a1          first polygon
    87          * @param a2          second polygon
    88          * @param computation the computation to perform when intersection is unknown
    89          * @return the intersection between two polygons
    90          * @see Map#computeIfAbsent
    91          */
    92         PolygonIntersection computeIfAbsent(JoinedPolygon a1, JoinedPolygon a2, Supplier<PolygonIntersection> computation) {
    93             PolygonIntersection intersection = results.get(Pair.create(a1, a2));
    94             if (intersection == null) {
    95                 intersection = computation.get();
    96                 synchronized (results) {
    97                     results.put(Pair.create(a1, a2), intersection);
    98                     results.put(Pair.create(a2, a1), getReverseIntersectionResult(intersection));
    99                 }
    100             }
    101             return intersection;
    102         }
    103 
    104     }
    105 
    10631    /**
    10732     * Represents one polygon that consists of multiple ways.
    10833     */
    10934    public static class JoinedPolygon {
     35        /** list of ways building this polygon */
    11036        public final List<Way> ways;
     37        /** list of flags that indicate if the nodes of the way in the same position where reversed */
    11138        public final List<Boolean> reversed;
     39        /** the nodes of the polygon, first node is not duplicated as last node. */
    11240        public final List<Node> nodes;
     41        /** the area in east/north space */
    11342        public final Area area;
     43        /** the integer bounds,
     44         * @deprecated better use area.getBounds2D()
     45         *  */
     46        @Deprecated
    11447        public final Rectangle bounds;
    11548
     
    14073         */
    14174        public List<Node> getNodes() {
    142             List<Node> nodes = new ArrayList<>();
     75            List<Node> ringNodes = new ArrayList<>();
    14376
    14477            for (int waypos = 0; waypos < this.ways.size(); waypos++) {
    14578                Way way = this.ways.get(waypos);
    146                 boolean reversed = this.reversed.get(waypos).booleanValue();
    147 
    148                 if (!reversed) {
     79
     80                if (!this.reversed.get(waypos)) {
    14981                    for (int pos = 0; pos < way.getNodesCount() - 1; pos++) {
    150                         nodes.add(way.getNode(pos));
     82                        ringNodes.add(way.getNode(pos));
    15183                    }
    15284                } else {
    15385                    for (int pos = way.getNodesCount() - 1; pos > 0; pos--) {
    154                         nodes.add(way.getNode(pos));
     86                        ringNodes.add(way.getNode(pos));
    15587                    }
    15688                }
    15789            }
    15890
    159             return nodes;
    160         }
    161     }
    162 
    163     /**
    164      * Helper storage class for finding findOuterWays
    165      */
    166     static class PolygonLevel {
    167         public final int level; // nesting level, even for outer, odd for inner polygons.
    168         public final JoinedPolygon outerWay;
    169 
    170         public List<JoinedPolygon> innerWays;
    171 
    172         PolygonLevel(JoinedPolygon pol, int level) {
    173             this.outerWay = pol;
    174             this.level = level;
    175             this.innerWays = new ArrayList<>();
     91            return ringNodes;
    17692        }
    17793    }
     
    202118    /**
    203119     * Splits ways into inner and outer JoinedWays. Sets {@link #innerWays} and {@link #outerWays} to the result.
    204      * TODO: Currently cannot process touching polygons. See code in JoinAreasAction.
     120     * Calculation is done in {@link MultipolygonTest#makeFromWays(Collection)} to ensure that the result is a valid multipolygon.
    205121     * @param ways ways to analyze
    206122     * @return error description if the ways cannot be split, {@code null} if all fine.
    207123     */
    208124    public String makeFromWays(Collection<Way> ways) {
    209         try {
    210             List<JoinedPolygon> joinedWays = joinWays(ways);
    211             //analyze witch way is inside witch outside.
    212             return makeFromPolygons(joinedWays);
    213         } catch (JoinedPolygonCreationException ex) {
    214             Logging.debug(ex);
    215             return ex.getMessage();
    216         }
     125        MultipolygonTest mpTest = new MultipolygonTest();
     126        Relation calculated = mpTest.makeFromWays(ways);
     127        if (!mpTest.getErrors().isEmpty()) {
     128            return mpTest.getErrors().iterator().next().getMessage();
     129        }
     130        Pair<List<JoinedPolygon>, List<JoinedPolygon>> outerInner = joinWays(calculated);
     131        this.outerWays.clear();
     132        this.innerWays.clear();
     133        this.outerWays.addAll(outerInner.a);
     134        this.innerWays.addAll(outerInner.b);
     135        return null;
    217136    }
    218137
     
    328247        return joinedWays;
    329248    }
    330 
    331     /**
    332      * This method analyzes which ways are inner and which outer. Sets {@link #innerWays} and {@link #outerWays} to the result.
    333      * @param polygons polygons to analyze
    334      * @return error description if the ways cannot be split, {@code null} if all fine.
    335      */
    336     private String makeFromPolygons(List<JoinedPolygon> polygons) {
    337         List<PolygonLevel> list = findOuterWaysMultiThread(polygons);
    338 
    339         if (list == null) {
    340             return tr("There is an intersection between ways.");
    341         }
    342 
    343         this.outerWays.clear();
    344         this.innerWays.clear();
    345 
    346         //take every other level
    347         for (PolygonLevel pol : list) {
    348             if (pol.level % 2 == 0) {
    349                 this.outerWays.add(pol.outerWay);
    350             } else {
    351                 this.innerWays.add(pol.outerWay);
    352             }
    353         }
    354 
    355         return null;
    356     }
    357 
    358     private static Pair<Boolean, List<JoinedPolygon>> findInnerWaysCandidates(IntersectionMatrix cache,
    359             JoinedPolygon outerWay, Collection<JoinedPolygon> boundaryWays) {
    360         boolean outerGood = true;
    361         List<JoinedPolygon> innerCandidates = new ArrayList<>();
    362 
    363         for (JoinedPolygon innerWay : boundaryWays) {
    364             if (innerWay == outerWay) {
    365                 continue;
    366             }
    367 
    368             // Preliminary computation on bounds. If bounds do not intersect, no need to do a costly area intersection
    369             if (outerWay.bounds.intersects(innerWay.bounds)) {
    370                 // Bounds intersection, let's see in detail
    371                 final PolygonIntersection intersection = cache.computeIfAbsent(outerWay, innerWay,
    372                         () -> Geometry.polygonIntersection(outerWay.area, innerWay.area));
    373 
    374                 if (intersection == PolygonIntersection.FIRST_INSIDE_SECOND) {
    375                     outerGood = false;  // outer is inside another polygon
    376                     break;
    377                 } else if (intersection == PolygonIntersection.SECOND_INSIDE_FIRST) {
    378                     innerCandidates.add(innerWay);
    379                 } else if (intersection == PolygonIntersection.CROSSING) {
    380                     // ways intersect
    381                     return null;
    382                 }
    383             }
    384         }
    385 
    386         return new Pair<>(outerGood, innerCandidates);
    387     }
    388 
    389     /**
    390      * Collects outer way and corresponding inner ways from all boundaries.
    391      * @param boundaryWays boundary ways
    392      * @return the outermostWay, or {@code null} if intersection found.
    393      */
    394     private static List<PolygonLevel> findOuterWaysMultiThread(List<JoinedPolygon> boundaryWays) {
    395         final IntersectionMatrix cache = new IntersectionMatrix(boundaryWays);
    396         if (THREAD_POOL != null) {
    397             return THREAD_POOL.invoke(new Worker(cache, boundaryWays, 0, boundaryWays.size(), new ArrayList<PolygonLevel>(),
    398                     Math.max(32, boundaryWays.size() / THREAD_POOL.getParallelism() / 3)));
    399         } else {
    400             return new Worker(cache, boundaryWays, 0, boundaryWays.size(), new ArrayList<PolygonLevel>(), 0).computeDirectly();
    401         }
    402     }
    403 
    404     private static class Worker extends RecursiveTask<List<PolygonLevel>> {
    405 
    406         // Needed for Findbugs / Coverity because parent class is serializable
    407         private static final long serialVersionUID = 1L;
    408 
    409         private final transient List<JoinedPolygon> input;
    410         private final int from;
    411         private final int to;
    412         private final transient List<PolygonLevel> output;
    413         private final int directExecutionTaskSize;
    414         private final IntersectionMatrix cache;
    415 
    416         Worker(IntersectionMatrix cache, List<JoinedPolygon> input, int from, int to, List<PolygonLevel> output, int directExecutionTaskSize) {
    417             this.cache = cache;
    418             this.input = input;
    419             this.from = from;
    420             this.to = to;
    421             this.output = output;
    422             this.directExecutionTaskSize = directExecutionTaskSize;
    423         }
    424 
    425         /**
    426          * Collects outer way and corresponding inner ways from all boundaries.
    427          * @param level nesting level
    428          * @param cache cache that tracks previously calculated results
    429          * @param boundaryWays boundary ways
    430          * @return the outermostWay, or {@code null} if intersection found.
    431          */
    432         private static List<PolygonLevel> findOuterWaysRecursive(int level, IntersectionMatrix cache, List<JoinedPolygon> boundaryWays) {
    433 
    434             final List<PolygonLevel> result = new ArrayList<>();
    435 
    436             for (JoinedPolygon outerWay : boundaryWays) {
    437                 if (processOuterWay(level, cache, boundaryWays, result, outerWay) == null) {
    438                     return null;
    439                 }
    440             }
    441 
    442             return result;
    443         }
    444 
    445         private static List<PolygonLevel> processOuterWay(int level, IntersectionMatrix cache, List<JoinedPolygon> boundaryWays,
    446                 final List<PolygonLevel> result, JoinedPolygon outerWay) {
    447             Pair<Boolean, List<JoinedPolygon>> p = findInnerWaysCandidates(cache, outerWay, boundaryWays);
    448             if (p == null) {
    449                 // ways intersect
    450                 return null;
    451             }
    452 
    453             if (p.a) {
    454                 //add new outer polygon
    455                 PolygonLevel pol = new PolygonLevel(outerWay, level);
    456 
    457                 //process inner ways
    458                 if (!p.b.isEmpty()) {
    459                     List<PolygonLevel> innerList = findOuterWaysRecursive(level + 1, cache, p.b);
    460                     if (innerList == null) {
    461                         return null; //intersection found
    462                     }
    463 
    464                     result.addAll(innerList);
    465 
    466                     for (PolygonLevel pl : innerList) {
    467                         if (pl.level == level + 1) {
    468                             pol.innerWays.add(pl.outerWay);
    469                         }
    470                     }
    471                 }
    472 
    473                 result.add(pol);
    474             }
    475             return result;
    476         }
    477 
    478         @Override
    479         protected List<PolygonLevel> compute() {
    480             if (to - from <= directExecutionTaskSize) {
    481                 return computeDirectly();
    482             } else {
    483                 final Collection<ForkJoinTask<List<PolygonLevel>>> tasks = new ArrayList<>();
    484                 for (int fromIndex = from; fromIndex < to; fromIndex += directExecutionTaskSize) {
    485                     tasks.add(new Worker(cache, input, fromIndex, Math.min(fromIndex + directExecutionTaskSize, to),
    486                             new ArrayList<PolygonLevel>(), directExecutionTaskSize));
    487                 }
    488                 for (ForkJoinTask<List<PolygonLevel>> task : ForkJoinTask.invokeAll(tasks)) {
    489                     List<PolygonLevel> res = task.join();
    490                     if (res == null) {
    491                         return null;
    492                     }
    493                     output.addAll(res);
    494                 }
    495                 return output;
    496             }
    497         }
    498 
    499         List<PolygonLevel> computeDirectly() {
    500             for (int i = from; i < to; i++) {
    501                 if (processOuterWay(0, cache, input, output, input.get(i)) == null) {
    502                     return null;
    503                 }
    504             }
    505             return output;
    506         }
    507 
    508         private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
    509             // Needed for Findbugs / Coverity because parent class is serializable
    510             ois.defaultReadObject();
    511         }
    512 
    513         private void writeObject(ObjectOutputStream oos) throws IOException {
    514             // Needed for Findbugs / Coverity because parent class is serializable
    515             oos.defaultWriteObject();
    516         }
    517     }
    518249}
  • trunk/src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java

    r15132 r15160  
    7979    private static final int FOUND_OUTSIDE = 2;
    8080
     81    /** set when used to build a multipolygon relation */
     82    private Relation createdRelation;
     83
    8184    /**
    8285     * Constructs a new {@code MultipolygonTest}.
     
    464467            return;
    465468        }
    466 
     469        if (r == createdRelation) {
     470            List<RelationMember> modMembers = new ArrayList<>();
     471            for (PolygonLevel pol : list) {
     472                final String calculatedRole = (pol.level % 2 == 0) ? "outer" : "inner";
     473                for (long wayId : pol.outerWay.getWayIds()) {
     474                    RelationMember member = wayMap.get(wayId);
     475                    modMembers.add(new RelationMember(calculatedRole, member.getMember()));
     476                }
     477            }
     478            r.setMembers(modMembers);
     479            return;
     480        }
    467481        for (PolygonLevel pol : list) {
    468             String calculatedRole = (pol.level % 2 == 0) ? "outer" : "inner";
     482            final String calculatedRole = (pol.level % 2 == 0) ? "outer" : "inner";
    469483            for (long wayId : pol.outerWay.getWayIds()) {
    470484                RelationMember member = wayMap.get(wayId);
     
    894908        }
    895909    }
     910
     911    /**
     912     * Create a multipolygon relation from the given ways and test it.
     913     * @param ways the collection of ways
     914     * @return a pair of a {@link Multipolygon} instance and the relation.
     915     * @since 15160
     916     */
     917    public Relation makeFromWays(Collection<Way> ways) {
     918        Relation r = new Relation();
     919        createdRelation = r;
     920        r.put("type", "multipolygon");
     921        for (Way w : ways) {
     922            r.addMember(new RelationMember("", w));
     923        }
     924        errors.clear();
     925        Multipolygon polygon = null;
     926        boolean hasRepeatedMembers = checkRepeatedWayMembers(r);
     927        if (!hasRepeatedMembers) {
     928            polygon = new Multipolygon(r);
     929            // don't check style consistency here
     930            checkGeometryAndRoles(r, polygon);
     931        }
     932        createdRelation = null;
     933        errors.removeIf(e->e.getSeverity() == Severity.OTHER);
     934        return r;
     935    }
     936
    896937}
  • trunk/src/org/openstreetmap/josm/tools/Geometry.java

    r15107 r15160  
    10161016        for (JoinedPolygon out : outerInner.a) {
    10171017            if (a1 == null
    1018                     ? nodeInsidePolygon(nodes.get(0), out.getNodes())
     1018                    ? nodeInsidePolygon(nodes.get(0), out.nodes)
    10191019                    : PolygonIntersection.FIRST_INSIDE_SECOND == polygonIntersection(a1, out.area)) {
    10201020                boolean insideInner = false;
    10211021                // If inside an outer, check it is not inside an inner
    10221022                for (JoinedPolygon in : outerInner.b) {
    1023                     if (a1 == null ? nodeInsidePolygon(nodes.get(0), in.getNodes())
     1023                    if (a1 == null ? nodeInsidePolygon(nodes.get(0), in.nodes)
    10241024                            : in.area.getBounds2D().contains(a1.getBounds2D())
    10251025                                    && polygonIntersection(a1, in.area) == PolygonIntersection.FIRST_INSIDE_SECOND
Note: See TracChangeset for help on using the changeset viewer.