| | 116 | private boolean isPOI(OsmPrimitive p) { |
| | 117 | return p.hasKey("shop", "amenity", "tourism", "leisure", "emergency", "craft", "name"); |
| | 118 | } |
| | 119 | |
| | 120 | private boolean hasAddress(OsmPrimitive p) { |
| | 121 | return (p.hasKey(ADDR_HOUSE_NUMBER) && p.hasKey(ADDR_STREET, ADDR_PLACE)); |
| | 122 | } |
| | 123 | |
| | 124 | /** |
| | 125 | * adds the OsmPrimitive to the address map if it complies to the restrictions |
| | 126 | * @param p OsmPrimitive that has an address |
| | 127 | */ |
| | 128 | private void collectAddress(OsmPrimitive p) { |
| | 129 | if (isPOI(p)) { |
| | 130 | return; |
| | 131 | } |
| | 132 | String simplified_address = getSimplifiedAddress(p); |
| | 133 | if (!ignored_addresses.contains(simplified_address)) { |
| | 134 | if (addresses.containsKey(simplified_address)) { |
| | 135 | addresses.get(simplified_address).add(p); |
| | 136 | } else { |
| | 137 | ArrayList<OsmPrimitive> objects = new ArrayList<>(); |
| | 138 | objects.add(p); |
| | 139 | addresses.put(simplified_address, objects); |
| | 140 | } |
| | 141 | } |
| | 142 | } |
| | 143 | |
| | 144 | protected void initAddressMap(OsmPrimitive primitive) { |
| | 145 | addresses = new HashMap<>(); |
| | 146 | ignored_addresses = new HashSet<>(); |
| | 147 | Collection<OsmPrimitive> primitives = primitive.getDataSet().getPrimitives(p -> !p.isDeleted()); |
| | 148 | String simplified_address; |
| | 149 | for (OsmPrimitive p : primitives) { |
| | 150 | if (p.hasKey(ADDR_UNIT, ADDR_FLATS) && p instanceof Node) { |
| | 151 | for (OsmPrimitive r : p.getReferrers()) { |
| | 152 | if (hasAddress(r)) { |
| | 153 | // ignore addresses of buildings that are connected to addr:unit nodes |
| | 154 | // it's quite reasonable that there are more buildings with this address |
| | 155 | simplified_address = getSimplifiedAddress(r); |
| | 156 | if (!ignored_addresses.contains(simplified_address)) { |
| | 157 | ignored_addresses.add(simplified_address); |
| | 158 | } else if (addresses.containsKey(simplified_address)) { |
| | 159 | addresses.remove(simplified_address); |
| | 160 | } |
| | 161 | } |
| | 162 | } |
| | 163 | } |
| | 164 | if (hasAddress(p)) { |
| | 165 | collectAddress(p); |
| | 166 | } |
| | 167 | } |
| | 168 | } |
| | 169 | |
| | 171 | public void endTest() { |
| | 172 | addresses = null; |
| | 173 | ignored_addresses = null; |
| | 174 | super.endTest(); |
| | 175 | } |
| | 176 | |
| | 177 | protected void checkForDuplicate(OsmPrimitive p) { |
| | 178 | if (this.addresses == null) { |
| | 179 | initAddressMap(p); |
| | 180 | } |
| | 181 | if (!isPOI(p) && hasAddress(p)) { |
| | 182 | String simplified_address = getSimplifiedAddress(p); |
| | 183 | if (ignored_addresses.contains(simplified_address)) { |
| | 184 | return; |
| | 185 | } |
| | 186 | if (addresses.containsKey(simplified_address)) { |
| | 187 | for (OsmPrimitive p2 : addresses.get(simplified_address)) { |
| | 188 | if (p == p2) { |
| | 189 | continue; |
| | 190 | } |
| | 191 | Severity severity_level = Severity.WARNING; |
| | 192 | List<OsmPrimitive> primitives = new ArrayList<>(2); |
| | 193 | primitives.add(p); |
| | 194 | primitives.add(p2); |
| | 195 | String city1 = p.get(ADDR_CITY); |
| | 196 | String city2 = p2.get(ADDR_CITY); |
| | 197 | double distance = getDistance(p, p2); |
| | 198 | if (city1 != null && city2 != null) { |
| | 199 | if (city1.equals(city2)) { |
| | 200 | if (!p.hasKey(ADDR_POSTCODE) || !p2.hasKey(ADDR_POSTCODE) || p.get(ADDR_POSTCODE).equals(p2.get(ADDR_POSTCODE))) { |
| | 201 | severity_level = Severity.WARNING; |
| | 202 | } else { |
| | 203 | // address including city identical but postcode differs |
| | 204 | // most likely perfectly fine |
| | 205 | severity_level = Severity.OTHER; |
| | 206 | } |
| | 207 | } else { |
| | 208 | // address differs only by city - notify if very close, otherwise ignore |
| | 209 | if (distance < 200.0) { |
| | 210 | severity_level = Severity.OTHER; |
| | 211 | } else { |
| | 212 | continue; |
| | 213 | } |
| | 214 | } |
| | 215 | |
| | 216 | } else { |
| | 217 | // at least one address has no city specified |
| | 218 | if (p.hasKey(ADDR_POSTCODE) && p2.hasKey(ADDR_POSTCODE) && p.get(ADDR_POSTCODE).equals(p2.get(ADDR_POSTCODE))) { |
| | 219 | // address including postcode identical |
| | 220 | severity_level = Severity.WARNING; |
| | 221 | } else { |
| | 222 | // city/postcode unclear - warn if very close, otherwise only notify |
| | 223 | // TODO: get city from surrounding boundaries? |
| | 224 | if (distance < 200.0) { |
| | 225 | severity_level = Severity.WARNING; |
| | 226 | } else { |
| | 227 | severity_level = Severity.OTHER; |
| | 228 | } |
| | 229 | } |
| | 230 | } |
| | 231 | errors.add(TestError.builder(this, severity_level, DUPLICATE_HOUSE_NUMBER) |
| | 232 | .message(tr("Duplicate house numbers"), marktr("''{0}'' ({1}m)"), simplified_address, (int) distance) |
| | 233 | .primitives(primitives).build()); |
| | 234 | } |
| | 235 | addresses.get(simplified_address).remove(p); // otherwise we would get every warning two times |
| | 236 | } |
| | 237 | } |
| | 238 | } |
| | 239 | |
| | 240 | private String getSimplifiedAddress(OsmPrimitive p) { |
| | 241 | String simplified_street_name = p.hasKey(ADDR_STREET) ? p.get(ADDR_STREET) : p.get(ADDR_PLACE); |
| | 242 | // ignore whitespaces and dashes in street name, so that "Mozart-Gasse", "Mozart Gasse" and "Mozartgasse" are all seen as equal |
| | 243 | simplified_street_name = simplified_street_name.toUpperCase().replaceAll("[ -]", ""); |
| | 244 | String simplified_address = Stream.of( |
| | 245 | simplified_street_name, |
| | 246 | p.get(ADDR_HOUSE_NUMBER), |
| | 247 | p.get(ADDR_HOUSE_NAME), |
| | 248 | p.get(ADDR_UNIT), |
| | 249 | p.get(ADDR_FLATS)) |
| | 250 | .map(s -> (s == null ? "" : s)) |
| | 251 | .collect(Collectors.joining(" ")); |
| | 252 | simplified_address = simplified_address.trim().toUpperCase(); |
| | 253 | return simplified_address; |
| | 254 | } |
| | 255 | |
| | 256 | @Override |