Ticket #17768: 17768-v0.patch

File 17768-v0.patch, 12.4 KB (added by GerdP, 6 years ago)

improved version

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

     
    22package org.openstreetmap.josm.actions;
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
     5import static org.openstreetmap.josm.tools.I18n.trn;
    56
    67import java.awt.event.ActionEvent;
    78import java.awt.event.KeyEvent;
     
    1112import java.util.Collections;
    1213import java.util.HashMap;
    1314import java.util.HashSet;
     15import java.util.Iterator;
     16import java.util.LinkedHashSet;
    1417import java.util.List;
    1518import java.util.Map;
    1619import java.util.Map.Entry;
     
    2831import org.openstreetmap.josm.command.SequenceCommand;
    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;
    3536import org.openstreetmap.josm.data.osm.Relation;
    3637import org.openstreetmap.josm.data.osm.RelationMember;
    3738import org.openstreetmap.josm.data.osm.Way;
     39import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
     40import org.openstreetmap.josm.data.validation.TestError;
     41import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;
    3842import org.openstreetmap.josm.gui.MainApplication;
    3943import org.openstreetmap.josm.gui.Notification;
    4044import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask;
     
    212216        Set<Way> ways = new HashSet<>(selectedWays);
    213217        ways.addAll(selectedMultipolygonRelation.getMemberPrimitives(Way.class));
    214218
    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));
     219        // even if no way was added the inner/outer roles might be different
     220        MultipolygonTest mpTest = new MultipolygonTest();
     221        Pair<Multipolygon, Relation> tested = mpTest.makeFromWays(ways);
     222        if (mpTest.getErrors().isEmpty()) {
     223            return mergeRelationsMembers(selectedMultipolygonRelation, tested.b);
    220224        }
     225        showErrors(mpTest.getErrors());
     226        return null; //could not make multipolygon.
    221227    }
    222228
    223229    /**
     230     * Merge members of multipolygon relation. Maintains the order of the old relation. May change roles,
     231     * removes duplicate and non-way members and adds new members found in {@code calculated}.
     232     * @param old old multipolygon relation
     233     * @param calculated calculated multipolygon relation
     234     * @return pair of old and new multipolygon relation
     235     */
     236    private static Pair<Relation, Relation> mergeRelationsMembers(Relation old, Relation calculated) {
     237        Set<RelationMember> merged = new LinkedHashSet<>();
     238        boolean foundDiff = false;
     239        int nonWayMember = 0;
     240        // maintain order of members in updated relation
     241        for (RelationMember oldMem :old.getMembers()) {
     242            if (oldMem.isNode() || oldMem.isRelation()) {
     243                nonWayMember++;
     244                continue;
     245            }
     246            for (RelationMember newMem : calculated.getMembers()) {
     247                if (newMem.getMember().equals(oldMem.getMember())) {
     248                    if (!newMem.getRole().equals(oldMem.getRole())) {
     249                        foundDiff = true;
     250                    }
     251                    foundDiff |= !merged.add(newMem); // detect duplicate members in old relation
     252                    break;
     253                }
     254            }
     255        }
     256        if (nonWayMember > 0) {
     257            foundDiff = true;
     258            String msg = trn("Non-Way member removed from multipolygon", "Non-Way members removed from multipolygon", nonWayMember);
     259            GuiHelper.runInEDT(() -> new Notification(msg).setIcon(JOptionPane.WARNING_MESSAGE).show());
     260        }
     261        foundDiff |= merged.addAll(calculated.getMembers());
     262        if (!foundDiff) {
     263            return Pair.create(old, old); // unchanged
     264        }
     265        Relation toModify = new Relation(old);
     266        toModify.setMembers(new ArrayList<>(merged));
     267        return Pair.create(old, toModify);
     268    }
     269
     270    /**
    224271     * Returns a {@link Pair} null and the newly created/modified multipolygon {@link Relation}.
    225272     * @param selectedWays selected ways
    226273     * @param showNotif if {@code true}, shows a notification if an error occurs
     
    227274     * @return pair of null and new multipolygon relation
    228275     */
    229276    public static Pair<Relation, Relation> createMultipolygonRelation(Collection<Way> selectedWays, boolean showNotif) {
     277        MultipolygonTest mpTest = new MultipolygonTest();
     278        Pair<Multipolygon, Relation> tested = mpTest.makeFromWays(selectedWays);
     279        tested.b.setMembers(RelationSorter.sortMembersByConnectivity(tested.b.getMembers()));
     280        if (mpTest.getErrors().isEmpty())
     281            return Pair.create(null, tested.b);
     282        if (showNotif) {
     283            showErrors(mpTest.getErrors());
     284        }
     285        return null; //could not make multipolygon.
     286    }
    230287
    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));
     288    private static void showErrors(List<TestError> errors) {
     289        if (!errors.isEmpty()) {
     290            StringBuilder sb = new StringBuilder();
     291            Set<String> errorMessages = new LinkedHashSet<>();
     292            errors.forEach(e-> errorMessages.add(e.getMessage()));
     293            Iterator<String> iter = errorMessages.iterator();
     294            while (iter.hasNext()) {
     295                sb.append(iter.next());
     296                if (iter.hasNext())
     297                    sb.append('\n');
     298            }
     299            GuiHelper.runInEDT(() -> new Notification(sb.toString()).setIcon(JOptionPane.INFORMATION_MESSAGE).show());
    236300        }
    237301    }
    238302
     
    260324            list.add(new AddCommand(selectedWays.iterator().next().getDataSet(), relation));
    261325            commandName = getName(false);
    262326        } else {
    263             list.add(new ChangeCommand(existingRelation, relation));
     327            if (existingRelation != relation) {
     328                list.add(new ChangeCommand(existingRelation, relation));
     329            }
     330            if (list.isEmpty()) {
     331                GuiHelper.runInEDT(() -> new Notification(tr("Nothing changed")).setDuration(Notification.TIME_SHORT).setIcon(JOptionPane.INFORMATION_MESSAGE).show());
     332                return null;
     333            }
    264334            commandName = getName(true);
    265335        }
    266336        return Pair.create(new SequenceCommand(commandName, list), relation);
     
    289359        }
    290360    }
    291361
    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 
    357362    private static final List<String> DEFAULT_LINEAR_TAGS = Arrays.asList("barrier", "fence_type", "source");
    358363
    359364    /**
  • 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        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 new Pair<>(polygon, r);
     935    }
     936
    896937}