Subversion Repositories mkgmap

Rev

Rev 4735 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2015 Gerd Petermann
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 or
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */


package uk.me.parabola.mkgmap.osmstyle.housenumber;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map.Entry;

import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.CityInfo;
import uk.me.parabola.mkgmap.general.MapRoad;
import uk.me.parabola.mkgmap.general.ZipCodeInfo;
import uk.me.parabola.mkgmap.osmstyle.housenumber.HousenumberGenerator.HousenumberMatchByNumComparator;
import uk.me.parabola.mkgmap.osmstyle.housenumber.HousenumberGenerator.HousenumberMatchByPosComparator;

/**
 * Helper class to combine house numbers with MapRoad instances
 * @author Gerd Petermann
 *
 */

public class HousenumberRoad {
        private static final Logger log = Logger.getLogger(HousenumberRoad.class);
        private String streetName;
        private final MapRoad road;
        private CityInfo roadCityInfo;
        private ZipCodeInfo roadZipCode;
        private ExtNumbers extNumbersHead;
        private final List<HousenumberMatch> houseNumbers;
        private boolean changed;
        private boolean isRandom;
        private boolean removeGaps;
        private LinkedHashSet<String> furtherNames;
       
       
        public HousenumberRoad(MapRoad r, CityInfo ci, List<HousenumberMatch> potentialNumbersThisRoad) {
                this.streetName = r.getStreet();
                this.road = r;
                this.roadCityInfo = ci;
                this.houseNumbers = new ArrayList<>(potentialNumbersThisRoad);
                for (HousenumberMatch house : houseNumbers){
                        house.setHousenumberRoad(this);
                }
        }

       
        /**
         * Remember a place name so that the labels are set accordingly.
         * @param name the place name
         */

        public void addPlaceName(String name) {
                if (streetName == null) {
                        streetName = name;
                        return;
                }
                if (streetName.equals(name))
                        return;
                if (getRoad().isNamedByIdentifyServiceRoad()) {
                        // forget the name, the road is probably not a service road for a named road
                        // when a nearby house number is used with addr:place
                        streetName = name;
                        getRoad().setNamedByIdentifyServiceRoad(false);
                        return;
                }
                if (furtherNames == null){
                        furtherNames = new LinkedHashSet<>();
                }
                furtherNames.add(name);
        }

        public String getName (){
                return streetName;
        }
       
        public void buildIntervals() {
                houseNumbers.sort(new HousenumberMatchByNumComparator());
                if (log.isInfoEnabled())
                        log.info("Initial housenumbers for",road,"in",road.getCity(),houseNumbers);
               
                filterRealDuplicates();
                filterGroups();
                if (houseNumbers.isEmpty())
                        return;
                List<HousenumberMatch> leftNumbers = new ArrayList<>();
                List<HousenumberMatch> rightNumbers = new ArrayList<>();
               
                for (HousenumberMatch house : houseNumbers) {
                        if (house.getRoad() == null || house.isIgnored()){
                                continue;
                        }
                        if (house.getHousenumberRoad() != this || house.getHousenumberRoad().getRoad() != house.getRoad()){
                                log.error("internal error, road links are not correct",house.toBrowseURL());
                        }
                        if (house.isLeft()) {
                                leftNumbers.add(house);
                        } else {
                                rightNumbers.add(house);
                        }
                }
                detectGroups(leftNumbers, rightNumbers);
                leftNumbers.sort(new HousenumberMatchByPosComparator());
                rightNumbers.sort(new HousenumberMatchByPosComparator());
               
               
                int currNodePos = 0;
                int nodeIndex = 0;
                int prevNumberNodeIndex = 0;
                int prevNodePos = 0;
                extNumbersHead = null;
                ExtNumbers currNumbers = null;
                assert road.getPoints().get(0).isNumberNode();
                for (Coord p : road.getPoints()) {
                        // An ordinary point in the road.
                        if (!p.isNumberNode()) {
                                currNodePos++;
                                continue;
                        }

                        // The first time round, this is guaranteed to be a CoordNode
                        if (currNodePos == 0) {
                                nodeIndex++;
                                currNodePos++;
                                continue;
                        }

                        // Now we have a CoordNode and it is not the first one.
                        ExtNumbers numbers = new ExtNumbers(this);
                        numbers.setNodeIndex(prevNumberNodeIndex);
                        int leftUsed = numbers.setNumbers(leftNumbers, prevNodePos, currNodePos, true);
                        int rightUsed = numbers.setNumbers(rightNumbers, prevNodePos, currNodePos, false);
                        prevNodePos = currNodePos;
                        // maintain chain
                        numbers.prev = currNumbers;
                        if (currNumbers != null)
                                currNumbers.next = numbers;
                        else {
                                extNumbersHead = numbers;
                        }
                        currNumbers = numbers;
                        leftNumbers.subList(0, leftUsed).clear();
                        rightNumbers.subList(0, rightUsed).clear();                            

                        prevNumberNodeIndex = nodeIndex;
                        nodeIndex++;
                        currNodePos++;
                }
        }

        /**
         * Try to detect groups of houses with continues numbers
         * which should be attached to a zero-length segment.
         * Very useful when a service road connects eg.
         * numbers 7..15 to the named road, but also for just two numbers.
         * @param depth
         * @param leftNumbers
         * @param rightNumbers
         */

        private void detectGroups(List<HousenumberMatch> leftNumbers, List<HousenumberMatch> rightNumbers) {
                List<HousenumberGroup> groups = new ArrayList<>();

                for (int side = 0; side < 2; side++){
                        boolean left = side == 0;
                        List<HousenumberMatch> houses = left ? leftNumbers : rightNumbers;
                        HousenumberGroup group = null;
                        for (int j = 1; j < houses.size(); j++){
                                HousenumberMatch house = houses.get(j);
                                if (group == null){
                                        if (house.isInterpolated())
                                                continue;
                                        HousenumberMatch predHouse = houses.get(j-1);
                                        int deltaNum = predHouse.getHousenumber() - house.getHousenumber();
                                        if (Math.abs(deltaNum) > 2)
                                                continue;
                                        if (HousenumberGroup.housesFormAGroup(predHouse, house))
                                                group = new HousenumberGroup(this, houses.subList(j-1, j+1));
                                } else {
                                        if (!group.tryAddHouse(house)) {
                                                if(group.verify())
                                                        groups.add(group);
                                                group = null;
                                        }
                                }
                        }
                        if (group != null && group.verify()){
                                groups.add(group);
                        }
                }
                if (groups.isEmpty())
                        return;
                boolean nodesAdded = false;
                for (HousenumberGroup group : groups){
                        int oldNumPoints = getRoad().getPoints().size();
                        if (nodesAdded){
                                if (!group.recalcPositions())
                                        continue;
                        }
                        if (group.findSegment(streetName, groups)){
                                nodesAdded = true;
                                if (log.isDebugEnabled())
                                        log.debug("added",getRoad().getPoints().size() - oldNumPoints,"number node(s) at",group.linkNode,"for group",group,"in road",getRoad());
                                oldNumPoints = getRoad().getPoints().size();
                                int minSeg = group.minSeg;
                                for (HousenumberMatch house : this.houseNumbers){
                                        if (house.getSegment() >= minSeg)
                                                HousenumberGenerator.findClosestRoadSegment(house, getRoad());
                                }
                                group.recalcPositions();
                        } else {
                                if(group.linkNode != null){
                                        if (log.isDebugEnabled())
                                                log.debug("used existing zero-length-segment at",group.linkNode,"for group",group,"in road",getRoad());
                                }
                        }
                }
                return;
        }

        /**
         */

        public void checkIntervals(){
                if (extNumbersHead == null)
                        return;
                boolean anyChanges = false;
               
                extNumbersHead.detectRandom();
                for (int loop = 0; loop < 10; loop++){
                        if (loop > 4){
                                // TODO: 3,4,5 ?
                                setRandom(true);
                        }
                        setChanged(false);
                        extNumbersHead = extNumbersHead.checkSingleChainSegments(streetName, removeGaps);
                        extNumbersHead = extNumbersHead.checkChainPlausibility(streetName, houseNumbers);
                        if (isChanged())
                                anyChanges = true;
                        else
                                break;
                }
                setChanged(anyChanges);
        }
       
       
        /**
         * Identify duplicate numbers and ignore those which are close together
         * and those which are probably wrong.
         */

        private void filterRealDuplicates() {
                List<HousenumberMatch> toIgnore = new ArrayList<>();
                final int TO_SEARCH = 6;
                int oddLeft = 0, oddRight = 0, evenLeft = 0, evenRight = 0;
                for (HousenumberMatch  house: houseNumbers){
                        if (house.isIgnored())
                                continue;
                        if (house.isLeft()){
                                if (house.getHousenumber() % 2 == 0)
                                        evenLeft++;
                                else
                                        oddLeft++;
                        } else {
                                if (house.getHousenumber() % 2 == 0)
                                        evenRight++;
                                else
                                        oddRight++;
                        }
                }
                HousenumberMatch usedForCalc = null;
                for (int i = 1; i < houseNumbers.size(); i++){
                        HousenumberMatch house1 = houseNumbers.get(i - 1);
                        HousenumberMatch house2 = houseNumbers.get(i);
                        if (!house1.getSign().equals(house2.getSign())) {
                                usedForCalc = null;
                        } else {
                                if (!house1.isEqualAddress(house2))
                                        continue;
                                // found a duplicate address (e.g. 2 and 2 or 1b and 1b in same road,city etc.)
                                double distBetweenHouses = house2.getLocation().distance(house1.getLocation());
                                double distToUsed = (usedForCalc == null) ? distBetweenHouses : house2.getLocation().distance(usedForCalc.getLocation());
                                if (usedForCalc == null)
                                        usedForCalc = (house1.getDistance() < house2.getDistance()) ? house1 : house2;
                                else {
                                        house1 = usedForCalc;
                                }
                                boolean sameSide = (house2.isLeft() == house1.isLeft());
                                if (log.isDebugEnabled())
                                        log.debug("analysing duplicate address",streetName,house1.getSign(),"for road with id",getRoad().getRoadDef().getId());
                                if (sameSide && (distBetweenHouses < 100 || distToUsed < 100)){
                                        HousenumberMatch obsolete = house1 == usedForCalc ? house2 : house1;
                                        if (log.isDebugEnabled())
                                                log.debug("house",obsolete,obsolete.toBrowseURL(),"is close to other element and on the same road side, is ignored");
                                        toIgnore.add(obsolete);
                                        continue;
                                }
                               
                                if (!sameSide){
                                        if (log.isDebugEnabled())
                                                log.debug("oddLeft, oddRight, evenLeft, evenRight:",oddLeft, oddRight, evenLeft, evenRight);
                                        HousenumberMatch wrongSide = null;
                                        if (house2.getHousenumber() % 2 == 0){
                                                if (evenLeft == 1 && (oddLeft > 1 || evenRight > 0 && oddRight == 0)){
                                                        wrongSide = house2.isLeft() ? house2: house1;
                                                }
                                                if (evenRight == 1 && (oddRight > 1 || evenLeft > 0 && oddLeft == 0)){
                                                        wrongSide = !house2.isLeft() ? house2: house1;
                                                }
                                        } else {
                                                if (oddLeft == 1 && (evenLeft > 1 || oddRight > 0 && evenRight == 0)){
                                                        wrongSide = house2.isLeft() ? house2: house1;
                                                }
                                                if (oddRight == 1 && (evenRight > 1 || oddLeft > 0 && evenLeft == 0)){
                                                        wrongSide = !house2.isLeft() ? house2: house1;
                                                }
                                        }
                                        if (wrongSide != null){
                                                if (log.isDebugEnabled())
                                                        log.debug("house",streetName,wrongSide.getSign(),"from",wrongSide.toBrowseURL(),"seems to be wrong, is ignored");
                                                toIgnore.add(wrongSide);
                                                continue;
                                        }
                                }
                               
                                double[] sumDist = new double[2];
                                double[] sumDistSameSide = new double[2];
                                int[] confirmed = new int[2];
                                int[] falsified = new int[2];
                                int[] found = new int[2];
                                List<HousenumberMatch> dups = Arrays.asList(house2, house1);
                                for (int k = 0; k < dups.size(); k++){
                                        HousenumberMatch other, curr;
                                        if (k == 0){
                                                curr = dups.get(0);
                                                other = dups.get(1);
                                        } else {
                                                curr = dups.get(1);
                                                other = dups.get(0);
                                        }
                                        int pos = houseNumbers.indexOf(curr);

                                        int left = pos - 1;
                                        int right = pos + 1;
                                        HousenumberMatch nearHouse;
                                        int stillToFind = TO_SEARCH;
                                        while (stillToFind > 0){
                                                int oldDone = stillToFind;
                                                if (left >= 0){
                                                        nearHouse = houseNumbers.get(left);
                                                        if (nearHouse != other){
                                                                double dist = curr.getLocation().distance(nearHouse.getLocation());
                                                                sumDist[k] += dist;
                                                                if (nearHouse.isLeft() == curr.isLeft()){
                                                                        sumDistSameSide[k] += dist;
                                                                }
                                                                if (curr.getHousenumber() == nearHouse.getHousenumber()){
                                                                        if (dist < 20)
                                                                                confirmed[k]++;
                                                                } else {
                                                                        if (dist < 10 )
                                                                                falsified[k]++;
                                                                }
                                                        }
                                                        --left;
                                                        stillToFind--;
                                                        if (stillToFind == 0)
                                                                break;
                                                }
                                                if (right < houseNumbers.size()){
                                                        nearHouse = houseNumbers.get(right);
                                                        if (nearHouse != other){
                                                                double dist = curr.getLocation().distance(nearHouse.getLocation());
                                                                sumDist[k] += dist;
                                                                if (nearHouse.isLeft() == curr.isLeft()){
                                                                        sumDistSameSide[k] += dist;
                                                                }
                                                                if (curr.getHousenumber() == nearHouse.getHousenumber()){
                                                                        if (dist < 40)
                                                                                confirmed[k]++;
                                                                } else {
                                                                        if (dist < 10 )
                                                                                falsified[k]++;
                                                                }
                                                        }
                                                        stillToFind--;
                                                        right++;
                                                }
                                                if (oldDone == stillToFind)
                                                        break;
                                        }
                                        found[k] = TO_SEARCH - 1 - stillToFind;
                                }
                                if (log.isDebugEnabled()){
                                        log.debug("dup check 1:", streetName, house1, house1.toBrowseURL());
                                        log.debug("dup check 2:", streetName, house2, house2.toBrowseURL());
                                        log.debug("confirmed",Arrays.toString(confirmed),"falsified",Arrays.toString(falsified),"sum-dist",Arrays.toString(sumDist),"sum-dist-same-side",Arrays.toString(sumDistSameSide));
                                }
                                HousenumberMatch bad = null;
                                if (confirmed[1] > 0 && confirmed[0] == 0  && falsified[1] == 0)
                                        bad = dups.get(0);
                                else if (confirmed[0] > 0 && confirmed[1] == 0  && falsified[0] == 0)
                                        bad = dups.get(1);
                                else if (found[0] > 3 && sumDist[0] > sumDist[1] && sumDistSameSide[0] > sumDistSameSide[1])
                                        bad = dups.get(0);
                                else if (found[1] > 3 && sumDist[1] > sumDist[0] && sumDistSameSide[1] > sumDistSameSide[0])
                                        bad = dups.get(1);
                                if (bad != null){
                                        toIgnore.add(bad);
                                } else {
                                        if (log.isDebugEnabled())
                                                log.debug("duplicate house number, don't know which one to use, ignoring both");
                                        toIgnore.add(house1);
                                        toIgnore.add(house2);
                                        house2.setIgnored(true);
                                        house1.setIgnored(true);
                                }
                        }
                }
                for (HousenumberMatch house : toIgnore){
                        if (log.isInfoEnabled())
                                log.info("duplicate housenumber",streetName,house.getSign(),"is ignored for road with id",house.getRoad().getRoadDef().getId(),",house:",house.toBrowseURL());
                        houseNumbers.remove(house);
                }
        }

        /**
         * Identify groups of buildings with numbers like 1a,1b,1c.
         * The list in housenumbers is sorted so that 2 appears before 2a and
         * 2b appears before 2c.
         * XXX This is quite aggressive, maybe we have to add more logic here.  
         */

        private void filterGroups() {
                if (houseNumbers.size() <= 1)
                        return;
                HousenumberMatch prev = houseNumbers.get(0);
                HousenumberMatch used = null;
                for (int i = 1; i < houseNumbers.size(); i++){
                        HousenumberMatch house = houseNumbers.get(i);
                        if (house.getHousenumber() != prev.getHousenumber())
                                used = null;
                        else {
                                if (used == null)
                                        used = prev;
                                if (prev.getSign().equals(house.getSign()) &&  !prev.isEqualAddress(house)) {
                                        // we want to keep these duplicates
                                } else {
                                        house.setIgnored(true);
                                        if (log.isInfoEnabled())
                                                log.info("using",streetName,used.getSign(), "in favor of",house.getSign(),"as target for address search");
                                }
                        }
                        prev = house;
                }
        }

        public void checkWrongRoadAssignmments(HousenumberRoad other) {
                if (this.extNumbersHead == null || other.extNumbersHead == null)
                        return;
               
                for (int loop = 0; loop < 10; loop++){
                        boolean changed = false;
                        ExtNumbers head1 = this.extNumbersHead;
                        for (ExtNumbers en1 = head1; en1 != null; en1 = en1.next){
                                if (changed)
                                        break;
                                if (!en1.hasNumbers())
                                        continue;
                                ExtNumbers head2 = other.extNumbersHead;
                                for (ExtNumbers en2 = head2; en2 != null; en2 = en2.next){
                                        if (changed)
                                                break;
                                        if (!en2.hasNumbers())
                                                continue;
                                        int res = ExtNumbers.checkIntervals(streetName, en1, en2);
                                        switch (res) {
                                        case ExtNumbers.OK_NO_CHANGES:
                                        case ExtNumbers.NOT_OK_KEEP:
                                                break;
                                        case ExtNumbers.OK_AFTER_CHANGES:
                                                changed = true;
                                                this.setChanged(true);
                                                other.setChanged(true);
                                                break;
                                        case ExtNumbers.NOT_OK_TRY_SPLIT:
                                                if (en1.needsSplit()){
                                                        ExtNumbers test = en1.tryChange(ExtNumbers.SR_FIX_ERROR);
                                                        if (test != en1){
                                                                changed = true;
                                                                if (test.prev == null){
                                                                        this.extNumbersHead = test;
                                                                }
                                                        }
                                                }
                                                if (en2.needsSplit()){
                                                        ExtNumbers test = en2.tryChange(ExtNumbers.SR_FIX_ERROR);
                                                        if (test != en2){
                                                                changed = true;
                                                                if (test.prev == null){
                                                                        other.extNumbersHead = test;
                                                                }
                                                        }
                                                }
                                                break;
                                        case ExtNumbers.NOT_OK_STOP:
                                                return;
                                        default:
                                                log.error("can't fix",en1,en2);
                                        }
                                }
                        }
                        if (!changed)
                                break;
                }
        }
       
        public void setNumbers() {
                if (extNumbersHead == null || extNumbersHead.getNumberList().isEmpty())
                        return;
                // make sure that the name we used for the cluster is also attached to the road
                if (streetName == null){
                        log.error("found no name for road with housenumbers, implement a move to the next named road ?",road);
                        return;
                }
                String[] labels = road.getLabels();
                boolean found = false;
                for (String label : labels){
                        if (label == null)
                                break;
                        if (streetName.equals(label))
                                found = true;
                }
                if (!found){
                        if (labels[0] == null){
                                // add empty label so that the address search name doesn't appear in the map
                                // when the original road did not have any label
                                labels[0] = "";
                        }
                        for (int i = 1; i < labels.length; i++){
                                if (labels[i] == null){
                                        labels[i] = streetName;
                                        log.info("added label",streetName,"for",road,"Labels are now:",Arrays.toString(labels));
                                        found = true;
                                        break;
                                }
                        }
                }
                if (!found){
                        int last = labels.length-1;
                        String droppedLabel = labels[last];
                        labels[last] = streetName;
                        if (droppedLabel != null){
                                if (log.isInfoEnabled())
                                        log.info("dropped label",droppedLabel,"for",road,"in preference to correct address search. Labels are now:",Arrays.toString(labels));
                        }
                }
               
                if (furtherNames != null){
                        boolean changed = false;
                        for (String furtherName : furtherNames){
                                if (road.getLabelPos(furtherName) == -1) {
                                        if (road.addLabel(furtherName))
                                                changed = true;
                                        else {
                                                log.warn("could not add further label",furtherName, "for",road);
                                        }
                                }
                        }
                        if (changed){
                                log.info("added further labels for",road,"Labels are now:",Arrays.toString(labels));
                        }
                }

                if (road.getZip() == null && roadZipCode != null){
                        road.setZip(roadZipCode.getZipCode());
                }
                road.setNumbers(extNumbersHead.getNumberList());
               
        }
       
        public MapRoad getRoad(){
                return road;
        }

        public CityInfo getRoadCityInfo() {
                return roadCityInfo;
        }

        public ZipCodeInfo getRoadZipCode() {
                return roadZipCode;
        }


        public boolean isChanged() {
                return changed;
        }

        public void setChanged(boolean changed) {
                this.changed = changed;
        }

        public boolean isRandom() {
                return isRandom;
        }

        public void setRandom(boolean isRandom) {
                if (!this.isRandom)
                        if (log.isDebugEnabled())
                                log.debug("detected random case",this);
                this.isRandom = isRandom;
        }

        public void setRemoveGaps(boolean b) {
                removeGaps = true;
        }
        public boolean getRemoveGaps() {
                return removeGaps;
        }

        /**
         *
         */

        public void improveSearchResults() {
                ExtNumbers curr = extNumbersHead;
                while (curr != null) {
                        ExtNumbers en = curr.splitLargeGaps();
                        if (en != curr) {
                                if (en.hasNumbers() && en.next != null && en.next.hasNumbers())
                                        setChanged(true);
                                else {
                                        ExtNumbers test = en.hasNumbers() ?  en : en.next;
                                        if (!test.getNumbers().isSimilar(curr.getNumbers()))
                                                setChanged(true);
                                }
                                if (curr.prev == null)
                                        extNumbersHead = en;
                                curr = en;
                                continue;
                        }
                        curr = curr.next;
                }
        }

        public String toString(){
                return getRoad().toString() + " " + houseNumbers;
        }


        /**
         * Check if street name is set, if not, try to find one.
         * Identify those houses which are assigned to this road because it was the closest,
         * but can't be correct because street name doesn't match.
         *
         * @param nodeId2RoadLists maps node ids to the {@link MapRoad} that use the corresponding nodes.  
         * @return
         */

        public List<HousenumberMatch> checkStreetName(Int2ObjectOpenHashMap<HashSet<MapRoad>> nodeId2RoadLists) {
                List<HousenumberMatch> noWrongHouses = Collections.emptyList();
                List<HousenumberMatch> wrongHouses = Collections.emptyList();
                double minDist = Double.MAX_VALUE;
                double maxDist = 0;
                if (!houseNumbers.isEmpty()) {
                        HashMap<String, Integer>possibleStreetNamesFromHouses = new HashMap<>();
                        HashMap<String, Integer>possiblePlaceNamesFromHouses = new HashMap<>();
                        for (HousenumberMatch house : houseNumbers){
                                if (house.getDistance() > maxDist)
                                        maxDist = house.getDistance();
                                if (house.getDistance() < minDist)
                                        minDist = house.getDistance();
                                String potentialName = house.getStreet();
                                if (potentialName != null){
                                        Integer oldCount = possibleStreetNamesFromHouses.put(potentialName, 1);
                                        if (oldCount != null)
                                                possibleStreetNamesFromHouses.put(potentialName, oldCount + 1);
                                }
                                String placeName = house.getPlace();
                                if (placeName != null){
                                        Integer oldCount = possiblePlaceNamesFromHouses.put(placeName, 1);
                                        if (oldCount != null)
                                                possiblePlaceNamesFromHouses.put(placeName, oldCount + 1);
                                }
                        }
                        HashSet<String> connectedRoadNames = new HashSet<>();
                        for (Coord co : road.getPoints()){
                                if (co.getId() == 0)
                                        continue;
                                HashSet<MapRoad> connectedRoads = nodeId2RoadLists.get(co.getId());
                                for (MapRoad r : connectedRoads){
                                        if (r.getStreet() != null)
                                                connectedRoadNames.add(r.getStreet());
                                }
                        }
                        if (streetName != null){
                                if (possibleStreetNamesFromHouses.isEmpty()){
                                        // ok, houses have no street name
                                        return noWrongHouses;
                                }
                                if (possibleStreetNamesFromHouses.size() == 1){
                                        if (possibleStreetNamesFromHouses.containsKey(streetName)){
                                                // ok, houses have same name as street
                                                return noWrongHouses;
                                        }
                                }
                        }
                        if (possibleStreetNamesFromHouses.isEmpty()){
                                // neither road not houses tell us a street name
                                if (furtherNames != null && furtherNames.size() > 0){
                                        Iterator<String> iter = furtherNames.iterator();
                                        streetName = iter.next();
                                        iter.remove();
                                        if (furtherNames.isEmpty())
                                                furtherNames = null;
                                }
                                return noWrongHouses;
                        }
                        if (streetName == null){
                                if (possibleStreetNamesFromHouses.size() == 1){
                                        String potentialName = possibleStreetNamesFromHouses.keySet().iterator().next();
                                        boolean nameOK = false;
                                        if (connectedRoadNames.contains(potentialName))
                                                nameOK = true;
                                        else if (houseNumbers.size() > 1){
                                                nameOK = true;
                                        } else if (maxDist <= 10){
                                                nameOK = true;
                                        }
                                        if (nameOK){
                                                streetName = potentialName;
                                                return noWrongHouses; // all good, return empty list
                                               
                                        }
                                } else {
                                        List<String> matchingNames = new ArrayList<>();
                                        for (Entry<String, Integer> entry : possibleStreetNamesFromHouses.entrySet()){
                                                String name = entry.getKey();
                                                if (connectedRoadNames.contains(name)){
                                                        matchingNames.add(name);
                                                }
                                        }
                                        if (matchingNames.size() == 1){
                                                streetName = matchingNames.get(0);
                                        }
                                }

                               
                        }
                        // if we get here we have no usable street name
                        wrongHouses  = new ArrayList<>();
                        Iterator<HousenumberMatch> iter = houseNumbers.iterator();
                        while (iter.hasNext()){
                                HousenumberMatch house = iter.next();
                                if (streetName != null){
                                        if (house.getStreet() == null || streetName.equalsIgnoreCase(house.getStreet()))
                                                continue;
                                } else if (house.getPlace() != null)
                                        continue;
                                double bestDist = Double.MAX_VALUE;
                                HousenumberMatch best = null;
                                for (MapRoad altRoad : house.getAlternativeRoads()){
                                        if (house.getStreet() != null){
                                                if (house.getStreet().equals(altRoad.getStreet())){
                                                        HousenumberMatch test = new HousenumberMatch(house);
                                                        HousenumberGenerator.findClosestRoadSegment(test, altRoad);
                                                        if (test.getDistance() < bestDist){
                                                                best = test;
                                                                bestDist = test.getDistance();
                                                        }
                                                }
                                        }
                                }
                                iter.remove();
                                if (best != null){
                                        best.calcRoadSide();
                                        wrongHouses.add(best);
                                } else {
                                        log.warn("found no plausible road for address",house.getStreet(),house,house.toBrowseURL());
                                }
                        }
                       
                }
                return wrongHouses;
        }

        public void addHouse(HousenumberMatch house) {
                if (extNumbersHead != null){
                        log.error("internal error: trying to add house to road that was already processed",this.getRoad(),house);
                }
                house.setHousenumberRoad(this);
                houseNumbers.add(house);
        }

        public List<HousenumberMatch> getHouses() {
                return houseNumbers;
        }


        public void setZipCodeInfo(ZipCodeInfo zipInfo) {
                roadZipCode = zipInfo;
        }
}