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

Last change on this file since 12941 was 11783, checked in by stoecker, 7 years ago

get rid of many useless validator warnings

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