source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/Addresses.java@ 12718

Last change on this file since 12718 was 12620, checked in by Don-vip, 7 years ago

see #15182 - deprecate all Main logging methods and introduce suitable replacements in Logging for most of them

  • Property svn:eol-style set to native
File size: 9.9 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.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.HashMap;
10import java.util.HashSet;
11import java.util.List;
12import java.util.Locale;
13import java.util.Map;
14import java.util.Map.Entry;
15import java.util.Set;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.data.coor.EastNorth;
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.tools.Geometry;
28import org.openstreetmap.josm.tools.Logging;
29import org.openstreetmap.josm.tools.Pair;
30import org.openstreetmap.josm.tools.SubclassFilteredCollection;
31
32/**
33 * Performs validation tests on addresses (addr:housenumber) and associatedStreet relations.
34 * @since 5644
35 */
36public class Addresses extends Test {
37
38 protected static final int HOUSE_NUMBER_WITHOUT_STREET = 2601;
39 protected static final int DUPLICATE_HOUSE_NUMBER = 2602;
40 protected static final int MULTIPLE_STREET_NAMES = 2603;
41 protected static final int MULTIPLE_STREET_RELATIONS = 2604;
42 protected static final int HOUSE_NUMBER_TOO_FAR = 2605;
43
44 // CHECKSTYLE.OFF: SingleSpaceSeparator
45 protected static final String ADDR_HOUSE_NUMBER = "addr:housenumber";
46 protected static final String ADDR_INTERPOLATION = "addr:interpolation";
47 protected static final String ADDR_NEIGHBOURHOOD = "addr:neighbourhood";
48 protected static final String ADDR_PLACE = "addr:place";
49 protected static final String ADDR_STREET = "addr:street";
50 protected static final String ASSOCIATED_STREET = "associatedStreet";
51 // CHECKSTYLE.ON: SingleSpaceSeparator
52
53 /**
54 * Constructor
55 */
56 public Addresses() {
57 super(tr("Addresses"), tr("Checks for errors in addresses and associatedStreet relations."));
58 }
59
60 protected List<Relation> getAndCheckAssociatedStreets(OsmPrimitive p) {
61 List<Relation> list = OsmPrimitive.getFilteredList(p.getReferrers(), Relation.class);
62 list.removeIf(r -> !r.hasTag("type", ASSOCIATED_STREET));
63 if (list.size() > 1) {
64 Severity level;
65 // warning level only if several relations have different names, see #10945
66 final String name = list.get(0).get("name");
67 if (name == null || SubclassFilteredCollection.filter(list, r -> r.hasTag("name", name)).size() < list.size()) {
68 level = Severity.WARNING;
69 } else {
70 level = Severity.OTHER;
71 }
72 List<OsmPrimitive> errorList = new ArrayList<>(list);
73 errorList.add(0, p);
74 errors.add(TestError.builder(this, level, MULTIPLE_STREET_RELATIONS)
75 .message(tr("Multiple associatedStreet relations"))
76 .primitives(errorList)
77 .build());
78 }
79 return list;
80 }
81
82 protected void checkHouseNumbersWithoutStreet(OsmPrimitive p) {
83 List<Relation> associatedStreets = getAndCheckAssociatedStreets(p);
84 // Find house number without proper location
85 // (neither addr:street, associatedStreet, addr:place, addr:neighbourhood or addr:interpolation)
86 if (p.hasKey(ADDR_HOUSE_NUMBER) && !p.hasKey(ADDR_STREET, ADDR_PLACE, ADDR_NEIGHBOURHOOD)) {
87 for (Relation r : associatedStreets) {
88 if (r.hasTag("type", ASSOCIATED_STREET)) {
89 return;
90 }
91 }
92 for (Way w : OsmPrimitive.getFilteredList(p.getReferrers(), Way.class)) {
93 if (w.hasKey(ADDR_INTERPOLATION) && w.hasKey(ADDR_STREET)) {
94 return;
95 }
96 }
97 // No street found
98 errors.add(TestError.builder(this, Severity.WARNING, HOUSE_NUMBER_WITHOUT_STREET)
99 .message(tr("House number without street"))
100 .primitives(p)
101 .build());
102 }
103 }
104
105 @Override
106 public void visit(Node n) {
107 checkHouseNumbersWithoutStreet(n);
108 }
109
110 @Override
111 public void visit(Way w) {
112 checkHouseNumbersWithoutStreet(w);
113 }
114
115 @Override
116 public void visit(Relation r) {
117 checkHouseNumbersWithoutStreet(r);
118 if (r.hasTag("type", ASSOCIATED_STREET)) {
119 // Used to count occurences of each house number in order to find duplicates
120 Map<String, List<OsmPrimitive>> map = new HashMap<>();
121 // Used to detect different street names
122 String relationName = r.get("name");
123 Set<OsmPrimitive> wrongStreetNames = new HashSet<>();
124 // Used to check distance
125 Set<OsmPrimitive> houses = new HashSet<>();
126 Set<Way> street = new HashSet<>();
127 for (RelationMember m : r.getMembers()) {
128 String role = m.getRole();
129 OsmPrimitive p = m.getMember();
130 if ("house".equals(role)) {
131 houses.add(p);
132 String number = p.get(ADDR_HOUSE_NUMBER);
133 if (number != null) {
134 number = number.trim().toUpperCase(Locale.ENGLISH);
135 List<OsmPrimitive> list = map.get(number);
136 if (list == null) {
137 list = new ArrayList<>();
138 map.put(number, list);
139 }
140 list.add(p);
141 }
142 if (relationName != null && p.hasKey(ADDR_STREET) && !relationName.equals(p.get(ADDR_STREET))) {
143 if (wrongStreetNames.isEmpty()) {
144 wrongStreetNames.add(r);
145 }
146 wrongStreetNames.add(p);
147 }
148 } else if ("street".equals(role)) {
149 if (p instanceof Way) {
150 street.add((Way) p);
151 }
152 if (relationName != null && p.hasTagDifferent("name", relationName)) {
153 if (wrongStreetNames.isEmpty()) {
154 wrongStreetNames.add(r);
155 }
156 wrongStreetNames.add(p);
157 }
158 }
159 }
160 // Report duplicate house numbers
161 for (Entry<String, List<OsmPrimitive>> entry : map.entrySet()) {
162 List<OsmPrimitive> list = entry.getValue();
163 if (list.size() > 1) {
164 errors.add(TestError.builder(this, Severity.WARNING, DUPLICATE_HOUSE_NUMBER)
165 .message(tr("Duplicate house numbers"), marktr("House number ''{0}'' duplicated"), entry.getKey())
166 .primitives(list)
167 .build());
168 }
169 }
170 // Report wrong street names
171 if (!wrongStreetNames.isEmpty()) {
172 errors.add(TestError.builder(this, Severity.WARNING, MULTIPLE_STREET_NAMES)
173 .message(tr("Multiple street names in relation"))
174 .primitives(wrongStreetNames)
175 .build());
176 }
177 // Report addresses too far away
178 if (!street.isEmpty()) {
179 for (OsmPrimitive house : houses) {
180 if (house.isUsable()) {
181 checkDistance(house, street);
182 }
183 }
184 }
185 }
186 }
187
188 protected void checkDistance(OsmPrimitive house, Collection<Way> street) {
189 EastNorth centroid;
190 if (house instanceof Node) {
191 centroid = ((Node) house).getEastNorth();
192 } else if (house instanceof Way) {
193 List<Node> nodes = ((Way) house).getNodes();
194 if (house.hasKey(ADDR_INTERPOLATION)) {
195 for (Node n : nodes) {
196 if (n.hasKey(ADDR_HOUSE_NUMBER)) {
197 checkDistance(n, street);
198 }
199 }
200 return;
201 }
202 centroid = Geometry.getCentroid(nodes);
203 } else {
204 return; // TODO handle multipolygon houses ?
205 }
206 if (centroid == null) return; // fix #8305
207 double maxDistance = Main.pref.getDouble("validator.addresses.max_street_distance", 200.0);
208 boolean hasIncompleteWays = false;
209 for (Way streetPart : street) {
210 for (Pair<Node, Node> chunk : streetPart.getNodePairs(false)) {
211 EastNorth p1 = chunk.a.getEastNorth();
212 EastNorth p2 = chunk.b.getEastNorth();
213 if (p1 != null && p2 != null) {
214 EastNorth closest = Geometry.closestPointToSegment(p1, p2, centroid);
215 if (closest.distance(centroid) <= maxDistance) {
216 return;
217 }
218 } else {
219 Logging.warn("Addresses test skipped chunck "+chunk+" for street part "+streetPart+" because p1 or p2 is null");
220 }
221 }
222 if (!hasIncompleteWays && streetPart.isIncomplete()) {
223 hasIncompleteWays = true;
224 }
225 }
226 // No street segment found near this house, report error on if the relation does not contain incomplete street ways (fix #8314)
227 if (hasIncompleteWays) return;
228 List<OsmPrimitive> errorList = new ArrayList<>(street);
229 errorList.add(0, house);
230 errors.add(TestError.builder(this, Severity.WARNING, HOUSE_NUMBER_TOO_FAR)
231 .message(tr("House number too far from street"))
232 .primitives(errorList)
233 .build());
234 }
235}
Note: See TracBrowser for help on using the repository browser.