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

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

code style - Useless parentheses around expressions should be removed to prevent any misunderstanding

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