source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java@ 7881

Last change on this file since 7881 was 7523, checked in by Don-vip, 10 years ago

fix #10398 - clarify warning about missing public_transport:version tag in route relations

  • Property svn:eol-style set to native
File size: 10.9 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.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.text.MessageFormat;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.HashMap;
11import java.util.HashSet;
12import java.util.LinkedList;
13import java.util.List;
14import java.util.Set;
15
16import org.openstreetmap.josm.command.Command;
17import org.openstreetmap.josm.command.DeleteCommand;
18import org.openstreetmap.josm.data.osm.Node;
19import org.openstreetmap.josm.data.osm.OsmPrimitive;
20import org.openstreetmap.josm.data.osm.Relation;
21import org.openstreetmap.josm.data.osm.RelationMember;
22import org.openstreetmap.josm.data.osm.Way;
23import org.openstreetmap.josm.data.validation.Severity;
24import org.openstreetmap.josm.data.validation.Test;
25import org.openstreetmap.josm.data.validation.TestError;
26import org.openstreetmap.josm.gui.tagging.TaggingPreset;
27import org.openstreetmap.josm.gui.tagging.TaggingPresetItem;
28import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Key;
29import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Role;
30import org.openstreetmap.josm.gui.tagging.TaggingPresetItems.Roles;
31import org.openstreetmap.josm.gui.tagging.TaggingPresetType;
32import org.openstreetmap.josm.gui.tagging.TaggingPresets;
33
34/**
35 * Check for wrong relations.
36 * @since 3669
37 */
38public class RelationChecker extends Test {
39
40 protected static final int ROLE_UNKNOWN = 1701;
41 protected static final int ROLE_EMPTY = 1702;
42 protected static final int WRONG_TYPE = 1703;
43 protected static final int HIGH_COUNT = 1704;
44 protected static final int LOW_COUNT = 1705;
45 protected static final int ROLE_MISSING = 1706;
46 protected static final int RELATION_UNKNOWN = 1707;
47 protected static final int RELATION_EMPTY = 1708;
48
49 /**
50 * Error message used to group errors related to role problems.
51 * @since 6731
52 */
53 public static final String ROLE_VERIF_PROBLEM_MSG = tr("Role verification problem");
54
55 /**
56 * Constructor
57 */
58 public RelationChecker() {
59 super(tr("Relation checker"),
60 tr("Checks for errors in relations."));
61 }
62
63 @Override
64 public void initialize() {
65 initializePresets();
66 }
67
68 private static Collection<TaggingPreset> relationpresets = new LinkedList<>();
69
70 /**
71 * Reads the presets data.
72 */
73 public static synchronized void initializePresets() {
74 if (!relationpresets.isEmpty()) {
75 // the presets have already been initialized
76 return;
77 }
78 for (TaggingPreset p : TaggingPresets.getTaggingPresets()) {
79 for (TaggingPresetItem i : p.data) {
80 if (i instanceof Roles) {
81 relationpresets.add(p);
82 break;
83 }
84 }
85 }
86 }
87
88 private static class RoleInfo {
89 private int total = 0;
90 private Collection<Node> nodes = new LinkedList<>();
91 private Collection<Way> ways = new LinkedList<>();
92 private Collection<Way> openways = new LinkedList<>();
93 private Collection<Relation> relations = new LinkedList<>();
94 }
95
96 @Override
97 public void visit(Relation n) {
98 LinkedList<Role> allroles = buildAllRoles(n);
99 if (allroles.isEmpty() && n.hasTag("type", "route")
100 && n.hasTag("route", "train", "subway", "monorail", "tram", "bus", "trolleybus", "aerialway", "ferry")) {
101 errors.add(new TestError(this, Severity.WARNING,
102 tr("Route scheme is unspecified. Add {0} ({1}=public_transport; {2}=legacy)", "public_transport:version", "2", "1"),
103 RELATION_UNKNOWN, n));
104 } else if (allroles.isEmpty()) {
105 errors.add(new TestError(this, Severity.WARNING, tr("Relation type is unknown"), RELATION_UNKNOWN, n));
106 }
107
108 HashMap<String, RoleInfo> map = buildRoleInfoMap(n);
109 if (map.isEmpty()) {
110 errors.add(new TestError(this, Severity.ERROR, tr("Relation is empty"), RELATION_EMPTY, n));
111 } else if (!allroles.isEmpty()) {
112 checkRoles(n, allroles, map);
113 }
114 }
115
116 private HashMap<String, RoleInfo> buildRoleInfoMap(Relation n) {
117 HashMap<String,RoleInfo> map = new HashMap<>();
118 for (RelationMember m : n.getMembers()) {
119 String role = m.getRole();
120 RoleInfo ri = map.get(role);
121 if (ri == null) {
122 ri = new RoleInfo();
123 }
124 ri.total++;
125 if (m.isRelation()) {
126 ri.relations.add(m.getRelation());
127 } else if(m.isWay()) {
128 ri.ways.add(m.getWay());
129 if (!m.getWay().isClosed()) {
130 ri.openways.add(m.getWay());
131 }
132 }
133 else if (m.isNode()) {
134 ri.nodes.add(m.getNode());
135 }
136 map.put(role, ri);
137 }
138 return map;
139 }
140
141 private LinkedList<Role> buildAllRoles(Relation n) {
142 LinkedList<Role> allroles = new LinkedList<>();
143 for (TaggingPreset p : relationpresets) {
144 boolean matches = true;
145 Roles r = null;
146 for (TaggingPresetItem i : p.data) {
147 if (i instanceof Key) {
148 Key k = (Key) i;
149 if (!k.value.equals(n.get(k.key))) {
150 matches = false;
151 break;
152 }
153 } else if (i instanceof Roles) {
154 r = (Roles) i;
155 }
156 }
157 if (matches && r != null) {
158 allroles.addAll(r.roles);
159 }
160 }
161 return allroles;
162 }
163
164 private void checkRoles(Relation n, LinkedList<Role> allroles, HashMap<String, RoleInfo> map) {
165 List<String> done = new LinkedList<>();
166 // Remove empty roles if several exist (like in route=hiking, see #9844)
167 List<Role> emptyRoles = new LinkedList<>();
168 for (Role r : allroles) {
169 if ("".equals(r.key)) {
170 emptyRoles.add(r);
171 }
172 }
173 if (emptyRoles.size() > 1) {
174 allroles.removeAll(emptyRoles);
175 }
176 for (Role r : allroles) {
177 done.add(r.key);
178 String keyname = r.key;
179 if ("".equals(keyname)) {
180 keyname = tr("<empty>");
181 }
182 RoleInfo ri = map.get(r.key);
183 checkRoleCounts(n, r, keyname, ri);
184 if (ri != null) {
185 if (r.types != null) {
186 checkRoleTypes(n, r, keyname, ri);
187 }
188 if (r.memberExpression != null) {
189 checkRoleMemberExpressions(n, r, keyname, ri);
190 }
191 }
192 }
193 for (String key : map.keySet()) {
194 if (!done.contains(key)) {
195 if (key.length() > 0) {
196 String s = marktr("Role {0} unknown");
197 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
198 tr(s, key), MessageFormat.format(s, key), ROLE_UNKNOWN, n));
199 } else {
200 String s = marktr("Empty role found");
201 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
202 tr(s), s, ROLE_EMPTY, n));
203 }
204 }
205 }
206 }
207
208 private void checkRoleMemberExpressions(Relation n, Role r, String keyname, RoleInfo ri) {
209 Set<OsmPrimitive> notMatching = new HashSet<>();
210 Collection<OsmPrimitive> allPrimitives = new ArrayList<>();
211 allPrimitives.addAll(ri.nodes);
212 allPrimitives.addAll(ri.ways);
213 allPrimitives.addAll(ri.relations);
214 for (OsmPrimitive p : allPrimitives) {
215 if (p.isUsable() && !r.memberExpression.match(p)) {
216 notMatching.add(p);
217 }
218 }
219 if (!notMatching.isEmpty()) {
220 String s = marktr("Member for role ''{0}'' does not match ''{1}''");
221 LinkedList<OsmPrimitive> highlight = new LinkedList<>(notMatching);
222 highlight.addFirst(n);
223 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
224 tr(s, keyname, r.memberExpression), MessageFormat.format(s, keyname, r.memberExpression), WRONG_TYPE,
225 highlight, notMatching));
226 }
227 }
228
229 private void checkRoleTypes(Relation n, Role r, String keyname, RoleInfo ri) {
230 Set<OsmPrimitive> wrongTypes = new HashSet<>();
231 if (!r.types.contains(TaggingPresetType.WAY)) {
232 wrongTypes.addAll(r.types.contains(TaggingPresetType.CLOSEDWAY) ? ri.openways : ri.ways);
233 }
234 if (!r.types.contains(TaggingPresetType.NODE)) {
235 wrongTypes.addAll(ri.nodes);
236 }
237 if (!r.types.contains(TaggingPresetType.RELATION)) {
238 wrongTypes.addAll(ri.relations);
239 }
240 if (!wrongTypes.isEmpty()) {
241 String s = marktr("Member for role {0} of wrong type");
242 LinkedList<OsmPrimitive> highlight = new LinkedList<>(wrongTypes);
243 highlight.addFirst(n);
244 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
245 tr(s, keyname), MessageFormat.format(s, keyname), WRONG_TYPE,
246 highlight, wrongTypes));
247 }
248 }
249
250 private void checkRoleCounts(Relation n, Role r, String keyname, RoleInfo ri) {
251 long count = (ri == null) ? 0 : ri.total;
252 long vc = r.getValidCount(count);
253 if (count != vc) {
254 if (count == 0) {
255 String s = marktr("Role {0} missing");
256 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
257 tr(s, keyname), MessageFormat.format(s, keyname), ROLE_MISSING, n));
258 }
259 else if (vc > count) {
260 String s = marktr("Number of {0} roles too low ({1})");
261 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
262 tr(s, keyname, count), MessageFormat.format(s, keyname, count), LOW_COUNT, n));
263 } else {
264 String s = marktr("Number of {0} roles too high ({1})");
265 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
266 tr(s, keyname, count), MessageFormat.format(s, keyname, count), HIGH_COUNT, n));
267 }
268 }
269 }
270
271 @Override
272 public Command fixError(TestError testError) {
273 if (isFixable(testError)) {
274 return new DeleteCommand(testError.getPrimitives());
275 }
276 return null;
277 }
278
279 @Override
280 public boolean isFixable(TestError testError) {
281 Collection<? extends OsmPrimitive> primitives = testError.getPrimitives();
282 return testError.getCode() == RELATION_EMPTY && !primitives.isEmpty() && primitives.iterator().next().isNew();
283 }
284}
Note: See TracBrowser for help on using the repository browser.