Ticket #16310: Addresses.java.patch

File Addresses.java.patch, 9.2 KB (added by Luzandro, 8 years ago)
  • src/org/openstreetmap/josm/data/validation/tests/Addresses.java

     
    1313import java.util.Map;
    1414import java.util.Map.Entry;
    1515import java.util.Set;
     16import java.util.stream.Collectors;
     17import java.util.stream.Stream;
    1618
    1719import org.openstreetmap.josm.data.coor.EastNorth;
     20import org.openstreetmap.josm.data.coor.LatLon;
    1821import org.openstreetmap.josm.data.osm.Node;
    1922import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2023import org.openstreetmap.josm.data.osm.Relation;
     
    4750    protected static final String ADDR_NEIGHBOURHOOD = "addr:neighbourhood";
    4851    protected static final String ADDR_PLACE         = "addr:place";
    4952    protected static final String ADDR_STREET        = "addr:street";
     53    protected static final String ADDR_CITY          = "addr:city";
     54    protected static final String ADDR_UNIT          = "addr:unit";
     55    protected static final String ADDR_FLATS         = "addr:flats";
     56    protected static final String ADDR_HOUSE_NAME    = "addr:housename";
     57    protected static final String ADDR_POSTCODE      = "addr:postcode";
    5058    protected static final String ASSOCIATED_STREET  = "associatedStreet";
    5159    // CHECKSTYLE.ON: SingleSpaceSeparator
    5260
     61    private Map<String, Collection<OsmPrimitive>> addresses = null;
     62    private Set<String> ignored_addresses = null;
     63
    5364    /**
    5465     * Constructor
    5566     */
     
    102113        }
    103114    }
    104115
     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
    105170    @Override
     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
    106257    public void visit(Node n) {
    107258        checkHouseNumbersWithoutStreet(n);
     259        checkForDuplicate(n);
    108260    }
    109261
    110262    @Override
    111263    public void visit(Way w) {
    112264        checkHouseNumbersWithoutStreet(w);
     265        checkForDuplicate(w);
    113266    }
    114267
    115268    @Override
    116269    public void visit(Relation r) {
    117270        checkHouseNumbersWithoutStreet(r);
     271        checkForDuplicate(r);
    118272        if (r.hasTag("type", ASSOCIATED_STREET)) {
    119273            // Used to count occurences of each house number in order to find duplicates
    120274            Map<String, List<OsmPrimitive>> map = new HashMap<>();
     
    185339        }
    186340    }
    187341
     342    /**
     343     * returns rough distance between two OsmPrimitives
     344     * @param a primitive a
     345     * @param b primitive b
     346     * @return distance of center of bounding boxes in meters
     347     */
     348    private double getDistance(OsmPrimitive a, OsmPrimitive b) {
     349        LatLon center_a = a.getBBox().getCenter();
     350        LatLon center_b = b.getBBox().getCenter();
     351        return (center_a.greatCircleDistance(center_b));
     352    }
     353
    188354    protected void checkDistance(OsmPrimitive house, Collection<Way> street) {
    189355        EastNorth centroid;
    190356        if (house instanceof Node) {