Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/data/validation/tests/DuplicateRelation.java
r17476 r18976 2 2 package org.openstreetmap.josm.data.validation.tests; 3 3 4 import static org.openstreetmap.josm.tools.I18n.marktr; 4 5 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 6 7 import java.util.ArrayList; 7 8 import java.util.Collection; 8 import java.util.HashSet;9 9 import java.util.LinkedList; 10 10 import java.util.List; … … 20 20 import org.openstreetmap.josm.data.coor.LatLon; 21 21 import org.openstreetmap.josm.data.osm.AbstractPrimitive; 22 import org.openstreetmap.josm.data.osm.DataSet; 22 23 import org.openstreetmap.josm.data.osm.Node; 23 24 import org.openstreetmap.josm.data.osm.OsmPrimitive; … … 37 38 public class DuplicateRelation extends Test { 38 39 40 private static final String DUPLICATED_RELATIONS = marktr("Duplicated relations"); 41 39 42 /** 40 43 * Class to store one relation members and information about it … … 111 114 private static class RelationMembers { 112 115 /** Set of member objects of the relation */ 113 private final Set<RelMember> members;116 private final List<RelMember> members; 114 117 115 118 /** Store relation information … … 117 120 */ 118 121 RelationMembers(List<RelationMember> members) { 119 this.members = new HashSet<>(members.size());122 this.members = new ArrayList<>(members.size()); 120 123 for (RelationMember member : members) { 121 124 this.members.add(new RelMember(member)); … … 176 179 protected static final int SAME_RELATION = 1902; 177 180 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; 186 187 187 188 /** … … 189 190 */ 190 191 public DuplicateRelation() { 191 super(tr( "Duplicated relations"),192 super(tr(DUPLICATED_RELATIONS), 192 193 tr("This test checks that there are no relations with same tags and same members with same roles.")); 193 194 } … … 196 197 public void startTest(ProgressMonitor monitor) { 197 198 super.startTest(monitor); 198 relations= newMultiMap<>(1000);199 relationsNoKeys = new MultiMap<>(1000); 199 visited = new ArrayList<>(); 200 200 201 } 201 202 202 203 @Override 203 204 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()) { 205 250 if (duplicated.size() > 1) { 206 251 TestError testError = TestError.builder(this, Severity.ERROR, DUPLICATE_RELATION) 207 .message(tr( "Duplicated relations"))252 .message(tr(DUPLICATED_RELATIONS)) 208 253 .primitives(duplicated) 209 254 .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 } 225 268 } 226 269 227 270 @Override 228 271 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()); 240 391 } 241 392 … … 301 452 public boolean isFixable(TestError testError) { 302 453 if (!(testError.getTester() instanceof DuplicateRelation) 303 || testError.getCode() == SAME_RELATION) return false;454 || testError.getCode() != DUPLICATE_RELATION) return false; 304 455 305 456 // We fix it only if there is no more than one relation that is relation member. -
trunk/test/unit/org/openstreetmap/josm/data/validation/tests/DuplicateRelationTest.java
r18037 r18976 4 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 5 6 import org.junit.jupiter.api.Test; 6 7 import org.openstreetmap.josm.TestUtils; 7 8 import org.openstreetmap.josm.data.coor.LatLon; … … 12 13 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 13 14 import org.openstreetmap.josm.testutils.annotations.BasicPreferences; 14 15 import org.junit.jupiter.api.Test;16 15 17 16 /** … … 45 44 assertEquals(expected.code, error.getCode()); 46 45 assertEquals(expected.fixable, error.isFixable()); 46 if (error.isFixable()) 47 error.getFix(); 47 48 } 48 49 } … … 64 65 void testDuplicateRelationNoTags() { 65 66 doTest("", "", 66 new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true) ,67 new ExpectedResult(DuplicateRelation.SAME_RELATION, false));67 new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true) 68 ); 68 69 } 69 70 … … 74 75 void testDuplicateRelationSameTags() { 75 76 doTest("type=boundary", "type=boundary", 76 new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true) ,77 new ExpectedResult(DuplicateRelation.SAME_RELATION, false));77 new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true) 78 ); 78 79 } 79 80 … … 84 85 void testDuplicateRelationDifferentTags() { 85 86 doTest("type=boundary", "type=multipolygon", 86 new ExpectedResult(DuplicateRelation.SAME_RELATION, false)); 87 new ExpectedResult(DuplicateRelation.IDENTICAL_MEMBERLIST, false)); 88 } 89 90 /** 91 * Test of duplicate "tmc" relation, should not be ignored 92 */ 93 @Test 94 void testTMCRelation1() { 95 doTest("type=tmc t1=v1", "type=tmc t1=v1", 96 new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true)); 97 } 98 99 /** 100 * Test of "tmc" relation with equal members but different tags, should be ignored 101 */ 102 @Test 103 void testTMCRelation2() { 104 doTest("type=tmc t1=v1", "type=tmc t1=v2"); 105 } 106 107 /** 108 * Test with incomplete members 109 */ 110 @Test 111 void testIncomplete() { 112 DataSet ds = new DataSet(); 113 114 Node a = new Node(1234); 115 ds.addPrimitive(a); 116 ds.addPrimitive(TestUtils.newRelation("type=multipolygon", new RelationMember(null, a))); 117 ds.addPrimitive(TestUtils.newRelation("type=multipolygon", new RelationMember(null, a))); 118 performTest(ds, new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true)); 119 } 120 121 /** 122 * Test with different order of members, order doesn't count 123 */ 124 @Test 125 void testMemberOrder1() { 126 DataSet ds = new DataSet(); 127 128 Node a = new Node(1); 129 Node b = new Node(2); 130 ds.addPrimitive(a); 131 ds.addPrimitive(b); 132 ds.addPrimitive(TestUtils.newRelation("type=multipolygon", new RelationMember(null, a), new RelationMember(null, b))); 133 ds.addPrimitive(TestUtils.newRelation("type=multipolygon", new RelationMember(null, b), new RelationMember(null, a))); 134 performTest(ds, new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true)); 135 } 136 137 /** 138 * Test with different order of members, order counts 139 */ 140 @Test 141 void testMemberOrder2() { 142 DataSet ds = new DataSet(); 143 144 Node a = new Node(1); 145 a.setCoor(new LatLon(10.0, 5.0)); 146 Node b = new Node(2); 147 b.setCoor(new LatLon(10.0, 6.0)); 148 ds.addPrimitive(a); 149 ds.addPrimitive(b); 150 ds.addPrimitive(TestUtils.newRelation("type=route", new RelationMember(null, a), new RelationMember(null, b))); 151 ds.addPrimitive(TestUtils.newRelation("type=route", new RelationMember(null, b), new RelationMember(null, a))); 152 performTest(ds, new ExpectedResult(DuplicateRelation.SAME_RELATION, false)); 153 } 154 155 /** 156 * Test with different order of members, one is duplicated, order doesn't matter 157 */ 158 @Test 159 void testMemberOrder3() { 160 DataSet ds = new DataSet(); 161 162 Node a = new Node(1); 163 Node b = new Node(2); 164 ds.addPrimitive(a); 165 ds.addPrimitive(b); 166 ds.addPrimitive(TestUtils.newRelation("type=restriction", new RelationMember(null, a), 167 new RelationMember(null, b), new RelationMember(null, a))); 168 ds.addPrimitive(TestUtils.newRelation("type=restriction", new RelationMember(null, b), 169 new RelationMember(null, a), new RelationMember(null, a))); 170 performTest(ds, new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true)); 171 } 172 173 /** 174 * Test with different order of members, one is duplicated in one of the relations 175 */ 176 @Test 177 void testMemberOrder4() { 178 DataSet ds = new DataSet(); 179 Node a = new Node(new LatLon(10.0, 5.0)); 180 Node b = new Node(new LatLon(10.0, 6.0)); 181 ds.addPrimitive(a); 182 ds.addPrimitive(b); 183 ds.addPrimitive(TestUtils.newRelation("", new RelationMember(null, a), new RelationMember(null, b))); 184 ds.addPrimitive(TestUtils.newRelation("", new RelationMember(null, b), new RelationMember(null, a), new RelationMember(null, b))); 185 performTest(ds); 186 } 187 188 /** 189 * Test with two relations where members are different but geometry is equal. 190 */ 191 @Test 192 void testImport() { 193 DataSet ds = new DataSet(); 194 Node a = new Node(1234, 1); 195 a.setCoor(new LatLon(10.0, 5.0)); 196 197 Node b = new Node(new LatLon(10.0, 5.0)); 198 199 ds.addPrimitive(a); 200 ds.addPrimitive(b); 201 ds.addPrimitive(TestUtils.newRelation("type=multipolygon", new RelationMember(null, a))); 202 ds.addPrimitive(TestUtils.newRelation("type=multipolygon", new RelationMember(null, b))); 203 performTest(ds, new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true)); 87 204 } 88 205 }
Note:
See TracChangeset
for help on using the changeset viewer.