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

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

refactor tagging presets to allow presets-dependent unit tests to update them correctly at runtime

  • Property svn:eol-style set to native
File size: 10.8 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("This plugin 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 */
74 public void initializePresets() {
75 for (TaggingPreset p : TaggingPresets.getTaggingPresets()) {
76 for (TaggingPresetItem i : p.data) {
77 if (i instanceof Roles) {
78 relationpresets.add(p);
79 break;
80 }
81 }
82 }
83 }
84
85 private static class RoleInfo {
86 private int total = 0;
87 private Collection<Node> nodes = new LinkedList<>();
88 private Collection<Way> ways = new LinkedList<>();
89 private Collection<Way> openways = new LinkedList<>();
90 private Collection<Relation> relations = new LinkedList<>();
91 }
92
93 @Override
94 public void visit(Relation n) {
95 LinkedList<Role> allroles = buildAllRoles(n);
96 if (allroles.isEmpty() && n.hasTag("type", "route")
97 && n.hasTag("route", "train", "subway", "monorail", "tram", "bus", "trolleybus", "aerialway", "ferry")) {
98 errors.add(new TestError(this, Severity.WARNING,
99 tr("Route scheme (public_transport or legacy) is unspecified. Add {0}", "public_transport:version"),
100 RELATION_UNKNOWN, n));
101 } else if (allroles.isEmpty()) {
102 errors.add(new TestError(this, Severity.WARNING, tr("Relation type is unknown"), RELATION_UNKNOWN, n));
103 } else {
104 HashMap<String, RoleInfo> map = buildRoleInfoMap(n);
105 if (map.isEmpty()) {
106 errors.add(new TestError(this, Severity.ERROR, tr("Relation is empty"), RELATION_EMPTY, n));
107 } else {
108 checkRoles(n, allroles, map);
109 }
110 }
111 }
112
113 private HashMap<String, RoleInfo> buildRoleInfoMap(Relation n) {
114 HashMap<String,RoleInfo> map = new HashMap<>();
115 for (RelationMember m : n.getMembers()) {
116 String role = m.getRole();
117 RoleInfo ri = map.get(role);
118 if (ri == null) {
119 ri = new RoleInfo();
120 }
121 ri.total++;
122 if (m.isRelation()) {
123 ri.relations.add(m.getRelation());
124 } else if(m.isWay()) {
125 ri.ways.add(m.getWay());
126 if (!m.getWay().isClosed()) {
127 ri.openways.add(m.getWay());
128 }
129 }
130 else if (m.isNode()) {
131 ri.nodes.add(m.getNode());
132 }
133 map.put(role, ri);
134 }
135 return map;
136 }
137
138 private LinkedList<Role> buildAllRoles(Relation n) {
139 LinkedList<Role> allroles = new LinkedList<>();
140 for (TaggingPreset p : relationpresets) {
141 boolean matches = true;
142 Roles r = null;
143 for (TaggingPresetItem i : p.data) {
144 if (i instanceof Key) {
145 Key k = (Key) i;
146 if (!k.value.equals(n.get(k.key))) {
147 matches = false;
148 break;
149 }
150 } else if (i instanceof Roles) {
151 r = (Roles) i;
152 }
153 }
154 if (matches && r != null) {
155 allroles.addAll(r.roles);
156 }
157 }
158 return allroles;
159 }
160
161 private void checkRoles(Relation n, LinkedList<Role> allroles, HashMap<String, RoleInfo> map) {
162 List<String> done = new LinkedList<>();
163 // Remove empty roles if several exist (like in route=hiking, see #9844)
164 List<Role> emptyRoles = new LinkedList<>();
165 for (Role r : allroles) {
166 if ("".equals(r.key)) {
167 emptyRoles.add(r);
168 }
169 }
170 if (emptyRoles.size() > 1) {
171 allroles.removeAll(emptyRoles);
172 }
173 for (Role r : allroles) {
174 done.add(r.key);
175 String keyname = r.key;
176 if ("".equals(keyname)) {
177 keyname = tr("<empty>");
178 }
179 RoleInfo ri = map.get(r.key);
180 checkRoleCounts(n, r, keyname, ri);
181 if (ri != null) {
182 if (r.types != null) {
183 checkRoleTypes(n, r, keyname, ri);
184 }
185 if (r.memberExpression != null) {
186 checkRoleMemberExpressions(n, r, keyname, ri);
187 }
188 }
189 }
190 for (String key : map.keySet()) {
191 if (!done.contains(key)) {
192 if (key.length() > 0) {
193 String s = marktr("Role {0} unknown");
194 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
195 tr(s, key), MessageFormat.format(s, key), ROLE_UNKNOWN, n));
196 } else {
197 String s = marktr("Empty role found");
198 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
199 tr(s), s, ROLE_EMPTY, n));
200 }
201 }
202 }
203 }
204
205 private void checkRoleMemberExpressions(Relation n, Role r, String keyname, RoleInfo ri) {
206 Set<OsmPrimitive> notMatching = new HashSet<>();
207 Collection<OsmPrimitive> allPrimitives = new ArrayList<>();
208 allPrimitives.addAll(ri.nodes);
209 allPrimitives.addAll(ri.ways);
210 allPrimitives.addAll(ri.relations);
211 for (OsmPrimitive p : allPrimitives) {
212 if (p.isUsable() && !r.memberExpression.match(p)) {
213 notMatching.add(p);
214 }
215 }
216 if (!notMatching.isEmpty()) {
217 String s = marktr("Member for role ''{0}'' does not match ''{1}''");
218 LinkedList<OsmPrimitive> highlight = new LinkedList<>(notMatching);
219 highlight.addFirst(n);
220 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
221 tr(s, keyname, r.memberExpression), MessageFormat.format(s, keyname, r.memberExpression), WRONG_TYPE,
222 highlight, notMatching));
223 }
224 }
225
226 private void checkRoleTypes(Relation n, Role r, String keyname, RoleInfo ri) {
227 Set<OsmPrimitive> wrongTypes = new HashSet<>();
228 if (!r.types.contains(TaggingPresetType.WAY)) {
229 wrongTypes.addAll(r.types.contains(TaggingPresetType.CLOSEDWAY) ? ri.openways : ri.ways);
230 }
231 if (!r.types.contains(TaggingPresetType.NODE)) {
232 wrongTypes.addAll(ri.nodes);
233 }
234 if (!r.types.contains(TaggingPresetType.RELATION)) {
235 wrongTypes.addAll(ri.relations);
236 }
237 if (!wrongTypes.isEmpty()) {
238 String s = marktr("Member for role {0} of wrong type");
239 LinkedList<OsmPrimitive> highlight = new LinkedList<>(wrongTypes);
240 highlight.addFirst(n);
241 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
242 tr(s, keyname), MessageFormat.format(s, keyname), WRONG_TYPE,
243 highlight, wrongTypes));
244 }
245 }
246
247 private void checkRoleCounts(Relation n, Role r, String keyname, RoleInfo ri) {
248 long count = (ri == null) ? 0 : ri.total;
249 long vc = r.getValidCount(count);
250 if (count != vc) {
251 if (count == 0) {
252 String s = marktr("Role {0} missing");
253 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
254 tr(s, keyname), MessageFormat.format(s, keyname), ROLE_MISSING, n));
255 }
256 else if (vc > count) {
257 String s = marktr("Number of {0} roles too low ({1})");
258 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
259 tr(s, keyname, count), MessageFormat.format(s, keyname, count), LOW_COUNT, n));
260 } else {
261 String s = marktr("Number of {0} roles too high ({1})");
262 errors.add(new TestError(this, Severity.WARNING, ROLE_VERIF_PROBLEM_MSG,
263 tr(s, keyname, count), MessageFormat.format(s, keyname, count), HIGH_COUNT, n));
264 }
265 }
266 }
267
268 @Override
269 public Command fixError(TestError testError) {
270 if (isFixable(testError)) {
271 return new DeleteCommand(testError.getPrimitives());
272 }
273 return null;
274 }
275
276 @Override
277 public boolean isFixable(TestError testError) {
278 Collection<? extends OsmPrimitive> primitives = testError.getPrimitives();
279 return testError.getCode() == RELATION_EMPTY && !primitives.isEmpty() && primitives.iterator().next().isNew();
280 }
281}
Note: See TracBrowser for help on using the repository browser.