source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/DuplicateWay.java@ 8836

Last change on this file since 8836 was 8836, checked in by Don-vip, 9 years ago

fix Checkstyle issues

  • Property svn:eol-style set to native
File size: 11.7 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.Collections;
9import java.util.HashSet;
10import java.util.LinkedList;
11import java.util.List;
12import java.util.Map;
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.Relation;
23import org.openstreetmap.josm.data.osm.RelationMember;
24import org.openstreetmap.josm.data.osm.Way;
25import org.openstreetmap.josm.data.validation.Severity;
26import org.openstreetmap.josm.data.validation.Test;
27import org.openstreetmap.josm.data.validation.TestError;
28import org.openstreetmap.josm.gui.progress.ProgressMonitor;
29import org.openstreetmap.josm.tools.MultiMap;
30
31/**
32 * Tests if there are duplicate ways
33 */
34public class DuplicateWay extends Test {
35
36 /**
37 * Class to store a way reduced to coordinates and keys. Essentially this is used to call the
38 * <code>equals{}</code> function.
39 */
40 private static class WayPair {
41 private final List<LatLon> coor;
42 private final Map<String, String> keys;
43
44 WayPair(List<LatLon> coor, Map<String, String> keys) {
45 this.coor = coor;
46 this.keys = keys;
47 }
48
49 @Override
50 public int hashCode() {
51 return coor.hashCode() + keys.hashCode();
52 }
53
54 @Override
55 public boolean equals(Object obj) {
56 if (!(obj instanceof WayPair))
57 return false;
58 WayPair wp = (WayPair) obj;
59 return wp.coor.equals(coor) && wp.keys.equals(keys);
60 }
61 }
62
63 /**
64 * Class to store a way reduced to coordinates. Essentially this is used to call the
65 * <code>equals{}</code> function.
66 */
67 private static class WayPairNoTags {
68 private final List<LatLon> coor;
69
70 WayPairNoTags(List<LatLon> coor) {
71 this.coor = coor;
72 }
73
74 @Override
75 public int hashCode() {
76 return coor.hashCode();
77 }
78
79 @Override
80 public boolean equals(Object obj) {
81 if (!(obj instanceof WayPairNoTags)) return false;
82 WayPairNoTags wp = (WayPairNoTags) obj;
83 return wp.coor.equals(coor);
84 }
85 }
86
87 /** Test identification for exactly identical ways (coordinates and tags). */
88 protected static final int DUPLICATE_WAY = 1401;
89 /** Test identification for identical ways (coordinates only). */
90 protected static final int SAME_WAY = 1402;
91
92 /** Bag of all ways */
93 private MultiMap<WayPair, OsmPrimitive> ways;
94
95 /** Bag of all ways, regardless of tags */
96 private MultiMap<WayPairNoTags, OsmPrimitive> waysNoTags;
97
98 /** Set of known hashcodes for list of coordinates **/
99 private Set<Integer> knownHashCodes;
100
101 /**
102 * Constructor
103 */
104 public DuplicateWay() {
105 super(tr("Duplicated ways"),
106 tr("This test checks that there are no ways with same node coordinates and optionally also same tags."));
107 }
108
109 @Override
110 public void startTest(ProgressMonitor monitor) {
111 super.startTest(monitor);
112 ways = new MultiMap<>(1000);
113 waysNoTags = new MultiMap<>(1000);
114 knownHashCodes = new HashSet<>(1000);
115 }
116
117 @Override
118 public void endTest() {
119 super.endTest();
120 for (Set<OsmPrimitive> duplicated : ways.values()) {
121 if (duplicated.size() > 1) {
122 TestError testError = new TestError(this, Severity.ERROR, tr("Duplicated ways"), DUPLICATE_WAY, duplicated);
123 errors.add(testError);
124 }
125 }
126
127 for (Set<OsmPrimitive> sameway : waysNoTags.values()) {
128 if (sameway.size() > 1) {
129 //Report error only if at least some tags are different, as otherwise the error was already reported as duplicated ways
130 Map<String, String> tags0 = null;
131 boolean skip = true;
132
133 for (OsmPrimitive o : sameway) {
134 if (tags0 == null) {
135 tags0 = o.getKeys();
136 removeUninterestingKeys(tags0);
137 } else {
138 Map<String, String> tagsCmp = o.getKeys();
139 removeUninterestingKeys(tagsCmp);
140 if (!tagsCmp.equals(tags0)) {
141 skip = false;
142 break;
143 }
144 }
145 }
146 if (skip) {
147 continue;
148 }
149 TestError testError = new TestError(this, Severity.WARNING, tr("Ways with same position"), SAME_WAY, sameway);
150 errors.add(testError);
151 }
152 }
153 ways = null;
154 waysNoTags = null;
155 knownHashCodes = null;
156 }
157
158 /**
159 * Remove uninteresting discardable keys to normalize the tags
160 * @param wkeys The tags of the way, obtained by {@code Way#getKeys}
161 */
162 public void removeUninterestingKeys(Map<String, String> wkeys) {
163 for (String key : OsmPrimitive.getDiscardableKeys()) {
164 wkeys.remove(key);
165 }
166 }
167
168 @Override
169 public void visit(Way w) {
170 if (!w.isUsable())
171 return;
172 List<LatLon> wLat = getOrderedNodes(w);
173 // If this way has not direction-dependant keys, make sure the list is ordered the same for all ways (fix #8015)
174 if (!w.hasDirectionKeys()) {
175 int hash = wLat.hashCode();
176 if (!knownHashCodes.contains(hash)) {
177 List<LatLon> reversedwLat = new ArrayList<>(wLat);
178 Collections.reverse(reversedwLat);
179 int reverseHash = reversedwLat.hashCode();
180 if (!knownHashCodes.contains(reverseHash)) {
181 // Neither hash or reversed hash is known, remember hash
182 knownHashCodes.add(hash);
183 } else {
184 // Reversed hash is known, use the reverse list then
185 wLat = reversedwLat;
186 }
187 }
188 }
189 Map<String, String> wkeys = w.getKeys();
190 removeUninterestingKeys(wkeys);
191 WayPair wKey = new WayPair(wLat, wkeys);
192 ways.put(wKey, w);
193 WayPairNoTags wKeyN = new WayPairNoTags(wLat);
194 waysNoTags.put(wKeyN, w);
195 }
196
197 /**
198 * Replies the ordered list of nodes of way w such as it is easier to find duplicated ways.
199 * In case of a closed way, build the list of lat/lon starting from the node with the lowest id
200 * to ensure this list will produce the same hashcode as the list obtained from another closed
201 * way with the same nodes, in the same order, but that does not start from the same node (fix #8008)
202 * @param w way
203 * @return the ordered list of nodes of way w such as it is easier to find duplicated ways
204 * @since 7721
205 */
206 public static List<LatLon> getOrderedNodes(Way w) {
207 List<Node> wNodes = w.getNodes(); // The original list of nodes for this way
208 List<Node> wNodesToUse = new ArrayList<>(wNodes.size()); // The list that will be considered for this test
209 if (w.isClosed()) {
210 int lowestIndex = 0;
211 long lowestNodeId = wNodes.get(0).getUniqueId();
212 for (int i = 1; i < wNodes.size(); i++) {
213 if (wNodes.get(i).getUniqueId() < lowestNodeId) {
214 lowestNodeId = wNodes.get(i).getUniqueId();
215 lowestIndex = i;
216 }
217 }
218 for (int i = lowestIndex; i < wNodes.size()-1; i++) {
219 wNodesToUse.add(wNodes.get(i));
220 }
221 for (int i = 0; i < lowestIndex; i++) {
222 wNodesToUse.add(wNodes.get(i));
223 }
224 wNodesToUse.add(wNodes.get(lowestIndex));
225 } else {
226 wNodesToUse.addAll(wNodes);
227 }
228 // Build the list of lat/lon
229 List<LatLon> wLat = new ArrayList<>(wNodesToUse.size());
230 for (Node node : wNodesToUse) {
231 wLat.add(node.getCoor());
232 }
233 return wLat;
234 }
235
236 /**
237 * Fix the error by removing all but one instance of duplicate ways
238 */
239 @Override
240 public Command fixError(TestError testError) {
241 Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
242 Set<Way> ways = new HashSet<>();
243
244 for (OsmPrimitive osm : sel) {
245 if (osm instanceof Way && !osm.isDeleted()) {
246 ways.add((Way) osm);
247 }
248 }
249
250 if (ways.size() < 2)
251 return null;
252
253 long idToKeep = 0;
254 Way wayToKeep = ways.iterator().next();
255 // Only one way will be kept - the one with lowest positive ID, if such exist
256 // or one "at random" if no such exists. Rest of the ways will be deleted
257 for (Way w: ways) {
258 if (!w.isNew() && (idToKeep == 0 || w.getId() < idToKeep)) {
259 idToKeep = w.getId();
260 wayToKeep = w;
261 }
262 }
263
264 // Find the way that is member of one or more relations. (If any)
265 Way wayWithRelations = null;
266 List<Relation> relations = null;
267 for (Way w : ways) {
268 List<Relation> rel = OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class);
269 if (!rel.isEmpty()) {
270 if (wayWithRelations != null)
271 throw new AssertionError("Cannot fix duplicate Ways: More than one way is relation member.");
272 wayWithRelations = w;
273 relations = rel;
274 }
275 }
276
277 Collection<Command> commands = new LinkedList<>();
278
279 // Fix relations.
280 if (wayWithRelations != null && wayToKeep != wayWithRelations) {
281 for (Relation rel : relations) {
282 Relation newRel = new Relation(rel);
283 for (int i = 0; i < newRel.getMembers().size(); ++i) {
284 RelationMember m = newRel.getMember(i);
285 if (wayWithRelations.equals(m.getMember())) {
286 newRel.setMember(i, new RelationMember(m.getRole(), wayToKeep));
287 }
288 }
289 commands.add(new ChangeCommand(rel, newRel));
290 }
291 }
292
293 //Delete all ways in the list
294 //Note: nodes are not deleted, these can be detected and deleted at next pass
295 ways.remove(wayToKeep);
296 commands.add(new DeleteCommand(ways));
297 return new SequenceCommand(tr("Delete duplicate ways"), commands);
298 }
299
300 @Override
301 public boolean isFixable(TestError testError) {
302 if (!(testError.getTester() instanceof DuplicateWay))
303 return false;
304
305 //Do not automatically fix same ways with different tags
306 if (testError.getCode() != DUPLICATE_WAY) return false;
307
308 // We fix it only if there is no more than one way that is relation member.
309 Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
310 Set<Way> ways = new HashSet<>();
311
312 for (OsmPrimitive osm : sel) {
313 if (osm instanceof Way) {
314 ways.add((Way) osm);
315 }
316 }
317
318 if (ways.size() < 2)
319 return false;
320
321 int waysWithRelations = 0;
322 for (Way w : ways) {
323 List<Relation> rel = OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class);
324 if (!rel.isEmpty()) {
325 ++waysWithRelations;
326 }
327 }
328 return waysWithRelations <= 1;
329 }
330}
Note: See TracBrowser for help on using the repository browser.