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

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

fix #14895 - allow addr:neighbourhood as assignment additional to addr:street and addr:place

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