source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/DuplicateRelation.java@ 14468

Last change on this file since 14468 was 13809, checked in by Don-vip, 6 years ago

define InterestingTags functions in IPrimitive

  • Property svn:eol-style set to native
File size: 11.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.HashSet;
9import java.util.LinkedList;
10import java.util.List;
11import java.util.Map;
12import java.util.Objects;
13import java.util.Set;
14
15import org.openstreetmap.josm.command.ChangeCommand;
16import org.openstreetmap.josm.command.Command;
17import org.openstreetmap.josm.command.DeleteCommand;
18import org.openstreetmap.josm.command.SequenceCommand;
19import org.openstreetmap.josm.data.coor.LatLon;
20import org.openstreetmap.josm.data.osm.AbstractPrimitive;
21import org.openstreetmap.josm.data.osm.Node;
22import org.openstreetmap.josm.data.osm.OsmPrimitive;
23import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
24import org.openstreetmap.josm.data.osm.Relation;
25import org.openstreetmap.josm.data.osm.RelationMember;
26import org.openstreetmap.josm.data.osm.Way;
27import org.openstreetmap.josm.data.validation.Severity;
28import org.openstreetmap.josm.data.validation.Test;
29import org.openstreetmap.josm.data.validation.TestError;
30import org.openstreetmap.josm.gui.progress.ProgressMonitor;
31import org.openstreetmap.josm.tools.MultiMap;
32
33/**
34 * Tests if there are duplicate relations
35 */
36public class DuplicateRelation extends Test {
37
38 /**
39 * Class to store one relation members and information about it
40 */
41 public static class RelMember {
42 /** Role of the relation member */
43 private final String role;
44
45 /** Type of the relation member */
46 private final OsmPrimitiveType type;
47
48 /** Tags of the relation member */
49 private Map<String, String> tags;
50
51 /** Coordinates of the relation member */
52 private List<LatLon> coor;
53
54 /** ID of the relation member in case it is a {@link Relation} */
55 private long relId;
56
57 @Override
58 public int hashCode() {
59 return Objects.hash(role, type, tags, coor, relId);
60 }
61
62 @Override
63 public boolean equals(Object obj) {
64 if (this == obj) return true;
65 if (obj == null || getClass() != obj.getClass()) return false;
66 RelMember relMember = (RelMember) obj;
67 return relId == relMember.relId &&
68 type == relMember.type &&
69 Objects.equals(role, relMember.role) &&
70 Objects.equals(tags, relMember.tags) &&
71 Objects.equals(coor, relMember.coor);
72 }
73
74 /** Extract and store relation information based on the relation member
75 * @param src The relation member to store information about
76 */
77 public RelMember(RelationMember src) {
78 role = src.getRole();
79 type = src.getType();
80 relId = 0;
81 coor = new ArrayList<>();
82
83 if (src.isNode()) {
84 Node r = src.getNode();
85 tags = r.getKeys();
86 coor = new ArrayList<>(1);
87 coor.add(r.getCoor());
88 }
89 if (src.isWay()) {
90 Way r = src.getWay();
91 tags = r.getKeys();
92 List<Node> wNodes = r.getNodes();
93 coor = new ArrayList<>(wNodes.size());
94 for (Node wNode : wNodes) {
95 coor.add(wNode.getCoor());
96 }
97 }
98 if (src.isRelation()) {
99 Relation r = src.getRelation();
100 tags = r.getKeys();
101 relId = r.getId();
102 coor = new ArrayList<>();
103 }
104 }
105 }
106
107 /**
108 * Class to store relation members
109 */
110 private static class RelationMembers {
111 /** List of member objects of the relation */
112 private final List<RelMember> members;
113
114 /** Store relation information
115 * @param members The list of relation members
116 */
117 RelationMembers(List<RelationMember> members) {
118 this.members = new ArrayList<>(members.size());
119 for (RelationMember member : members) {
120 this.members.add(new RelMember(member));
121 }
122 }
123
124 @Override
125 public int hashCode() {
126 return Objects.hash(members);
127 }
128
129 @Override
130 public boolean equals(Object obj) {
131 if (this == obj) return true;
132 if (obj == null || getClass() != obj.getClass()) return false;
133 RelationMembers that = (RelationMembers) obj;
134 return Objects.equals(members, that.members);
135 }
136 }
137
138 /**
139 * Class to store relation data (keys are usually cleanup and may not be equal to original relation)
140 */
141 private static class RelationPair {
142 /** Member objects of the relation */
143 private final RelationMembers members;
144 /** Tags of the relation */
145 private final Map<String, String> keys;
146
147 /** Store relation information
148 * @param members The list of relation members
149 * @param keys The set of tags of the relation
150 */
151 RelationPair(List<RelationMember> members, Map<String, String> keys) {
152 this.members = new RelationMembers(members);
153 this.keys = keys;
154 }
155
156 @Override
157 public int hashCode() {
158 return Objects.hash(members, keys);
159 }
160
161 @Override
162 public boolean equals(Object obj) {
163 if (this == obj) return true;
164 if (obj == null || getClass() != obj.getClass()) return false;
165 RelationPair that = (RelationPair) obj;
166 return Objects.equals(members, that.members) &&
167 Objects.equals(keys, that.keys);
168 }
169 }
170
171 /** Code number of completely duplicated relation error */
172 protected static final int DUPLICATE_RELATION = 1901;
173
174 /** Code number of relation with same members error */
175 protected static final int SAME_RELATION = 1902;
176
177 /** MultiMap of all relations */
178 private MultiMap<RelationPair, OsmPrimitive> relations;
179
180 /** MultiMap of all relations, regardless of keys */
181 private MultiMap<List<RelationMember>, OsmPrimitive> relationsNoKeys;
182
183 /** List of keys without useful information */
184 private final Set<String> ignoreKeys = new HashSet<>(AbstractPrimitive.getUninterestingKeys());
185
186 /**
187 * Default constructor
188 */
189 public DuplicateRelation() {
190 super(tr("Duplicated relations"),
191 tr("This test checks that there are no relations with same tags and same members with same roles."));
192 }
193
194 @Override
195 public void startTest(ProgressMonitor monitor) {
196 super.startTest(monitor);
197 relations = new MultiMap<>(1000);
198 relationsNoKeys = new MultiMap<>(1000);
199 }
200
201 @Override
202 public void endTest() {
203 super.endTest();
204 for (Set<OsmPrimitive> duplicated : relations.values()) {
205 if (duplicated.size() > 1) {
206 TestError testError = TestError.builder(this, Severity.ERROR, DUPLICATE_RELATION)
207 .message(tr("Duplicated relations"))
208 .primitives(duplicated)
209 .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.WARNING, SAME_RELATION)
217 .message(tr("Relations with same members"))
218 .primitives(duplicated)
219 .build();
220 errors.add(testError);
221 }
222 }
223 relationsNoKeys = null;
224 }
225
226 @Override
227 public void visit(Relation r) {
228 if (!r.isUsable() || r.hasIncompleteMembers() || "tmc".equals(r.get("type")) || "TMC".equals(r.get("type")))
229 return;
230 List<RelationMember> rMembers = r.getMembers();
231 Map<String, String> rkeys = r.getKeys();
232 for (String key : ignoreKeys) {
233 rkeys.remove(key);
234 }
235 RelationPair rKey = new RelationPair(rMembers, rkeys);
236 relations.put(rKey, r);
237 relationsNoKeys.put(rMembers, r);
238 }
239
240 /**
241 * Fix the error by removing all but one instance of duplicate relations
242 * @param testError The error to fix, must be of type {@link #DUPLICATE_RELATION}
243 */
244 @Override
245 public Command fixError(TestError testError) {
246 if (testError.getCode() == SAME_RELATION) return null;
247 Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
248 Set<Relation> relFix = new HashSet<>();
249
250 for (OsmPrimitive osm : sel) {
251 if (osm instanceof Relation && !osm.isDeleted()) {
252 relFix.add((Relation) osm);
253 }
254 }
255
256 if (relFix.size() < 2)
257 return null;
258
259 long idToKeep = 0;
260 Relation relationToKeep = relFix.iterator().next();
261 // Find the relation that is member of one or more relations. (If any)
262 Relation relationWithRelations = null;
263 List<Relation> relRef = null;
264 for (Relation w : relFix) {
265 List<Relation> rel = OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class);
266 if (!rel.isEmpty()) {
267 if (relationWithRelations != null)
268 throw new AssertionError("Cannot fix duplicate relations: More than one relation is member of another relation.");
269 relationWithRelations = w;
270 relRef = rel;
271 }
272 // Only one relation will be kept - the one with lowest positive ID, if such exist
273 // or one "at random" if no such exists. Rest of the relations will be deleted
274 if (!w.isNew() && (idToKeep == 0 || w.getId() < idToKeep)) {
275 idToKeep = w.getId();
276 relationToKeep = w;
277 }
278 }
279
280 Collection<Command> commands = new LinkedList<>();
281
282 // Fix relations.
283 if (relationWithRelations != null && relRef != null && relationToKeep != relationWithRelations) {
284 for (Relation rel : relRef) {
285 Relation newRel = new Relation(rel);
286 for (int i = 0; i < newRel.getMembers().size(); ++i) {
287 RelationMember m = newRel.getMember(i);
288 if (relationWithRelations.equals(m.getMember())) {
289 newRel.setMember(i, new RelationMember(m.getRole(), relationToKeep));
290 }
291 }
292 commands.add(new ChangeCommand(rel, newRel));
293 }
294 }
295
296 // Delete all relations in the list
297 relFix.remove(relationToKeep);
298 commands.add(new DeleteCommand(relFix));
299 return new SequenceCommand(tr("Delete duplicate relations"), commands);
300 }
301
302 @Override
303 public boolean isFixable(TestError testError) {
304 if (!(testError.getTester() instanceof DuplicateRelation)
305 || testError.getCode() == SAME_RELATION) return false;
306
307 // We fix it only if there is no more than one relation that is relation member.
308 Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
309 Set<Relation> rels = new HashSet<>();
310
311 for (OsmPrimitive osm : sel) {
312 if (osm instanceof Relation) {
313 rels.add((Relation) osm);
314 }
315 }
316
317 if (rels.size() < 2)
318 return false;
319
320 int relationsWithRelations = 0;
321 for (Relation w : rels) {
322 List<Relation> rel = OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class);
323 if (!rel.isEmpty()) {
324 ++relationsWithRelations;
325 }
326 }
327 return relationsWithRelations <= 1;
328 }
329}
Note: See TracBrowser for help on using the repository browser.