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

Last change on this file since 12846 was 12846, checked in by bastiK, 15 months ago

see #15229 - use Config.getPref() wherever possible

  • Property svn:eol-style set to native
File size: 10.0 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.data.coor.EastNorth;
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.spi.preferences.Config;
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 = Config.getPref().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.