Ticket #17768: 17768-beta.patch

File 17768-beta.patch, 11.2 KB (added by GerdP, 6 years ago)

Proof of concept, I am not yet 100% sure how to handle update of an invalid MP (e.g. node as member)

  • src/org/openstreetmap/josm/actions/CreateMultipolygonAction.java

     
    1111import java.util.Collections;
    1212import java.util.HashMap;
    1313import java.util.HashSet;
     14import java.util.LinkedHashSet;
    1415import java.util.List;
    1516import java.util.Map;
    1617import java.util.Map.Entry;
     
    2829import org.openstreetmap.josm.command.SequenceCommand;
    2930import org.openstreetmap.josm.data.UndoRedoHandler;
    3031import org.openstreetmap.josm.data.osm.DataSet;
    31 import org.openstreetmap.josm.data.osm.MultipolygonBuilder;
    32 import org.openstreetmap.josm.data.osm.MultipolygonBuilder.JoinedPolygon;
    3332import org.openstreetmap.josm.data.osm.OsmPrimitive;
    3433import org.openstreetmap.josm.data.osm.OsmUtils;
    3534import org.openstreetmap.josm.data.osm.Relation;
    3635import org.openstreetmap.josm.data.osm.RelationMember;
    3736import org.openstreetmap.josm.data.osm.Way;
     37import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
     38import org.openstreetmap.josm.data.validation.TestError;
     39import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
    3840import org.openstreetmap.josm.gui.MainApplication;
    3941import org.openstreetmap.josm.gui.Notification;
    4042import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask;
     
    210212        Set<Way> ways = new HashSet<>(selectedWays);
    211213        ways.addAll(selectedMultipolygonRelation.getMemberPrimitives(Way.class));
    212214
    213         final MultipolygonBuilder polygon = analyzeWays(ways, true);
    214         if (polygon == null) {
    215             return null; //could not make multipolygon.
    216         } else {
    217             return Pair.create(selectedMultipolygonRelation, createRelation(polygon, selectedMultipolygonRelation));
     215        // even if no way was added the inner/outer roles might be different
     216        MultipolygonTest mpTest = new MultipolygonTest();
     217        Pair<Multipolygon, Relation> tested = mpTest.makeFromWays(ways);
     218        if (mpTest.getErrors().isEmpty()) {
     219            return mergeRelationsMembers(selectedMultipolygonRelation, tested.b);
    218220        }
     221        showErrors(mpTest.getErrors());
     222        return null; //could not make multipolygon.
    219223    }
    220224
    221225    /**
     226     * Merge members of multipolygon relation. Maintains the order of the old relation. May change roles,
     227     * removes duplicate and non-way members and adds new members found in {@code calculated}.
     228     * @param old old multipolygon relation
     229     * @param calculated calculated multipolygon relation
     230     * @return pair of old and new multipolygon relation
     231     */
     232    private static Pair<Relation, Relation> mergeRelationsMembers(Relation old, Relation calculated) {
     233        Set<RelationMember> merged = new LinkedHashSet<>();
     234        boolean foundDiff = false;
     235        boolean foundNonWayMember = false;
     236        // maintain order of members in updated relation
     237        for (RelationMember oldMem :old.getMembers()) {
     238            if (oldMem.isNode() || oldMem.isRelation()) {
     239                foundNonWayMember = true;
     240                continue;
     241            }
     242            for (RelationMember newMem : calculated.getMembers()) {
     243                if (newMem.getMember().equals(oldMem.getMember())) {
     244                    if (!newMem.getRole().equals(oldMem.getRole())) {
     245                        foundDiff = true;
     246                    }
     247                    foundDiff |= !merged.add(newMem); // detect duplicate members in old relation
     248                    break;
     249                }
     250            }
     251        }
     252        if (foundNonWayMember) {
     253            foundDiff = true;
     254            GuiHelper.runInEDT(() -> new Notification(tr("Non-Way removed from multipolygon")).setIcon(JOptionPane.WARNING_MESSAGE).show());
     255        }
     256        foundDiff |= merged.addAll(calculated.getMembers());
     257        if (!foundDiff) {
     258            GuiHelper.runInEDT(() -> new Notification(tr("Nothing changed")).setDuration(Notification.TIME_SHORT).setIcon(JOptionPane.INFORMATION_MESSAGE).show());
     259            return null;
     260        }
     261        Relation toModify = new Relation(old);
     262        toModify.setMembers(new ArrayList<>(merged));
     263        return Pair.create(old, toModify);
     264    }
     265
     266    /**
    222267     * Returns a {@link Pair} null and the newly created/modified multipolygon {@link Relation}.
    223268     * @param selectedWays selected ways
    224269     * @param showNotif if {@code true}, shows a notification if an error occurs
     
    225270     * @return pair of null and new multipolygon relation
    226271     */
    227272    public static Pair<Relation, Relation> createMultipolygonRelation(Collection<Way> selectedWays, boolean showNotif) {
     273        MultipolygonTest mpTest = new MultipolygonTest();
     274        Pair<Multipolygon, Relation> tested = mpTest.makeFromWays(selectedWays);
     275        tested.b.setMembers(RelationSorter.sortMembersByConnectivity(tested.b.getMembers()));
     276        if (mpTest.getErrors().isEmpty())
     277            return Pair.create(null, tested.b);
     278        if (showNotif) {
     279            showErrors(mpTest.getErrors());
     280        }
     281        return null; //could not make multipolygon.
     282    }
    228283
    229         final MultipolygonBuilder polygon = analyzeWays(selectedWays, showNotif);
    230         if (polygon == null) {
    231             return null; //could not make multipolygon.
    232         } else {
    233             return Pair.create(null, createRelation(polygon, null));
     284    private static void showErrors(List<TestError> errors) {
     285        StringBuilder sb = new StringBuilder();
     286        for (TestError e : errors) {
     287            sb.append(e.getMessage()).append('\n');
    234288        }
     289        GuiHelper.runInEDT(() -> new Notification(sb.toString()).setIcon(JOptionPane.INFORMATION_MESSAGE).show());
    235290    }
    236291
    237292    /**
     
    287342        }
    288343    }
    289344
    290     /**
    291      * This method analyzes ways and creates multipolygon.
    292      * @param selectedWays list of selected ways
    293      * @param showNotif if {@code true}, shows a notification if an error occurs
    294      * @return <code>null</code>, if there was a problem with the ways.
    295      */
    296     private static MultipolygonBuilder analyzeWays(Collection<Way> selectedWays, boolean showNotif) {
    297 
    298         MultipolygonBuilder pol = new MultipolygonBuilder();
    299         final String error = pol.makeFromWays(selectedWays);
    300 
    301         if (error != null) {
    302             if (showNotif) {
    303                 GuiHelper.runInEDT(() ->
    304                         new Notification(error)
    305                         .setIcon(JOptionPane.INFORMATION_MESSAGE)
    306                         .show());
    307             }
    308             return null;
    309         } else {
    310             return pol;
    311         }
    312     }
    313 
    314     /**
    315      * Builds a relation from polygon ways.
    316      * @param pol data storage class containing polygon information
    317      * @param clone relation to clone, can be null
    318      * @return multipolygon relation
    319      */
    320     private static Relation createRelation(MultipolygonBuilder pol, Relation clone) {
    321         // Create new relation
    322         Relation rel = clone != null ? new Relation(clone) : new Relation();
    323         rel.put("type", "multipolygon");
    324         // Add ways to it
    325         for (JoinedPolygon jway:pol.outerWays) {
    326             addMembers(jway, rel, "outer");
    327         }
    328 
    329         for (JoinedPolygon jway:pol.innerWays) {
    330             addMembers(jway, rel, "inner");
    331         }
    332 
    333         if (clone == null) {
    334             rel.setMembers(RelationSorter.sortMembersByConnectivity(rel.getMembers()));
    335         }
    336 
    337         return rel;
    338     }
    339 
    340     private static void addMembers(JoinedPolygon polygon, Relation rel, String role) {
    341         final int count = rel.getMembersCount();
    342         final Set<Way> ways = new HashSet<>(polygon.ways);
    343         for (int i = 0; i < count; i++) {
    344             final RelationMember m = rel.getMember(i);
    345             if (ways.contains(m.getMember()) && !role.equals(m.getRole())) {
    346                 rel.setMember(i, new RelationMember(role, m.getMember()));
    347             }
    348         }
    349         ways.removeAll(rel.getMemberPrimitivesList());
    350         for (final Way way : ways) {
    351             rel.addMember(new RelationMember(role, way));
    352         }
    353     }
    354 
    355345    private static final List<String> DEFAULT_LINEAR_TAGS = Arrays.asList("barrier", "fence_type", "source");
    356346
    357347    /**
  • src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java

     
    3838import org.openstreetmap.josm.tools.Geometry;
    3939import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
    4040import org.openstreetmap.josm.tools.Logging;
     41import org.openstreetmap.josm.tools.Pair;
    4142
    4243/**
    4344 * Checks if multipolygons are valid
     
    7879    private static final int FOUND_INSIDE = 1;
    7980    private static final int FOUND_OUTSIDE = 2;
    8081
     82    /** set when used to build a multipolygon relation */
     83    private Relation createdRelation;
     84
    8185    /**
    8286     * Constructs a new {@code MultipolygonTest}.
    8387     */
     
    463467        if (list == null || list.isEmpty()) {
    464468            return;
    465469        }
    466 
     470        if (r == createdRelation) {
     471            List<RelationMember> modMembers = new ArrayList<>();
     472            for (PolygonLevel pol : list) {
     473                final String calculatedRole = (pol.level % 2 == 0) ? "outer" : "inner";
     474                for (long wayId : pol.outerWay.getWayIds()) {
     475                    RelationMember member = wayMap.get(wayId);
     476                    modMembers.add(new RelationMember(calculatedRole, member.getMember()));
     477                }
     478            }
     479            r.setMembers(modMembers);
     480            return;
     481        }
    467482        for (PolygonLevel pol : list) {
    468             String calculatedRole = (pol.level % 2 == 0) ? "outer" : "inner";
     483            final String calculatedRole = (pol.level % 2 == 0) ? "outer" : "inner";
    469484            for (long wayId : pol.outerWay.getWayIds()) {
    470485                RelationMember member = wayMap.get(wayId);
    471486                if (!calculatedRole.equals(member.getRole())) {
     
    893908            return null;
    894909        }
    895910    }
     911
     912    /**
     913     * Create a multipolygon relation from the given ways and test it.
     914     * @param ways the collection of ways
     915     * @return a pair of a {@link Multipolygon} instance and the relation.
     916     */
     917    public Pair<Multipolygon, 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        // Rest of checks is only for complete multipolygon
     928        if (!hasRepeatedMembers && !r.hasIncompleteMembers()) {
     929            polygon = new Multipolygon(r);
     930            checkGeometryAndRoles(r, polygon);
     931        }
     932        errors.removeIf(e->e.getSeverity() == Severity.OTHER);
     933        return new Pair<>(polygon, r);
     934    }
     935
    896936}