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

Last change on this file since 4153 was 4051, checked in by stoecker, 13 years ago

fix #5642 - patch by bilbo - improve validator checks

  • Property svn:eol-style set to native
File size: 8.5 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.HashSet;
9import java.util.LinkedList;
10import java.util.List;
11import java.util.Map;
12import java.util.Set;
13
14import org.openstreetmap.josm.command.ChangeCommand;
15import org.openstreetmap.josm.command.Command;
16import org.openstreetmap.josm.command.DeleteCommand;
17import org.openstreetmap.josm.command.SequenceCommand;
18import org.openstreetmap.josm.data.coor.LatLon;
19import org.openstreetmap.josm.data.osm.Node;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.data.osm.Relation;
22import org.openstreetmap.josm.data.osm.RelationMember;
23import org.openstreetmap.josm.data.osm.Way;
24import org.openstreetmap.josm.data.validation.Severity;
25import org.openstreetmap.josm.data.validation.Test;
26import org.openstreetmap.josm.data.validation.TestError;
27import org.openstreetmap.josm.gui.progress.ProgressMonitor;
28import org.openstreetmap.josm.tools.MultiMap;
29
30/**
31 * Tests if there are duplicate ways
32 */
33public class DuplicateWay extends Test
34{
35
36 private class WayPair {
37 public List<LatLon> coor;
38 public Map<String, String> keys;
39 public WayPair(List<LatLon> _coor, Map<String, String> _keys) {
40 coor=_coor;
41 keys=_keys;
42 }
43
44 @Override
45 public int hashCode() {
46 return coor.hashCode() + keys.hashCode();
47 }
48
49 @Override
50 public boolean equals(Object obj) {
51 if (!(obj instanceof WayPair))
52 return false;
53 WayPair wp = (WayPair) obj;
54 return wp.coor.equals(coor) && wp.keys.equals(keys);
55 }
56 }
57
58 private class WayPairNoTags {
59 public List<LatLon> coor;
60 public WayPairNoTags(List<LatLon> _coor) {
61 coor=_coor;
62 }
63 @Override
64 public int hashCode() {
65 return coor.hashCode();
66 }
67 @Override
68 public boolean equals(Object obj) {
69 if (!(obj instanceof WayPairNoTags)) return false;
70 WayPairNoTags wp = (WayPairNoTags) obj;
71 return wp.coor.equals(coor);
72 }
73 }
74
75 protected static int DUPLICATE_WAY = 1401;
76 protected static int SAME_WAY = 1402;
77
78 /** Bag of all ways */
79 MultiMap<WayPair, OsmPrimitive> ways;
80
81 /** Bag of all ways, regardless of tags */
82 MultiMap<WayPairNoTags, OsmPrimitive> waysNoTags;
83
84 /**
85 * Constructor
86 */
87 public DuplicateWay() {
88 super(tr("Duplicated ways."),
89 tr("This test checks that there are no ways with same node coordinates and optionally also same tags."));
90 }
91
92
93 @Override
94 public void startTest(ProgressMonitor monitor) {
95 super.startTest(monitor);
96 ways = new MultiMap<WayPair, OsmPrimitive>(1000);
97 waysNoTags = new MultiMap<WayPairNoTags, OsmPrimitive>(1000);
98 }
99
100 @Override
101 public void endTest() {
102 super.endTest();
103 for (Set<OsmPrimitive> duplicated : ways.values()) {
104 if (duplicated.size() > 1) {
105 TestError testError = new TestError(this, Severity.ERROR, tr("Duplicated ways"), DUPLICATE_WAY, duplicated);
106 errors.add(testError);
107 }
108 }
109
110 for(Set<OsmPrimitive> sameway : waysNoTags.values()) {
111 if( sameway.size() > 1) {
112 //Report error only if at least some tags are different, as otherwise the error was already reported as duplicated ways
113 Map<String, String> tags0=null;
114 boolean skip=true;
115
116 for(OsmPrimitive o : sameway) {
117 if (tags0==null) {
118 tags0=o.getKeys();
119 removeUninterestingKeys(tags0);
120 } else {
121 Map<String, String> tagsCmp=o.getKeys();
122 removeUninterestingKeys(tagsCmp);
123 if (!tagsCmp.equals(tags0)) {
124 skip=false;
125 break;
126 }
127 }
128 }
129 if (skip) continue;
130 TestError testError = new TestError(this, Severity.WARNING, tr("Ways with same position"), SAME_WAY, sameway);
131 errors.add(testError);
132 }
133 }
134 ways = null;
135 waysNoTags = null;
136 }
137
138 /**
139 * Remove uninteresting keys, like created_by to normalize the tags
140 */
141 public void removeUninterestingKeys(Map<String, String> wkeys) {
142 wkeys.remove("created_by");
143 }
144
145 @Override
146 public void visit(Way w) {
147 if (!w.isUsable())
148 return;
149 List<Node> wNodes = w.getNodes();
150 List<LatLon> wLat = new ArrayList<LatLon>(wNodes.size());
151 for (int i=0;i<wNodes.size();i++) {
152 wLat.add(wNodes.get(i).getCoor());
153 }
154 Map<String, String> wkeys = w.getKeys();
155 removeUninterestingKeys(wkeys);
156 WayPair wKey = new WayPair(wLat, wkeys);
157 ways.put(wKey, w);
158 WayPairNoTags wKeyN = new WayPairNoTags(wLat);
159 waysNoTags.put(wKeyN, w);
160 }
161
162 /**
163 * Fix the error by removing all but one instance of duplicate ways
164 */
165 @Override
166 public Command fixError(TestError testError) {
167 Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
168 HashSet<Way> ways = new HashSet<Way>();
169
170 for (OsmPrimitive osm : sel) {
171 if (osm instanceof Way) {
172 ways.add((Way)osm);
173 }
174 }
175
176 if (ways.size() < 2)
177 return null;
178
179 long idToKeep = 0;
180 Way wayToKeep = ways.iterator().next();
181 // Only one way will be kept - the one with lowest positive ID, if such exist
182 // or one "at random" if no such exists. Rest of the ways will be deleted
183 for (Way w: ways) {
184 if (!w.isNew()) {
185 if (idToKeep == 0 || w.getId() < idToKeep) {
186 idToKeep = w.getId();
187 wayToKeep = w;
188 }
189 }
190 }
191
192 // Find the way that is member of one or more relations. (If any)
193 Way wayWithRelations = null;
194 List<Relation> relations = null;
195 for (Way w : ways) {
196 List<Relation> rel = OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class);
197 if (!rel.isEmpty()) {
198 if (wayWithRelations != null)
199 throw new AssertionError("Cannot fix duplicate Ways: More than one way is relation member.");
200 wayWithRelations = w;
201 relations = rel;
202 }
203 }
204
205 Collection<Command> commands = new LinkedList<Command>();
206
207 // Fix relations.
208 if (wayWithRelations != null && wayToKeep != wayWithRelations) {
209 for (Relation rel : relations) {
210 Relation newRel = new Relation(rel);
211 for (int i = 0; i < newRel.getMembers().size(); ++i) {
212 RelationMember m = newRel.getMember(i);
213 if (wayWithRelations.equals(m.getMember())) {
214 newRel.setMember(i, new RelationMember(m.getRole(), wayToKeep));
215 }
216 }
217 commands.add(new ChangeCommand(rel, newRel));
218 }
219 }
220
221 //Delete all ways in the list
222 //Note: nodes are not deleted, these can be detected and deleted at next pass
223 ways.remove(wayToKeep);
224 commands.add(new DeleteCommand(ways));
225 return new SequenceCommand(tr("Delete duplicate ways"), commands);
226 }
227
228 @Override
229 public boolean isFixable(TestError testError) {
230 if (!(testError.getTester() instanceof DuplicateWay))
231 return false;
232
233 //Do not automatically fix same ways with different tags
234 if (testError.getCode()!=DUPLICATE_WAY) return false;
235
236 // We fix it only if there is no more than one way that is relation member.
237 Collection<? extends OsmPrimitive> sel = testError.getPrimitives();
238 HashSet<Way> ways = new HashSet<Way>();
239
240 for (OsmPrimitive osm : sel) {
241 if (osm instanceof Way) {
242 ways.add((Way)osm);
243 }
244 }
245
246 if (ways.size() < 2)
247 return false;
248
249 int waysWithRelations = 0;
250 for (Way w : ways) {
251 List<Relation> rel = OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class);
252 if (!rel.isEmpty()) {
253 ++waysWithRelations;
254 }
255 }
256 return (waysWithRelations <= 1);
257 }
258}
Note: See TracBrowser for help on using the repository browser.