Changeset 18976 in josm for trunk/src/org/openstreetmap


Ignore:
Timestamp:
2024-02-13T09:29:09+01:00 (9 months ago)
Author:
GerdP
Message:

fix #20424, fix #20425: Duplicate relations not detected with incomplete members, Duplicate Relation test is too lazy/too aggressive

  • add code to compare just members so that it doesn't matter if they are complete
  • change original test which compares geometry to use a list instead of a set to store members. That means that the order of members is taken into account as well as duplicated members
  • before comparing tags only the "discardable" tag keys are removed, previously all "uninteresting" keys were removed, so fixme=* or note=* was ignored
  • if two relations have the same members in the same order but different tags the new message informational message "Identical members" is produced.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/validation/tests/DuplicateRelation.java

    r17476 r18976  
    22package org.openstreetmap.josm.data.validation.tests;
    33
     4import static org.openstreetmap.josm.tools.I18n.marktr;
    45import static org.openstreetmap.josm.tools.I18n.tr;
    56
    67import java.util.ArrayList;
    78import java.util.Collection;
    8 import java.util.HashSet;
    99import java.util.LinkedList;
    1010import java.util.List;
     
    2020import org.openstreetmap.josm.data.coor.LatLon;
    2121import org.openstreetmap.josm.data.osm.AbstractPrimitive;
     22import org.openstreetmap.josm.data.osm.DataSet;
    2223import org.openstreetmap.josm.data.osm.Node;
    2324import org.openstreetmap.josm.data.osm.OsmPrimitive;
     
    3738public class DuplicateRelation extends Test {
    3839
     40    private static final String DUPLICATED_RELATIONS = marktr("Duplicated relations");
     41
    3942    /**
    4043     * Class to store one relation members and information about it
     
    111114    private static class RelationMembers {
    112115        /** Set of member objects of the relation */
    113         private final Set<RelMember> members;
     116        private final List<RelMember> members;
    114117
    115118        /** Store relation information
     
    117120         */
    118121        RelationMembers(List<RelationMember> members) {
    119             this.members = new HashSet<>(members.size());
     122            this.members = new ArrayList<>(members.size());
    120123            for (RelationMember member : members) {
    121124                this.members.add(new RelMember(member));
     
    176179    protected static final int SAME_RELATION = 1902;
    177180
    178     /** MultiMap of all relations */
    179     private MultiMap<RelationPair, OsmPrimitive> relations;
    180 
    181     /** MultiMap of all relations, regardless of keys */
    182     private MultiMap<List<RelationMember>, OsmPrimitive> relationsNoKeys;
    183 
    184     /** List of keys without useful information */
    185     private final Set<String> ignoreKeys = new HashSet<>(AbstractPrimitive.getUninterestingKeys());
     181    /** Code number of relation with same members error */
     182    protected static final int IDENTICAL_MEMBERLIST = 1903;
     183
     184
     185    /** List of all initially visited testable relations*/
     186    List<Relation> visited;
    186187
    187188    /**
     
    189190     */
    190191    public DuplicateRelation() {
    191         super(tr("Duplicated relations"),
     192        super(tr(DUPLICATED_RELATIONS),
    192193                tr("This test checks that there are no relations with same tags and same members with same roles."));
    193194    }
     
    196197    public void startTest(ProgressMonitor monitor) {
    197198        super.startTest(monitor);
    198         relations = new MultiMap<>(1000);
    199         relationsNoKeys = new MultiMap<>(1000);
     199        visited = new ArrayList<>();
     200
    200201    }
    201202
    202203    @Override
    203204    public void endTest() {
    204         for (Set<OsmPrimitive> duplicated : relations.values()) {
     205        if (!visited.isEmpty())
     206            performChecks();
     207        visited = null;
     208        super.endTest();
     209    }
     210
     211    private void performChecks() {
     212        MultiMap<RelationPair, Relation> relations = new MultiMap<>(1000);
     213        // MultiMap of all relations, regardless of keys
     214        MultiMap<List<RelationMember>, Relation> sameMembers = new MultiMap<>(1000);
     215
     216        for (Relation r : visited) {
     217            final List<RelationMember> rMembers = getSortedMembers(r);
     218            sameMembers.put(rMembers, r);
     219            addToRelations(relations, r, true);
     220        }
     221
     222        if (partialSelection) {
     223            // add data for relations which were not in the initial selection when
     224            // they can be duplicates
     225            DataSet ds = visited.iterator().next().getDataSet();
     226            for (Relation r : ds.getRelations()) {
     227                if (r.isDeleted() || r.getMembers().isEmpty() || visited.contains(r))
     228                    continue;
     229                final List<RelationMember> rMembers = getSortedMembers(r);
     230                if (sameMembers.containsKey(rMembers))
     231                    sameMembers.put(rMembers, r);
     232
     233                addToRelations(relations, r, false);
     234            }
     235        }
     236
     237        for (Set<Relation> duplicated : sameMembers.values()) {
     238            if (duplicated.size() > 1) {
     239                checkOrderAndTags(duplicated);
     240            }
     241        }
     242
     243        performGeometryTest(relations);
     244    }
     245
     246    private void performGeometryTest(MultiMap<RelationPair, Relation> relations) {
     247        // perform special test to find relations with different members but (possibly) same geometry
     248        // this test is rather speculative and works only with complete members
     249        for (Set<Relation> duplicated : relations.values()) {
    205250            if (duplicated.size() > 1) {
    206251                TestError testError = TestError.builder(this, Severity.ERROR, DUPLICATE_RELATION)
    207                         .message(tr("Duplicated relations"))
     252                        .message(tr(DUPLICATED_RELATIONS))
    208253                        .primitives(duplicated)
    209254                        .build();
    210                 errors.add(testError);
    211             }
    212         }
    213         relations = null;
    214         for (Set<OsmPrimitive> duplicated : relationsNoKeys.values()) {
    215             if (duplicated.size() > 1) {
    216                 TestError testError = TestError.builder(this, Severity.OTHER, SAME_RELATION)
    217                         .message(tr("Relations with same members"))
    218                         .primitives(duplicated)
    219                         .build();
    220                 errors.add(testError);
    221             }
    222         }
    223         relationsNoKeys = null;
    224         super.endTest();
     255                if (errors.stream().noneMatch(e -> e.isSimilar(testError))) {
     256                    errors.add(testError);
     257                }
     258            }
     259        }
     260    }
     261
     262    private static void addToRelations(MultiMap<RelationPair, Relation> relations, Relation r, boolean forceAdd) {
     263        if (r.isUsable() && !r.hasIncompleteMembers()) {
     264            RelationPair rKey = new RelationPair(r.getMembers(), cleanedKeys(r));
     265            if (forceAdd || (!relations.isEmpty() && !relations.containsKey(rKey)))
     266                relations.put(rKey, r);
     267        }
    225268    }
    226269
    227270    @Override
    228271    public void visit(Relation r) {
    229         if (!r.isUsable() || r.hasIncompleteMembers() || "tmc".equals(r.get("type")) || "TMC".equals(r.get("type"))
    230                || "destination_sign".equals(r.get("type")) || r.getMembers().isEmpty())
    231             return;
    232         List<RelationMember> rMembers = r.getMembers();
    233         Map<String, String> rkeys = r.getKeys();
    234         for (String key : ignoreKeys) {
    235             rkeys.remove(key);
    236         }
    237         RelationPair rKey = new RelationPair(rMembers, rkeys);
    238         relations.put(rKey, r);
    239         relationsNoKeys.put(rMembers, r);
     272        if (!r.isDeleted() && r.getMembersCount() > 0)
     273            visited.add(r);
     274    }
     275
     276    /**
     277     * Check a list of relations which are guaranteed to have the same members, possibly in different order
     278     * and possibly different tags.
     279     * @param sameMembers the list of relations
     280     */
     281    private void checkOrderAndTags(Set<Relation> sameMembers) {
     282        MultiMap<List<RelationMember>, Relation> sameOrder = new MultiMap<>();
     283        MultiMap<Map<String, String>, Relation> sameKeys = new MultiMap<>();
     284        for (Relation r : sameMembers) {
     285            sameOrder.put(r.getMembers(), r);
     286            sameKeys.put(cleanedKeys(r), r);
     287        }
     288        for (Set<Relation> duplicated : sameKeys.values()) {
     289            if (duplicated.size() > 1) {
     290                reportDuplicateKeys(duplicated);
     291            }
     292        }
     293        for (Set<Relation> duplicated : sameOrder.values()) {
     294            if (duplicated.size() > 1) {
     295                reportDuplicateMembers(duplicated);
     296            }
     297        }
     298        List<Relation> primitives = sameMembers.stream().filter(r -> !ignoredType(r)).collect(Collectors.toList());
     299        // report this collection if not already reported
     300        if (primitives.size() > 1 && errors.stream().noneMatch(e -> e.getPrimitives().containsAll(primitives))) {
     301            // same members, possibly different order
     302            TestError testError = TestError.builder(this, Severity.OTHER, SAME_RELATION)
     303                    .message(tr("Relations with same members"))
     304                    .primitives(primitives)
     305                    .build();
     306            errors.add(testError);
     307        }
     308    }
     309
     310    /**
     311     * Check collection of relations with the same keys and members, possibly in different order
     312     * @param duplicated collection of relations, caller must make sure that they have the same keys and members
     313     */
     314    private void reportDuplicateKeys(Collection<Relation> duplicated) {
     315        Relation first = duplicated.iterator().next();
     316        if (memberOrderMatters(first)) {
     317            List<Relation> toCheck = new ArrayList<>(duplicated);
     318            while (toCheck.size() > 1) {
     319                Relation ref = toCheck.iterator().next();
     320                List<Relation> same = toCheck.stream()
     321                        .filter(r -> r.getMembers().equals(ref.getMembers())).collect(Collectors.toList());
     322                if (same.size() > 1) {
     323                    // same members and keys, members in same order
     324                    TestError testError = TestError.builder(this, Severity.ERROR, DUPLICATE_RELATION)
     325                            .message(tr(DUPLICATED_RELATIONS))
     326                            .primitives(same)
     327                            .build();
     328                    errors.add(testError);
     329                }
     330                toCheck.removeAll(same);
     331            }
     332        } else {
     333            // same members and keys, possibly different order
     334            TestError testError = TestError.builder(this, Severity.ERROR, DUPLICATE_RELATION)
     335                    .message(tr(DUPLICATED_RELATIONS))
     336                    .primitives(duplicated)
     337                    .build();
     338            errors.add(testError);
     339        }
     340    }
     341
     342    /**
     343     * Report relations with the same member(s) in the same order, keys may be different
     344     * @param duplicated collection of relations with identical member list
     345     */
     346    private void reportDuplicateMembers(Set<Relation> duplicated) {
     347        List<Relation> primitives = duplicated.stream().filter(r -> !ignoredType(r)).collect(Collectors.toList());
     348        if (primitives.size() > 1 && errors.stream().noneMatch(e -> e.getPrimitives().containsAll(primitives))) {
     349            TestError testError = TestError.builder(this, Severity.OTHER, IDENTICAL_MEMBERLIST)
     350                    .message(tr("Identical members"))
     351                    .primitives(primitives)
     352                    .build();
     353            errors.add(testError);
     354        }
     355
     356    }
     357
     358    private static boolean memberOrderMatters(Relation r) {
     359        return r.hasTag("type", "route", "waterway"); // add more types?
     360    }
     361
     362    private static boolean ignoredType(Relation r) {
     363        return r.hasTag("type", "tmc", "TMC", "destination_sign"); // see r11783
     364    }
     365
     366    /** return tags of a primitive after removing all discardable tags
     367     *
     368     * @param p the primitive
     369     * @return map with cleaned tags
     370     */
     371    private static Map<String, String> cleanedKeys(OsmPrimitive p) {
     372        Map<String, String> cleaned = p.getKeys();
     373        for (String key : AbstractPrimitive.getDiscardableKeys()) {
     374            cleaned.remove(key);
     375        }
     376        return cleaned;
     377    }
     378
     379    /**
     380     *  Order members of given relation by type, unique id, and role.
     381     *  @param r the relation
     382     * @return sorted list of members
     383     */
     384    private static List<RelationMember> getSortedMembers(Relation r) {
     385        return r.getMembers().stream().sorted((m1, m2) -> {
     386            int d = m1.getMember().compareTo(m2.getMember());
     387            if (d != 0)
     388                return d;
     389            return m1.getRole().compareTo(m2.getRole());
     390        }).collect(Collectors.toList());
    240391    }
    241392
     
    301452    public boolean isFixable(TestError testError) {
    302453        if (!(testError.getTester() instanceof DuplicateRelation)
    303             || testError.getCode() == SAME_RELATION) return false;
     454            || testError.getCode() != DUPLICATE_RELATION) return false;
    304455
    305456        // We fix it only if there is no more than one relation that is relation member.
Note: See TracChangeset for help on using the changeset viewer.