Subversion Repositories mkgmap

Rev

Rev 4542 | 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.BitSet;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.net.NumberStyle;
import uk.me.parabola.imgfmt.app.net.Numbers;
import uk.me.parabola.imgfmt.app.net.RoadDef;
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.HousenumberMatchByPosComparator;

/**
 * Helper class to allow easy corrections like splitting.
 *
 * If we want to split an interval because it overlaps
 * with another one, we have different options.
 * 1) if the interval covers multiple points of the road, we may change a point to a number node
 * 2) or if the road segment is long enough we may add a point to split it,
 * long enough means that the we can find a point that is so close to the original
 * line that it is not too distorting,  
 * 3) or we may duplicate the node at one end (or both) and move some of the numbers to that new
 * zero-length-interval.
 *
 * When we find no more overlaps, we can start to reduce the distance
 * of the calculated position (the result of the address search in
 * Garmin products) and the known best position on the road.
 * This is a bit tricky: Garmin software places  
 * a)  a single house in the middle of the
 * segment covered by the interval
 * b) two or more houses are placed so that the first
 * is at the very beginning, the last is at the very end,
 * the rest is between them with equal distances.
 * The problem: We can have houses on both sides of the segment,
 * so the optimal length for the left side may not be the
 * best for the right side.
 * We try to find a good compromise between good search result
 * and the number of additional intervals.
 *  
 * @author GerdP
 *
 */

public class ExtNumbers {
        private static final Logger log = Logger.getLogger(ExtNumbers.class);

        private final HousenumberRoad housenumberRoad;
        private static final int MAX_LOCATE_ERROR = 40;
       
        private static final List<HousenumberMatch> NO_HOUSES = Collections.emptyList();
       
        public ExtNumbers prev,next;
        class RoadSide {
                List<HousenumberMatch> houses = Collections.emptyList();
                boolean multipleZipCodes;
                boolean multipleCities;
                boolean notInOrder;
        }
       
        private RoadSide leftSide,rightSide;
        private Numbers numbers = null;
        private int startInRoad, endInRoad;
        private int nodeIndex;
       
        private boolean needsSplit;
        private HousenumberMatch worstHouse;
       
        // indicates a number that is found in the interval, but should not  
        private int badNum;
       
        private boolean hasGaps; // true if interval covers more numbers than known

        // constants representing reasons for splitting
        public static final int SR_FIX_ERROR = 0;
        public static final int SR_OPT_LEN = 1;
        public static final int SR_SPLIT_ROAD_END = 2;
       
        public ExtNumbers(HousenumberRoad housenumberRoad) {
                super();
                this.housenumberRoad = housenumberRoad;
                reset();
        }

        private void setNeedsSplit(boolean b) {
                needsSplit = true;
        }

        public boolean needsSplit() {
                return needsSplit;
        }

        private boolean notInOrder(boolean left){
                RoadSide rs = (left) ? leftSide : rightSide;
                return (rs != null) ? rs.notInOrder : false;
        }

        private List<HousenumberMatch> getHouses(boolean left){
                RoadSide rs = (left) ? leftSide : rightSide;
                return (rs != null) ? rs.houses : NO_HOUSES;  
        }

        private MapRoad getRoad(){
                return housenumberRoad.getRoad();
        }
       
        private void reset() {
                numbers = null;
                needsSplit = false;
                hasGaps = false;
        }
       
       
        public void setNodeIndex(int nodeIndex) {
                this.nodeIndex = nodeIndex;
                if (numbers != null)
                        numbers.setIndex(nodeIndex);
        }


       
        public Numbers getNumbers() {
                if (numbers == null){
                        numbers = new Numbers();
                        numbers.setIndex(nodeIndex);
                        fillNumbers(Numbers.LEFT);
                        fillNumbers(Numbers.RIGHT);
                        if (!numbers.isEmpty()){
                                verify(getHouses(Numbers.LEFT)); // TODO : remove
                                verify(getHouses(Numbers.RIGHT)); // TODO : remove
                        }
                }
                return numbers;
        }

        /**
         * Store given house numbers and meta info
         * @param housenumbers a list of house numbers, sorted by appearance on the road
         * @param startSegment index of road point where this segment starts
         * @param endSegment index of road point where this segment ends
         * @param left {@code true} the left side of the street; {@code false} the right side of the street
         * @return the number of elements which were used (from the beginning)
         */

        public int setNumbers(List<HousenumberMatch> housenumbers, int startSegment, int endSegment, boolean left) {
                int assignedNumbers = 0;
                startInRoad = startSegment;
                endInRoad = endSegment;
                if (!housenumbers.isEmpty()) {
                        RoadSide rs = new RoadSide();
                        if (left)
                                leftSide = rs;
                        else
                                rightSide = rs;
                        // get the sublist of house numbers
                        int maxN = -1;
                        int numHouses = housenumbers.size();
                        for (int i = 0; i< numHouses; i++) {
                                HousenumberMatch house = housenumbers.get(i);
                                if (house.isIgnored())
                                        continue;
                                if (house.getSegment() >= endSegment) {
                                        break;
                                }
                                maxN = i;
                        }
                       
                        if (maxN >= 0) {
                                assignedNumbers = maxN + 1;
                                rs.houses = new ArrayList<>(housenumbers.subList(0, assignedNumbers));
                                assert startSegment < endSegment : this;
                                checkIfStartIsNumberNode();
                        }
                }
                return assignedNumbers;
        }
       
        /**
         * Log an error if start is not a number node
         */

        private void checkIfStartIsNumberNode() {
                if (!getRoad().getPoints().get(startInRoad).isNumberNode()) {
                        log.error("internal error: start is not a number node", this);
                }
        }
       
        /**
         * Apply the given house numbers to the numbers object.
         * @param left {@code true} the left side of the street; {@code false} the right side of the street
         */

        private void fillNumbers(boolean left) {
                NumberStyle style = NumberStyle.NONE;
                List<HousenumberMatch> houses = getHouses(left);
                if (!houses.isEmpty()) {
                        Set<CityInfo> cityInfos = new HashSet<>();
                        Set<ZipCodeInfo> zipCodes = new HashSet<>();
                        // get the sublist of house numbers
                        boolean even = false;
                        boolean odd = false;
                        boolean inOrder = true;
                        boolean inc = false;
                        boolean dec = false;
                        HousenumberMatch highest, lowest;
                        lowest = highest = houses.get(0);
                        Int2IntOpenHashMap distinctNumbers = new Int2IntOpenHashMap();
                        int numHouses = houses.size();
                        HousenumberMatch pred = null;
                        for (int i = 0; i< numHouses; i++) {
                                HousenumberMatch house = houses.get(i);
                                if (house.getCityInfo() != null && !house.getCityInfo().isEmpty())
                                        cityInfos.add(house.getCityInfo());
                                if (house.getZipCode() != null && house.getZipCode().getZipCode() != null)
                                        zipCodes.add(house.getZipCode());
                                int num = house.getHousenumber();
                                if (!hasGaps)
                                        distinctNumbers.put(num, 1);
                                if (num > highest.getHousenumber())
                                        highest = house;
                                if (num < lowest.getHousenumber())
                                        lowest = house;
                                if (num % 2 == 0) {
                                        even = true;
                                } else {
                                        odd = true;
                                }
                               
                                if (pred != null){
                                        int diff = num - pred.getHousenumber();
                                        if(diff > 0)
                                                inc = true;
                                        else if (diff < 0)
                                                dec = true;
                                }
                                pred = house;
                        }
                       
                        if (even && odd) {
                                style = NumberStyle.BOTH;
                        } else if (even) {
                                style = NumberStyle.EVEN;
                        } else {
                                style = NumberStyle.ODD;
                        }
                        int highestNum = highest.getHousenumber();
                        int lowestNum = lowest.getHousenumber();
                        int start = houses.get(0).getHousenumber();
                        int end = houses.get(numHouses-1).getHousenumber();
                        boolean increasing = false; // from low to high
                        if (dec & inc)
                                inOrder = false;
                        if (start == end && highestNum - lowestNum != 0){
                                if (prev != null){
                                        int lastEnd = prev.getNumbers().getEnd(left );
                                        if (lastEnd <= lowestNum)
                                                increasing = true;
                                } else if (next != null){
                                        int nextStart = next.getNumbers().getStart(left);
                                        if (highestNum < nextStart)
                                                increasing = true;
                                } else {
                                        increasing = true;
                                }
                        }
                        else if (start != highestNum && start != lowestNum
                                        || end != highestNum && end != lowestNum) {
                                inOrder = false;
                                if (start <= end)
                                        increasing = true;
                        } else if (start < end){
                                increasing = true;
                        }
                        if (increasing){
                                start = lowestNum;
                                end = highestNum;
                        } else {
                                start = highestNum;
                                end = lowestNum;
                        }
                        if (!hasGaps){
                                int step = (style == NumberStyle.BOTH) ? 1 : 2;
                                for (int n = lowestNum+step; n < highestNum; n += step){
                                        if (distinctNumbers.containsKey(n))
                                                continue;
                                        hasGaps = true;
                                        break;
                                }
                        }
                        RoadSide rs = (left) ? leftSide : rightSide;
                        numbers.setNumbers(left, style, start, end);
                        rs.multipleCities = (cityInfos.size() > 1);
                        rs.multipleZipCodes = (zipCodes.size() > 1);
                        if (cityInfos.size() == 1){
                                CityInfo ci = cityInfos.iterator().next();
                                if (!ci.isEmpty() && !ci.equals(housenumberRoad.getRoadCityInfo())) {
                                        numbers.setCityInfo(left, ci);
                                }
                        }
                       
                        if (zipCodes.size() == 1){
                                ZipCodeInfo zipCodeInfo = zipCodes.iterator().next();
                                if (zipCodeInfo.getZipCode() != null) {
                                        if (!zipCodeInfo.equals(housenumberRoad.getRoadZipCode())) {
                                                // we found a zip code and the road doesn't yet have one, use it for the whole road
                                                if (housenumberRoad.getRoadZipCode() == null){
                                                        housenumberRoad.setZipCodeInfo(zipCodeInfo);
                                                } else
                                                        numbers.setZipCode(left, zipCodeInfo);
                                        }
                                }
                        }
                        rs.notInOrder = !inOrder;
                }
        }

        /**
         * Return the intervals in the format used for the writer routines
         * @return list of numbers, might be empty but is never null
         */

        public List<Numbers> getNumberList() {
                List<Numbers> list = new ArrayList<>();
                // do we have numbers?
                if (!hasNumbersInChain())
                        return list;
               
                boolean headerWasReported = false;

                for (ExtNumbers curr = this; curr != null; curr = curr.next) {
                        Numbers cn = curr.getNumbers();
                        checkIfStartIsNumberNode();
                        if (!cn.isEmpty()) {
                                list.add(cn);
                        }
                        if (log.isInfoEnabled()) {
                                if (!headerWasReported) {
                                        MapRoad road = curr.getRoad();
                                        if (road.getStreet() == null && road.getName() == null)
                                                log.info("final numbers for", road, curr.housenumberRoad.getName(), "in", road.getCity());
                                        else
                                                log.info("final numbers for", road, "in", road.getCity());
                                        headerWasReported = true;
                                }
                                log.info("Left: ", cn.getLeftNumberStyle(), cn.getIndex(), "Start:", cn.getLeftStart(), "End:",
                                                cn.getLeftEnd(), "numbers " + curr.getHouses(Numbers.LEFT));
                                log.info("Right:", cn.getRightNumberStyle(), cn.getIndex(), "Start:", cn.getRightStart(), "End:",
                                                cn.getRightEnd(), "numbers " + curr.getHouses(Numbers.RIGHT));
                        }
                }
                return list;
        }
       
        private boolean hasNumbersInChain() {
                for (ExtNumbers curr = this; curr != null; curr = curr.next){
                        if (curr.hasNumbers()){
                                return true;
                        }
                }
                return false;
        }

        public ExtNumbers checkSingleChainSegments(String streetName, boolean removeGaps) {
                ExtNumbers curr = this;
                ExtNumbers head = this;
                if (housenumberRoad.isRandom() || removeGaps){
                        for (curr = head; curr != null; curr = curr.next){
                                while (curr.hasGaps && (removeGaps || curr.notInOrder(true) || curr.notInOrder(false))){
                                        curr.worstHouse = null;
                                        curr.badNum = -1;
                                        ExtNumbers test = curr.tryChange(SR_FIX_ERROR);
                                        if (test != curr){
                                                if (curr.prev == null)
                                                        head = test;
                                                curr = test;
                                        }
                                        else {
                                                log.warn("can't split numbers interaval for road", curr.getNumbers(), curr);
                                                break;
                                        }
                                }

                        }
                }
                for (curr = head; curr != null; curr = curr.next){
                        while (!curr.isPlausible()) {
                                // this happens in the following cases:
                                // 1. correct OSM data, multiple houses build a block. Standing on the road
                                // you probably see a small service road which leads to the houses.
                                // It is okay to use each of them.
                                // 2. correct OSM data, one or more house should be connected to a
                                // different road with the same name, we want to ignore them
                                // 3. wrong OSM data, one or more numbers are wrong, we want to ignore them
                                // 4. other cases, e.g. numbers 1,3,5 followed by 10,14,12. This should be fixed
                                // by splitting the segment first, as the OSM data might be correct.
                                if (log.isInfoEnabled())
                                        log.info("detected unplausible interval in",streetName, curr.getNumbers(),"in road", getRoad());
                                if (log.isDebugEnabled()){
                                        if (curr.notInOrder(Numbers.LEFT))
                                                log.debug("left numbers not in order:", getRoad(), curr.getHouses(Numbers.LEFT));
                                        if (curr.notInOrder(Numbers.RIGHT))
                                                log.debug("right numbers not in order:", getRoad(), curr.getHouses(Numbers.RIGHT));
                                }
                                curr.setNeedsSplit(true);
                                curr.findGoodSplitPos();
                                ExtNumbers test = curr.tryChange(SR_FIX_ERROR);
                                if (test != curr){
                                        housenumberRoad.setChanged(true);
                                        if (curr.prev == null)
                                                head = test;
                                        curr = test;
                                }
                                else {
                                        log.warn("can't fix unplausible numbers interaval for road",curr.getNumbers(),curr);
                                        break;
                                }
                        }
                }
                return head;
        }

        private void verify(List<HousenumberMatch> houses) {
                for (HousenumberMatch house : houses){
                        if (house.isIgnored())
                                continue;
                        if (house.getSegment() < startInRoad || house.getSegment() >= endInRoad){
                                log.error("internal error, house has wrong segment, road",getRoad(),"house",house,house.toBrowseURL());
                        }
                        if (Double.isNaN(house.getDistance()) || house.getDistance() > HousenumberGenerator.MAX_DISTANCE_TO_ROAD + 10){
                                if (house.getGroup() == null)
                                        log.error("internal error, distance to road too large, road",getRoad(),"house",house,house.toBrowseURL());
                        }
                }
        }

       
        /**
         * Split an interval. This means that we either change an existing point
         * to a number node or we add a number node. A new node may be added between
         * two other points or as a duplicate of one of them.
         * Depending on the reason for the split we use different methods to distribute
         * the existing numbers to the new intervals.
         * @param reason indicates the reason for the split
         * @return this if split was not done or a new {@link ExtNumbers} instance which is
         * the start of a chain, the last instance in this chain points to the same
         * {@link ExtNumbers} instance as the {@code next} field in {@code this} .
         *  
         */

        public ExtNumbers tryChange(int reason){
                ExtNumbers en = this;
                if (reason == SR_FIX_ERROR){
                        if (!notInOrder(Numbers.LEFT) && !notInOrder(Numbers.RIGHT)) {
                                if (badNum < 0 && worstHouse != null)
                                        badNum = worstHouse.getHousenumber();
                                if (badNum > 0){
                                        en = splitInterval();
                                }
                                else {
                                        log.info("have to split",this);
                                }
                        }
                }
                //TODO: in some cases it might be better to move a house to the prev or next interval instead of splitting
                if (en == this)
                        en = tryAddNumberNode(reason);
                boolean changedInterval = false;
                if (en != this){
                        if (en.hasNumbers() && en.next != null && en.next.hasNumbers()){
                                changedInterval = true;
                        } else {
                                ExtNumbers test = en.hasNumbers() ?  en : en.next;
                                if (!test.getNumbers().isSimilar(this.getNumbers()))
                                        changedInterval = true;
                        }
                        if (changedInterval)
                                housenumberRoad.setChanged(true);
                        else {
                                if (reason == SR_FIX_ERROR){
                                        if (en.hasNumbers()){
                                                en.worstHouse = worstHouse;
                                                return en.tryChange(reason);
                                        } else {
                                                en.next.worstHouse = worstHouse;
                                                en.next = en.next.tryAddNumberNode(reason);
                                        }
                                }
                        }
                }
                return en;
        }
       
        /**
         * Split an interval to remove overlaps
         * 1) detect the optimal split position
         * 2) calculate the new intervals
         * @return
         */

        private ExtNumbers splitInterval(){
                if (log.isDebugEnabled())
                        log.debug("trying to split",this,"so that",badNum,"is not contained");
                if (getRoad().countNodes() + 1 > 0x3ff) {
                        log.warn("cannot increase number of number nodes", getRoad());
                        return this;
                }
               
                boolean doSplit = false;
                Numbers origNumbers = getNumbers();
                if (origNumbers.countMatches(badNum) == 0){
                        if (log.isDebugEnabled())
                                log.debug("badNum",badNum,"is not contained in",this);
                        return this;
                }
                // create an test interval to find out which side contains the  bad number
                Numbers testNumbers = new Numbers();
                testNumbers.setNumbers(Numbers.LEFT, origNumbers.getLeftNumberStyle(), origNumbers.getLeftStart(), origNumbers.getLeftEnd());
                boolean left = (testNumbers.countMatches(badNum) > 0);
               
                List<HousenumberMatch> before = new ArrayList<>();
                List<HousenumberMatch> after = new ArrayList<>();
                List<HousenumberMatch> toSplit = getHouses(left);
                boolean inc = (origNumbers.getEnd(left) > origNumbers.getStart(left));
                BitSet segmentsBefore = new BitSet();
                BitSet segmentsAfter = new BitSet();
                for (HousenumberMatch house : toSplit){
                        List<HousenumberMatch> target;
                        if (house.getHousenumber() < badNum){
                                target = inc ? before : after;
                        } else if (house.getHousenumber() > badNum){
                                target = inc ? after : before;
                        } else {
                                int s = origNumbers.getStart(left);
                                target = (s == badNum) ? before : after;
                        }
                        target.add(house);
                        if (target == before){
                                segmentsBefore.set(house.getSegment());
                        } else {
                                segmentsAfter.set(house.getSegment());
                        }
                }
                if (before.isEmpty() || after.isEmpty())
                        return this;
                if (log.isDebugEnabled())
                        log.debug("todo: find best method to separate",before,"and",after);
                HousenumberMatch house1 = before.get(before.size() - 1);
                HousenumberMatch house2 = after.get(0);
                List<HousenumberMatch> testOrder = new ArrayList<>();
                testOrder.add(house1);
                testOrder.add(house2);
                testOrder.sort(new HousenumberMatchByPosComparator());
                if (testOrder.get(0) != house1){
                        log.info("order indicates random case or missing road!",this);
                        housenumberRoad.setRandom(true);
                }
                int splitSegment = -1;
                if (house1.getSegment() != house2.getSegment()){
                        // simple case: change point
                        if (log.isDebugEnabled())
                                log.debug("simple case: change point to number node between",house1,house2);
                        // what point is best?, use beginning of 2nd for now
                        splitSegment = house2.getSegment();
                        doSplit = true;
                       
                } else {
                        int seg = house1.getSegment();
                        Coord c1 = getRoad().getPoints().get(seg);
                        Coord c2 = getRoad().getPoints().get(seg + 1);
                        double segmentLength = c1.distance(c2);

                        Coord toAdd = null;
                        boolean addOK = true;
                        double wantedFraction = (house1.getSegmentFrac() + house2.getSegmentFrac()) / 2;
                        // handle cases where perpendicular is not on the road
                        if (wantedFraction <= 0){
                                wantedFraction = 0;
                                toAdd = new Coord(c1);
                        } else if (wantedFraction >= 1){
                                wantedFraction = 1;
                                toAdd = new Coord(c2);
                        }
                        double usedFraction = wantedFraction;

                        if (toAdd == null) {
                                Coord wanted = c1.makeBetweenPoint(c2, wantedFraction);
                                log.debug("possible solution: split segment with length",formatLen(segmentLength),"near",formatLen(wantedFraction * segmentLength));
                                toAdd = rasterLineNearPoint(c1, c2, wanted, true);
                                if (toAdd != null){
                                        if (toAdd.equals(c1)){
                                                toAdd = new Coord(c1);
                                                usedFraction = 0.0;
                                        }
                                        else if (toAdd.equals(c2)){
                                                toAdd = new Coord(c2);
                                                usedFraction = 0;
                                        }
                                        else {
                                                addOK = checkLineDistortion(c1, c2, toAdd);
                                                if (addOK)
                                                        usedFraction = HousenumberGenerator.getFrac(c1, c2, toAdd);
                                                else
                                                        toAdd = null;
                                        }
                                }
                        }
                        if (toAdd == null){
                                double len1 = wantedFraction * segmentLength;
                                double len2 = (1 - wantedFraction) * segmentLength;
                                if (Math.min(len1, len2) < MAX_LOCATE_ERROR){
                                        if (len1 < len2){
                                                toAdd = new Coord(c1);
                                                usedFraction = 0.0;
                                        } else {
                                                toAdd = new Coord(c2);
                                                usedFraction = 1.0;
                                        }
                                }
                        }
                        if (toAdd == null){
                                log.error("internal error, cannot split",this);
                        }
                        if (toAdd != null){
                                if (log.isDebugEnabled()){
                                        log.debug("solution: split segment with length",formatLen(segmentLength),"at",formatLen(usedFraction * segmentLength));
                                        double distToLine = toAdd.getDisplayedCoord().distToLineSegment(c1.getDisplayedCoord(), c2.getDisplayedCoord());                                       
                                        log.info("adding number node at", toAdd, "to split, dist to line is", formatLen(distToLine));
                                }
                                doSplit = true;
                                splitSegment = seg+1;
                                addAsNumberNode(splitSegment, toAdd);
                                this.endInRoad++;
                                for (HousenumberMatch house : before){
                                        if (house.getSegment() >= seg){
                                                HousenumberGenerator.findClosestRoadSegment(house, getRoad(), seg, splitSegment);
                                        }
                                }
                                for (HousenumberMatch house : after){
                                        if (house.getSegment() < splitSegment)
                                                HousenumberGenerator.findClosestRoadSegment(house, getRoad(), splitSegment, splitSegment + 1);
                                        else
                                                house.setSegment(house.getSegment()+1);
                                }
                                recalcHousePositions(getHouses(!left)); // the other side
                        }
                }
                if (doSplit){
                        ExtNumbers en1 = split(splitSegment);
                        ExtNumbers en2 = en1.next;
                        if (en1.getHouses(Numbers.LEFT).size() + en2.getHouses(Numbers.LEFT).size() != getHouses(Numbers.LEFT).size() ||
                                        en1.getHouses(Numbers.RIGHT).size() + en2.getHouses(Numbers.RIGHT).size() != getHouses(Numbers.RIGHT).size()){
                                log.error("internal error, lost houses");
                        }
                        log.info("number node added in street",getRoad(),getNumbers(),"==>",en1.getNumbers(),"+",en2.getNumbers());            
                        return en1;
                }
                return this;
        }
       
        private boolean checkLineDistortion(Coord c1, Coord c2, Coord toAdd){
                double distToLine = toAdd.getDisplayedCoord().distToLineSegment(c1.getDisplayedCoord(), c2.getDisplayedCoord());
                if (distToLine > 0.2){
                        double angle = Utils.getDisplayedAngle(c1, toAdd, c2);
                        if (Math.abs(angle) > 3){
                                return false;
                        }
                }
                return true;
        }
       
        /**
         * Try to add a number node.
         * We may change an existing point to a number node or add a new
         * number node. A new node might be between the existing ones
         * or a duplicate of one of them.
         * @return
         */

        private ExtNumbers tryAddNumberNode(int reason) {
                int numNumNodes = getRoad().countNodes();
                if (numNumNodes + 1 > RoadDef.MAX_NUMBER_NODES){
                        log.warn("can't change intervals, road has already", numNumNodes, "number nodes");
                        return this; // can't add a node
                }
                String action;
                if (endInRoad - startInRoad > 1)
                        action = "change";
                else {
                        Coord c1 = getRoad().getPoints().get(startInRoad);
                        Coord c2 = getRoad().getPoints().get(startInRoad+1);
                        if (c1.equals(c2)){
                                return dupNode(0, SR_FIX_ERROR);
                        }
                        double segmentLength = c1.distance(c2);
                        int countAfterEnd = 0, countBeforeStart = 0;
                        double minFraction0To1 = 2;
                        double maxFraction0To1 = -1;
                        for (int side = 0; side < 2; side++){
                                boolean left = side == 0;
                                List<HousenumberMatch> houses = getHouses(left);
                                for (HousenumberMatch house : houses){
                                        if (house.getSegmentFrac() < 0)
                                                ++countBeforeStart;
                                        else if (house.getSegmentFrac() > 1)
                                                ++countAfterEnd;
                                        else {
                                                if (minFraction0To1 > house.getSegmentFrac())
                                                        minFraction0To1 = house.getSegmentFrac();
                                                if (maxFraction0To1 < house.getSegmentFrac())
                                                        maxFraction0To1 = house.getSegmentFrac();
                                        }
                                }
                        }
                        // special cases: perpendicular not on the road
                        if (countBeforeStart > 0){
                                return dupNode(0, SR_SPLIT_ROAD_END);
                        }
                        if (countAfterEnd > 0){
                                return dupNode(1, SR_SPLIT_ROAD_END);
                        }
                       
                        // try to find a good split point depending on the split reason
                        double wantedFraction, midFraction;
                        wantedFraction = midFraction = (minFraction0To1 + maxFraction0To1) / 2;
                        Coord toAdd = null;  
                        double len1 = segmentLength * minFraction0To1; // dist to first
                        double len2 = segmentLength * maxFraction0To1;
                        double len3 = (1-maxFraction0To1) * segmentLength;
                        if (reason == SR_FIX_ERROR && worstHouse != null){
                                wantedFraction = worstHouse.getSegmentFrac();
                                if (wantedFraction < minFraction0To1 || wantedFraction > maxFraction0To1){
                                        log.error("internal error, worst house not found",this,worstHouse);
                                }
                        }
                        boolean allowSplitBetween = true;
                        boolean forceEmpty = false;
                        List<Double> wantedFractions = new ArrayList<>();
                       
                        if (reason == SR_OPT_LEN){
                                if (log.isDebugEnabled()){
                                        if (maxFraction0To1 != minFraction0To1){
                                                log.debug("trying to find good split point, houses are between",formatLen(len1),"and",formatLen(len2),"in segment with",formatLen(segmentLength));
                                        } else
                                                log.debug("trying to find good split point, houses are at",formatLen(len1),"in segment with",formatLen(segmentLength));
                                }
                                if (len2 - len1 < 10 && getHouses(Numbers.LEFT).size() <= 1 && getHouses(Numbers.RIGHT).size() <= 1){
                                        // one house or two opposite houses  
                                        // we try to split so that the house(s) are near the middle of one part
                                        wantedFraction = midFraction * 2 - (midFraction > 0.5 ? 1 : 0);
                                        allowSplitBetween = false;
                                } else {
                                        if (len1 > MAX_LOCATE_ERROR / 2){
                                                // create empty segment at start
                                                wantedFractions.add(minFraction0To1 * 0.999);
                                                forceEmpty = true;
                                        }
                                        if (len3 > MAX_LOCATE_ERROR / 2){
                                                // create empty segment at end
                                                if (!wantedFractions.isEmpty()  && len3 > len1)
                                                        wantedFractions.add(0, maxFraction0To1 * 1.001);
                                                else
                                                        wantedFractions.add(maxFraction0To1 * 1.001);
                                                forceEmpty = true;
                                        }
                                }
                        }
                        if (wantedFractions.isEmpty())
                                wantedFractions.add(wantedFraction);
                       
                        double usedFraction = 0;
                        double bestDist = Double.MAX_VALUE;
                        for (int w = 0; w < wantedFractions.size(); w++) {
                                wantedFraction = wantedFractions.get(w);
                                double partLen = wantedFraction * segmentLength ;
                                double shorterLen = Math.min(partLen , segmentLength - partLen);
                                if (shorterLen < 10){
                                        if (reason == SR_FIX_ERROR && minFraction0To1 == maxFraction0To1)
                                                return dupNode(midFraction, SR_FIX_ERROR);
                                        double splitFrac = len1 < len3 ? minFraction0To1 : maxFraction0To1;
                                        return dupNode(splitFrac, SR_OPT_LEN);
                                }
                                double expectedError = c1.getDisplayedCoord().distance(new Coord(c1.getLatitude()+1,c1.getLongitude()));
                                double maxDistBefore = expectedError;
                                double maxDistAfter = expectedError;

                                if (wantedFraction < minFraction0To1){
                                        maxDistAfter = 0;
                                }
                                if (wantedFraction > maxFraction0To1){
                                        maxDistBefore = 0;
                                }
                                for (;;){
                                        Coord wanted = c1.makeBetweenPoint(c2, wantedFraction);
                                        Map<Double, List<Coord>> candidates = rasterLineNearPoint2(c1, c2, wanted, maxDistBefore, maxDistAfter);
                                        boolean foundGood = false;
                                        for (Entry<Double, List<Coord>> entry : candidates.entrySet()){
                                                if (foundGood)
                                                        break;
                                                bestDist = entry.getKey();
                                                for (Coord candidate: entry.getValue()){
                                                        toAdd = candidate;
                                                        usedFraction = HousenumberGenerator.getFrac(c1, c2, toAdd);
                                                        if (usedFraction <= 0 || usedFraction >= 1)
                                                                toAdd = null;
                                                        else if (usedFraction > minFraction0To1 && wantedFraction < minFraction0To1 || usedFraction < maxFraction0To1 && wantedFraction > maxFraction0To1){
                                                                toAdd = null;
                                                        } else if (!allowSplitBetween && usedFraction > minFraction0To1 && usedFraction < maxFraction0To1){
                                                                toAdd = null;
                                                        } else {
                                                                if (bestDist > 0.2){
                                                                        double angle = Utils.getDisplayedAngle(c1, toAdd, c2);
                                                                        if (Math.abs(angle) > 3){
                                                                                toAdd = null;
                                                                                continue;
                                                                        }
                                                                }
                                                                foundGood = true;
                                                                break;
                                                        }
                                                }
                                        }
                                        if (foundGood){
                                                break;
                                        }
                                        toAdd = null;
                                        boolean tryAgain = false;
                                        if (maxDistBefore > 0 && maxDistBefore < segmentLength * wantedFraction) {
                                                maxDistBefore *= 2;
                                                tryAgain = true;
                                        }
                                        if (maxDistAfter > 0 && maxDistAfter < segmentLength * (1 - wantedFraction)) {
                                                maxDistAfter *= 2;
                                                tryAgain = true;
                                        }
                                        if (!tryAgain)
                                                break;
                                }
                                if (toAdd != null)
                                        break;
                        }
                        boolean addOK = true;
                        if (toAdd == null)
                                addOK = false;
                        else {
                                toAdd.incHighwayCount();
                                if (log.isDebugEnabled()){
                                        log.debug("spliting road segment",startInRoad,"at",formatLen(usedFraction * segmentLength));
                                }
                        }
                        if (!addOK){
                                if (reason == SR_FIX_ERROR && minFraction0To1 == maxFraction0To1)
                                        return dupNode(midFraction, SR_FIX_ERROR);
                               
                                if (Math.min(len1, len3) < MAX_LOCATE_ERROR) {
                                        double splitFrac = midFraction;
                                        if (splitFrac <= 0.5) {
                                                if (splitFrac * segmentLength > MAX_LOCATE_ERROR)
                                                        splitFrac = Math.max(minFraction0To1, MAX_LOCATE_ERROR / segmentLength);
                                        } else {
                                                if ((1-splitFrac) * segmentLength > MAX_LOCATE_ERROR)
                                                        splitFrac = Math.min(maxFraction0To1, (segmentLength-MAX_LOCATE_ERROR) / segmentLength);
                                               
                                        }
                                        return dupNode(splitFrac, SR_OPT_LEN);
                                }
                                if(reason == SR_FIX_ERROR)
                                        log.warn("can't fix error in interval",this);
                                else if (log.isDebugEnabled())
                                        log.debug("can't improve search result",this);
                                return this;
                        }
                        if (log.isInfoEnabled())
                                log.info("adding number node at", toAdd, "to split, dist to line is", formatLen(bestDist));
                        action = "add";
                        this.endInRoad = addAsNumberNode(startInRoad + 1, toAdd);
                        int forcedSegment = - 1;
                        if (forceEmpty){
                                if (wantedFraction < minFraction0To1)
                                        forcedSegment = startInRoad + 1;
                                else if (wantedFraction > maxFraction0To1)
                                        forcedSegment = startInRoad;
                        }
                        if (forcedSegment >= 0){
                                setSegment(forcedSegment, getHouses(Numbers.LEFT));
                                setSegment(forcedSegment, getHouses(Numbers.RIGHT));
                        } else {
                                this.recalcHousePositions(getHouses(Numbers.LEFT));
                                this.recalcHousePositions(getHouses(Numbers.RIGHT));
                        }
                }
                int splitSegment = (startInRoad + endInRoad) / 2;
                if (worstHouse != null){
                        if (worstHouse.getSegment() == startInRoad)
                                splitSegment = startInRoad + 1;
                        else if (worstHouse.getSegment() == endInRoad - 1)
                                splitSegment = worstHouse.getSegment();
                } else if (endInRoad - startInRoad > 2){
                        int firstSegWithHouses = endInRoad;
                        int lastSegWithHouses = -1;
                        for (int side = 0; side < 2; side++){
                                boolean left = side == 0;
                                List<HousenumberMatch> houses = getHouses(left);
                                for (HousenumberMatch house : houses){
                                        int s = house.getSegment();
                                        if (s < firstSegWithHouses)
                                                firstSegWithHouses = s;
                                        if (s > lastSegWithHouses)
                                                lastSegWithHouses = s;
                                }
                                       
                        }
                        splitSegment = (firstSegWithHouses + lastSegWithHouses) / 2;
                        if (splitSegment == startInRoad)
                                splitSegment++;
                }
                ExtNumbers en1 = split(splitSegment);
                ExtNumbers en2 = en1.next;
                if (reason == SR_OPT_LEN){
                        // TODO: fill gaps, e.g. if split results in O,1,9 -> O,1,1 + O,9,9 ?
                }
                if ("add".equals(action))
                        log.info("number node added in street",getRoad(),getNumbers(),"==>",en1.getNumbers(),"+",en2.getNumbers());
                else
                        log.info("point changed to number node in street",getRoad(),getNumbers(),"==>",en1.getNumbers(),"+",en2.getNumbers());
                return en1;
        }


        /**
         * Duplicate a node in the road. This creates a zero-length segment.
         * We can add house numbers to this segment and the position in the
         * address search will be the same for all of them.
         * @param fraction a value below 0.5 means duplicate the start node, others
         * duplicate the end node
         * @return the new chain with two segments
         */

        private ExtNumbers dupNode(double fraction, int reason) {
                log.info("duplicating number node in road",getRoad(),getNumbers(),getHouses(Numbers.LEFT),getHouses(Numbers.RIGHT));
                boolean atStart = (fraction <= 0.5);
               
                // add a copy of an existing node
                int index = (atStart) ? startInRoad : endInRoad;
                int splitSegment = (atStart) ? startInRoad + 1: endInRoad;
                Coord closePoint = getRoad().getPoints().get(index);
                Coord toAdd = new Coord(closePoint);
                toAdd.setOnBoundary(closePoint.getOnBoundary());
                toAdd.setOnCountryBorder(closePoint.getOnCountryBorder());
                toAdd.incHighwayCount();
                // we have to make sure that the road starts and ends with a CoordNode!
                this.endInRoad = addAsNumberNode(splitSegment, toAdd);
               
                // distribute the houses to the new intervals
                List<ArrayList<HousenumberMatch>> leftTargets = Arrays.asList(new ArrayList<>(),new ArrayList<>());
                List<ArrayList<HousenumberMatch>> rightTargets = Arrays.asList(new ArrayList<>(),new ArrayList<>());
                int target;
                if (reason == SR_SPLIT_ROAD_END || reason == SR_OPT_LEN){
                        for (int side = 0; side < 2; side++){
                                boolean left = side == 0;
                                List<ArrayList<HousenumberMatch>> targets = left ? leftTargets : rightTargets;
                                for (HousenumberMatch house : getHouses(left)){
                                        if (house.getSegmentFrac() < fraction)
                                                target = 0;
                                        else if (house.getSegmentFrac() > fraction)
                                                target = 1;
                                        else target = (atStart) ? 0 : 1;
                                        targets.get(target).add(house);
                                }
                        }
                } else if (getHouses(Numbers.LEFT).size() > 1 || getHouses(Numbers.RIGHT).size() > 1){
                        int start,end;
                        for (int side = 0; side < 2; side++){
                                boolean left = side == 0;
                                if (getHouses(left).isEmpty())
                                        continue;
                                start = getNumbers().getStart(left);
                                end = getNumbers().getEnd(left);
                                List<ArrayList<HousenumberMatch>> targets = left ? leftTargets : rightTargets;
                                if (start != end){
                                        int midNum = (start + end) / 2;
                                        for (HousenumberMatch house : getHouses(left)){
                                                if (house.getHousenumber() < midNum)
                                                        target = 0;
                                                else if (house.getHousenumber() > midNum)
                                                        target = 1;
                                                else target = (atStart) ? 0 : 1;
                                                targets.get(target).add(house);
                                        }
                                } else if (!multipleZipOrCity(left)) {
                                        if (atStart)
                                                targets.get(1).addAll(getHouses(left));
                                        else
                                                targets.get(0).addAll(getHouses(left));
                                } else {
                                        int mid = getHouses(left).size() / 2;
                                        targets.get(0).addAll(getHouses(left).subList(0, mid));
                                        targets.get(1).addAll(getHouses(left).subList(mid,getHouses(left).size()));
                                }
                               
                        }
                } else {
                        log.error("internal error, don't know how to split", this);
                }
               
                assert splitSegment != startInRoad && splitSegment != endInRoad;
                // make sure that the numbers are assigned to the wanted segment
                setSegment(startInRoad, leftTargets.get(0));
                setSegment(startInRoad, rightTargets.get(0));
                setSegment(splitSegment, leftTargets.get(1));
                setSegment(splitSegment, rightTargets.get(1));
               
                // don't use split() here, the numbers in this may not be properly
                // sorted and we don't want to sort them
                ExtNumbers en1 = divide();
                ExtNumbers en2 = en1.next;
                en1.setNumbers(leftTargets.get(0), startInRoad, splitSegment, true);
                en1.setNumbers(rightTargets.get(0), startInRoad, splitSegment, false);
                en2.setNumbers(leftTargets.get(1), splitSegment, endInRoad, true);
                en2.setNumbers(rightTargets.get(1), splitSegment, endInRoad, false);
               
                log.info("zero length interval added in street",getRoad(),getNumbers(),"==>",en1.getNumbers(),"+",en2.getNumbers());
                if (atStart && !en1.hasNumbers() ||  !atStart && !en2.hasNumbers()){
                        log.error("internal error, zero length interval has no numbers in road",getRoad());
                }
                return en1;    
               
        }

        private void setSegment(int segment, List<HousenumberMatch> houses) {
                for (HousenumberMatch house : houses){
                        HousenumberGenerator.findClosestRoadSegment(house, getRoad(), segment,segment+1);
                        if (house.getRoad() == null || house.getSegment() != segment){
                                // should not happen
                                log.error("internal error, house too far from forced segment in road",getRoad(),house,house.toBrowseURL());
                                house.setIgnored(true);
                        }
                }
               
        }

        /**
         * This should be called if a point was added to the road segment
         * covered by this interval. We have to recalculate the segment numbers
         * and fraction values.
         */

        private void recalcHousePositions(List<HousenumberMatch> houses){
                for (HousenumberMatch house : houses){
                        HousenumberGenerator.findClosestRoadSegment(house, getRoad(), startInRoad, endInRoad);
                }
                if (houses.size() > 1)
                        houses.sort(new HousenumberMatchByPosComparator());
        }
       
       
        /**
         * Create two empty intervals which will replace this.
         * @return
         */

        private ExtNumbers divide(){
                ExtNumbers en1 = new ExtNumbers(housenumberRoad);
                ExtNumbers en2 = new ExtNumbers(housenumberRoad);
                // maintain the linked list
                en1.prev = this.prev;
                if (prev != null)
                        prev.next = en1;
                en1.next = en2;
                en2.prev = en1;
                en2.next = this.next;
                if (this.next != null)
                        next.prev = en2;
                en1.setNodeIndex(nodeIndex);
                en2.setNodeIndex(nodeIndex+1);
                ExtNumbers work = en2.next;
                while (work != null){
                        work.setNodeIndex(work.nodeIndex + 1);
                        work = work.next;
                }
                return en1;
        }
       
        /**
         * Add node to the road.
         * Maintain the positions of houses in the following intervals.
         * @param toAdd
         * @param pos
         * @return new start of next interval
         */

        private int addAsNumberNode(int pos, Coord toAdd){
                if (getRoad().countNodes()  + 1 > RoadDef.MAX_NUMBER_NODES) {
                        throw new MapFailedException("too many number nodes in " + getRoad());
                }
                toAdd.setNumberNode(true);
                toAdd.setAddedNumberNode(true);
                getRoad().getPoints().add(pos, toAdd);
               
                ExtNumbers work = next;
                while (work != null){
                        work.increaseNodeIndexes(startInRoad);                 
                        work = work.next;
                }
                return endInRoad + 1;
        }
       
        /**
         * This has to be called when a point is added to the road.
         * @param startPos
         */

        private void increaseNodeIndexes(int startPos){
                if (startInRoad > startPos){
                        startInRoad++;
                        endInRoad++;
                }
                for (int side = 0; side < 2; side++){
                        boolean left = side == 0;
                        for (HousenumberMatch house : getHouses(left)){
                                int s = house.getSegment();
                                if (s > startPos)
                                        house.setSegment(s+1);
                                else
                                        assert false : "internal error " + getRoad() + " " + getHouses(Numbers.LEFT) + " " + getHouses(Numbers.RIGHT);
                        }
                }
        }
       
       
        private void findGoodSplitPos(){
                badNum = -1;
                worstHouse = null;
                boolean multipleZipOrCity = false;
                for (int side = 0; side < 2; side++){
                        boolean left = side == 0;
                        List<HousenumberMatch> houses = getHouses(left);
                        if (houses.size() <= 1)
                                continue;
                        if (multipleZipOrCity(left))
                                multipleZipOrCity = true;
                        if (houses.size() < 50) {
                                for (HousenumberMatch house: houses){
                                        int hn = house.getHousenumber();
                                        if (countOccurence(houses, hn) > 1)
                                                continue;
                                        ExtNumbers modIvl = simulateRemovalOfHouseNumber(hn, left);
                                        if (modIvl.isPlausible()){
                                                badNum = hn;
                                                if (log.isDebugEnabled())
                                                        log.debug("splitpos details: single remove of",badNum,"results in plausible interval");
                                                return;
                                        }
                                }
                        }
                }
                if (multipleZipOrCity)
                        return; // unlikely
//              log.debug("did not yet find good split position");
                Numbers ivl = getNumbers();
                int[] firstBad = {-1,-1};
                int[] lastBad = {-1,-1};
                for (int side = 0; side < 2; side++){
                        boolean left = (side == 0);
                        int step = 2;
                        if (ivl.getNumberStyle(left) == NumberStyle.BOTH)
                                step = 1;
                        int s = ivl.getStart(left);
                        int e = ivl.getEnd(left);
                        int s2 = ivl.getStart(!left);
                        int e2 = ivl.getEnd(!left );
                        NumberStyle style2 = ivl.getNumberStyle(!left);
                        for (int hn = Math.min(s, e); hn <= Math.max(s, e); hn += step){
                                if (style2 == NumberStyle.EVEN && hn % 2 == 1 || style2 == NumberStyle.ODD && hn % 2 == 0 ){
                                        if (firstBad[side] < 0)
                                                firstBad[side] = hn;
                                        lastBad[side] = hn;
                                        continue;
                                }
                                if (hn < Math.min(s2, e2) || hn > Math.max(s2, e2)){
                                        if (firstBad[side] < 0)
                                                firstBad[side] = hn;
                                        lastBad[side] = hn;
                                }
                        }
                }
                if (firstBad[0] == lastBad[0]){
                        badNum = firstBad[0];
                        if (badNum >= 0)
                                return;
                }
                if (firstBad[1] == lastBad[1]){
                        badNum = firstBad[1];
                        if (badNum >= 0)
                                return;
                }
                badNum = Math.max(firstBad[0], lastBad[0]);
                if (badNum == -1)
                        badNum = Math.min(firstBad[1], lastBad[1]);
                if (log.isDebugEnabled())
                        log.debug("splitpos details",Arrays.toString(firstBad), Arrays.toString(lastBad),"gives badNum",badNum);
        }
       
        private boolean multipleZipOrCity(boolean left) {
                RoadSide rs = left ? leftSide : rightSide;
                return rs == null ? false : rs.multipleCities || rs.multipleZipCodes;
        }

        public ExtNumbers checkChainPlausibility(String streetName,
                        List<HousenumberMatch> potentialNumbersThisRoad) {
                // we try to repair up to 10 times
                ExtNumbers head = this;
                for (int loop = 0; loop < 10; loop++){
                        boolean anyChanges = false;
                        for (ExtNumbers en1 = head; en1 != null; en1 = en1.next){
                                if (anyChanges)
                                        break;
                                if (!en1.hasNumbers())
                                        continue;
                                for (ExtNumbers en2 = en1.next; en2 != null; en2 = en2.next){
                                        if (anyChanges)
                                                break;
                                        if (!en2.hasNumbers())
                                                continue;
                                       
                                        int res = checkIntervals(streetName, en1, en2);
                                        switch (res) {
                                        case OK_NO_CHANGES:
                                        case NOT_OK_KEEP:
                                                break;
                                        case OK_AFTER_CHANGES:
                                                anyChanges = true;
                                                break;
                                        case NOT_OK_TRY_SPLIT:
                                                if (en1.needsSplit){
                                                        ExtNumbers test = en1.tryChange(SR_FIX_ERROR);
                                                        if (test != en1){
                                                                housenumberRoad.setChanged(true);
                                                                anyChanges = true;
                                                                if (test.prev == null){
                                                                        head = test;
                                                                }
                                                        }
                                                }
                                                if (en2.needsSplit){
                                                        ExtNumbers test = en2.tryChange(SR_FIX_ERROR);
                                                        if (test != en2){
                                                                anyChanges = true;
                                                                housenumberRoad.setChanged(true);
                                                        }
                                                }
                                                break;
                                        case NOT_OK_STOP:
                                                return head;
                                        default:
                                                break;
                                        }
                                }
                        }
                        if (!anyChanges)
                                break;
                }
                return head;
        }
       
        public static final int OK_NO_CHANGES = 0;
        public static final int OK_AFTER_CHANGES = 1;
        public static final int NOT_OK_TRY_SPLIT = 2;
        public static final int NOT_OK_KEEP  = 3;
        public static final int NOT_OK_STOP = 4;

        /**
         * Check if two intervals are overlapping  (all combinations of left + right)
         * @param streetName
         * @param en1
         * @param en2
         * @return true if something was changed
         */

        public static int checkIntervals(String streetName, ExtNumbers en1, ExtNumbers en2) {
                if (en1.getRoad() != en2.getRoad()){
                        Coord cs1 = en1.getRoad().getPoints().get(en1.startInRoad);
                        Coord ce1 = en1.getRoad().getPoints().get(en1.endInRoad);
                        Coord ce2 = en2.getRoad().getPoints().get(en2.endInRoad);
                        if (ce2 == cs1 || ce2 == ce1){
                                ExtNumbers help = en1;
                                en1 = en2;
                                en2 = help;
                        }
                }
                boolean allOK = true;
                Numbers ivl1 = en1.getNumbers();
                Numbers ivl2 = en2.getNumbers();
                for (int i = 0; i < 2; i++){
                        boolean left1 = i == 0;
                        NumberStyle style1 = ivl1.getNumberStyle(left1);
                        if (style1 == NumberStyle.NONE)
                                continue;
                        int s1 = ivl1.getStart(left1);
                        int e1 = ivl1.getEnd(left1);
                        for (int j = 0; j < 2; j++){
                                boolean left2 = (j == 0);
                                NumberStyle style2 = ivl2.getNumberStyle(left2);
                                if (style2 == NumberStyle.NONE)
                                        continue;
                                int s2 = ivl2.getStart(left2);
                                int e2 = ivl2.getEnd(left2);
                                boolean ok = true;
                                if (style1 == style2 || style1 == NumberStyle.BOTH || style2 == NumberStyle.BOTH)
                                        ok = checkIntervalBoundaries(s1, e1, s2, e2, left1 == left2 && en1.getRoad() == en2.getRoad());
                                if (ok)
                                        continue;
                                if (en1.getRoad() != en2.getRoad() && !en1.hasGaps && !en2.hasGaps) {
                                        allOK = false;
                                        continue;
                                }

                                if (s1 == e1){
                                        if (en1.getHouses(left1).get(0).isFarDuplicate()){
                                                allOK = false;
                                                continue;
                                        }
                                }
                                if (s2 == e2){
                                        if (en2.getHouses(left2).get(0).isFarDuplicate()){
                                                allOK = false;
                                                continue;
                                        }
                                }
                                List<HousenumberMatch> houses1 = en1.getHouses(left1);
                                List<HousenumberMatch> houses2 = en2.getHouses(left2);
                                if (log.isInfoEnabled()){
                                        log.info("detected unplausible combination of intervals in",streetName,
                                                        s1 + ".." + e1, "and", s2 + ".." + e2, "houses:",
                                                        (left1 ? "left:" : "right"), houses1,
                                                        (left2 ? "left:" : "right"), houses2,
                                                        (en1.getRoad() == en2.getRoad() ? "in road " + en1.getRoad() :  
                                                                "road id(s):" + en1.getRoad().getRoadDef().getId() + ", " + en2.getRoad().getRoadDef().getId()));
                                }
                                double smallestDelta = Double.POSITIVE_INFINITY;
                                HousenumberMatch bestMoveOrig = null;
                                HousenumberMatch bestMoveMod = null;
                                ExtNumbers bestRemove = null;
                                List<HousenumberMatch> possibleRemoves1 = new ArrayList<>();
                                List<HousenumberMatch> possibleRemoves2 = new ArrayList<>();
                               
                                if (!en1.housenumberRoad.isRandom() && !en2.housenumberRoad.isRandom()) {
                                        // check if we can move a house from en1 to en2
                                        for (HousenumberMatch house : houses1){
                                                if (house.getGroup() != null)
                                                        continue;
                                                int n = house.getHousenumber();
                                                if (countOccurence(houses1, n) > 1)
                                                        continue;

                                                if (n == s1 || n == e1) {
                                                        Numbers modNumbers = en1.simulateRemovalOfHouseNumber(n, left1).getNumbers();
                                                        int s1Mod = modNumbers.getStart(left1);
                                                        int e1Mod = modNumbers.getEnd(left1);
                                                        NumberStyle modStyle = modNumbers.getNumberStyle(left1);
                                                        boolean ok2 = true;
                                                        if (modStyle == style2 || modStyle == NumberStyle.BOTH || style2 == NumberStyle.BOTH)
                                                                ok2 = checkIntervalBoundaries(s1Mod, e1Mod, s2, e2, left1 == left2 && en1.getRoad() == en2.getRoad());
                                                        if (ok2){
                                                                // the intervals don't overlap if house is removed from en1
                                                                if (houses1.size() > 1)
                                                                        possibleRemoves1.add(house);
                                                               
                                                                // check if it fits into en2
                                                                HousenumberMatch test = checkMoveTo(house, en2, left2);
                                                                if (test.getRoad() != null){
                                                                        double deltaDist = test.getDistance() - house.getDistance();
                                                                        if (deltaDist < smallestDelta){
                                                                                bestMoveMod = test;
                                                                                bestMoveOrig = house;
                                                                                smallestDelta = deltaDist;
                                                                                bestRemove = en1;
                                                                        }
                                                                }
                                                        }
                                                }
                                        }
                                        for (HousenumberMatch house : houses2){
                                                if (house.getGroup() != null)
                                                        continue;
                                                int n = house.getHousenumber();
                                                if (countOccurence(houses2, n) > 1)
                                                        continue;

                                                if (n == s2 || n == e2) {
                                                        Numbers modNumbers = en2.simulateRemovalOfHouseNumber(n, left2).getNumbers();
                                                        int s2Mod = modNumbers.getStart(left2);
                                                        int e2Mod = modNumbers.getEnd(left2);
                                                        NumberStyle modStyle = modNumbers.getNumberStyle(left2);
                                                        boolean ok2 = true;
                                                        if (modStyle == style1 || modStyle == NumberStyle.BOTH || style1 == NumberStyle.BOTH)
                                                                ok2 = checkIntervalBoundaries(s1, e1, s2Mod, e2Mod, left1 == left2 && en1.getRoad() == en2.getRoad());
                                                        if (ok2){
                                                                // the intervals don't overlap if house is removed from en2
                                                                if (houses2.size() > 1)
                                                                        possibleRemoves2.add(house);
                                                               
                                                               
                                                                // check if it fits into en1
                                                                HousenumberMatch test = checkMoveTo(house, en1, left1);
                                                                if (test.getRoad() != null){
                                                                        double deltaDist = test.getDistance() - house.getDistance();
                                                                        if (deltaDist < smallestDelta){
                                                                                bestMoveMod = test;
                                                                                bestMoveOrig = house;
                                                                                smallestDelta = deltaDist;
                                                                                bestRemove = en2;
                                                                        }
                                                                }                                                              
                                                        }
                                                }
                                        }
                                        if (bestMoveMod != null){
                                                if (bestMoveOrig.isDuplicate()){
                                                        log.warn("duplicate number causes problems",streetName,bestMoveOrig.getSign(),bestMoveOrig.toBrowseURL() );
                                                }
                                                List<HousenumberMatch> fromHouses, toHouses;
                                                ExtNumbers from,to;
                                                if (bestRemove == en1){
                                                        from = en1;
                                                        to = en2;
                                                        fromHouses = houses1;
                                                        toHouses = houses2;
                                                        bestMoveOrig.setLeft(left2);

                                                } else {
                                                        from = en2;
                                                        to = en1;
                                                        fromHouses = houses2;
                                                        toHouses = houses1;
                                                        bestMoveOrig.setLeft(left1);
                                                }
                                                if (bestMoveOrig.getMoved() >= 3){
                                                        bestMoveMod = null;
                                                        bestMoveOrig = null;
                                                        bestRemove.housenumberRoad.setRandom(true);
                                                } else {
                                                        if (log.isInfoEnabled()){
                                                                if (to.getRoad() == from.getRoad())
                                                                        log.info("moving",streetName,bestMoveOrig.getSign(),bestMoveOrig.getElement().toBrowseURL(),"from",fromHouses,"to",toHouses,"in road",to.getRoad());
                                                                else
                                                                        log.info("moving",streetName,bestMoveOrig.getSign(),bestMoveOrig.getElement().toBrowseURL(),"from",fromHouses,"in road",from.getRoad(),"to",toHouses,"in road",to.getRoad());
                                                        }
                                                        bestMoveOrig.incMoved();
                                                        bestMoveOrig.setRoad(to.getRoad());
                                                        bestMoveOrig.setHousenumberRoad(to.housenumberRoad);
                                                        bestMoveOrig.setSegment(bestMoveMod.getSegment());
                                                        bestMoveOrig.setDistance(bestMoveMod.getDistance());
                                                        bestMoveOrig.setSegmentFrac(bestMoveMod.getSegmentFrac());
                                                        from.housenumberRoad.getHouses().remove(bestMoveOrig);
                                                        fromHouses.remove(bestMoveOrig);
                                                        toHouses.add(bestMoveOrig);
                                                        toHouses.sort(new HousenumberMatchByPosComparator());
                                                        en1.reset();
                                                        en2.reset();
                                                        en1.setNumbers(houses1, en1.startInRoad, en1.endInRoad, left1);
                                                        en2.setNumbers(houses2, en2.startInRoad, en2.endInRoad, left2);
                                                        return OK_AFTER_CHANGES;
                                                }
                                        }
                                }
                                ExtNumbers toSplit = null;
                                int splitNum = -1;
                                int delta1 = Math.abs(e1-s1);
                                int delta2 = Math.abs(e2-s2);
                                if (delta1 > 0 && delta2 > 0){
                                        if (en1.hasGaps != en2.hasGaps){
                                                if (en1.hasGaps){
                                                        if (!possibleRemoves1.isEmpty())
                                                                splitNum = possibleRemoves1.get(0).getHousenumber();
                                                        toSplit = en1;
                                                } else {
                                                        if (!possibleRemoves2.isEmpty())
                                                                splitNum = possibleRemoves2.get(0).getHousenumber();
                                                        toSplit = en2;
                                                }
                                        } else if (possibleRemoves1.size() == 1){
                                                splitNum = possibleRemoves1.get(0).getHousenumber();
                                                toSplit = en1;
                                        } else if (possibleRemoves2.size() == 1){
                                                splitNum = possibleRemoves2.get(0).getHousenumber();
                                                toSplit = en2;
                                        } else if (possibleRemoves1.size() > 0){
                                                splitNum = possibleRemoves1.get(0).getHousenumber();
                                                toSplit = en1;
                                        } else if (possibleRemoves2.size() > 0){
                                                splitNum = possibleRemoves2.get(0).getHousenumber();
                                                toSplit = en2;
                                        } else {
                                                // intervals are overlapping, a single remove doesn't help
                                                if (ivl1.isContained(s2, left1) && ivl1.isContained(e2, left1)){
                                                        // en2 is completely in en1
                                                        toSplit = en1;
                                                        splitNum = s2;
                                                } else if (ivl2.isContained(s1, left2) && ivl2.isContained(e1, left2)){
                                                        // en1 is completely in en2
                                                        toSplit = en2;
                                                        splitNum = s1;
                                                }
                                                else {
                                                        if (ivl1.isContained(s2, left1)){
                                                                toSplit = en1;
                                                                splitNum = s2;
                                                        } else if (ivl1.isContained(e2, left1)){
                                                                toSplit = en1;
                                                                splitNum = e2;
                                                        } else if (ivl2.isContained(s1, left2)){
                                                                toSplit = en2;
                                                                splitNum = s1;
                                                        } else if (ivl2.isContained(e1, left2)){
                                                                toSplit = en2;
                                                                splitNum = e1;
                                                        } else if (style1 == NumberStyle.BOTH){
                                                                toSplit = en1;
                                                        } else if (style2 == NumberStyle.BOTH){
                                                                toSplit = en2;
                                                        } else {
                                                                toSplit = (delta1 >= delta2) ? en1 : en2;
                                                        }
                                                }
                                        }
                                }
                                else if (delta1 == 0 && delta2 > 0 && countOccurence(houses2, s1) == 0){
                                        toSplit = en2;
                                        splitNum = s1;
                                }
                                else if (delta2 == 0 && delta1 > 0 && countOccurence(houses1, s2) == 0){
                                        toSplit = en1;
                                        splitNum = s2;
                                }
                                if (toSplit != null){
                                        toSplit.worstHouse = null;
                                        toSplit.badNum = splitNum;
                                        toSplit.setNeedsSplit(true);
                                        return NOT_OK_TRY_SPLIT;
                                       
                                }
                                allOK = false;
                        }
                }
                if (allOK)
                        return OK_NO_CHANGES;
                return NOT_OK_KEEP;
        }

       
        private static HousenumberMatch checkMoveTo(HousenumberMatch house, ExtNumbers other, boolean otherLeft){
                HousenumberMatch test = new HousenumberMatch(house);
                Numbers otherIvl = other.getNumbers();
                int oStart = otherIvl.getStart(otherLeft);
                int oEnd = otherIvl.getEnd(otherLeft);
                // check if it fits into en2
                if (house.getHousenumber() <= Math.min(oStart, oEnd) ||house.getHousenumber() >= Math.max(oStart, oEnd))
                        return test;
                boolean even = house.getHousenumber() % 2 == 0;
                NumberStyle oStyle = otherIvl.getNumberStyle(otherLeft);
                if (oStyle == NumberStyle.EVEN && !even || oStyle == NumberStyle.ODD && even)
                        return test;
                HousenumberGenerator.findClosestRoadSegment(test, other.getRoad(), other.startInRoad, other.endInRoad);
               
                if (test.getDistance() <= HousenumberGenerator.MAX_DISTANCE_TO_ROAD){
                        Coord c1 = other.getRoad().getPoints().get(test.getSegment());
                        Coord c2 = other.getRoad().getPoints().get(test.getSegment() + 1);
                        if (c1.highPrecEquals(c2) || otherLeft == HousenumberGenerator.isLeft(c1, c2, house.getLocation())){
                                test.setLeft(otherLeft);
                                return test;
                        }
                }
                test.setRoad(null);
                return test;
        }      
       
        /**
         * Check the start and end values of two consecutive number intervals
         * for plausibility.
         * @param s1 1st interval start
         * @param e1 1st interval end
         * @param s2 2nd interval start
         * @param e2 2nd interval end
         * @return
         */

        private static boolean checkIntervalBoundaries(int s1, int e1, int s2,int e2, boolean sameSide){
                boolean ok = false;
                // many cases, maybe someone finds simpler code?
                if (sameSide){
                        // allow equal numbers at boundaries
                        if (s1 == e1) {
                                if (e1 == s2) ok = true; // 6 6 6 4 , 6 6 6 6 , 6 6 6 8
                                else if (e1 < s2 && e1 < e2) ok = true; // 4 4 8 6, 4 4 6 8 1st equal, not in higher 2nd
                                else if (e1 > s2 && e1 > e2) ok = true; // 6 6 4 2, 6 6 2 4 1st equal, not in lower 2nd
                        } else if (s1 < e1){
                                if (e1 <= s2 && s2 <= e2) ok = true; // 6 8 8 8, 6 8 8 10, 6 8 10 10, 6 8 10 12 up
                                else if (s2 > e2 && e1 < e2) ok = true; // 6 8 12 10 up down, no overlap, 2nd is higher  
                                else if (s1 > s2 && s1 > e2) ok = true; // 6 8 4 4, 6 8 4 2, 6 8 2 4 up down, 2nd is lower
                        } else { // s1 > e1
                                if (e1 >= s2 && s2 >= e2) ok = true;  // 8 6 6 6, 8 6 6 4, 8 6 4 4, 8 6 4 2 down
                                else if (e1 > s2 && e1 > e2) ok = true;  // 8 6 2 4 down up, no overlap,2nd is lower
                                else if (s1 < s2 && s1 < e2) ok = true;  // 8 6 10 10, 8 6 10 12, 8 6 12 10, 1st down, no overlap,2nd is higher  
                        }
                } else {
                        // left and right: don't allow equal numbers in different intervals
                        if (s1 == e1) {
                                if (s2 == e2 && s1 != s2) ok = true; // 6 6 2 2, 6 6 8 8  
                                else if (s2 < e2 && (s1 < s2 || s1 > e2)) ok = true; // 6 6 2 4  , 6 6 8 10
                                else if (s2 > e2 && (s1 > s2 || s1 < e2)) ok = true; // 6 6 4 2, 6 6 10 8
                        } else if (s1 < e1){
                                if (e1 < s2 && s2 <= e2) ok = true; // 6 8 10 10, 6 8 10 12 up
                                else if (s2 > e2 && e1 < e2) ok = true; // 6 8 12 10 up down, no overlap, 2nd is higher  
                                else if (s1 > s2 && s1 > e2) ok = true; // 6 8 4 4, 6 8 4 2, 6 8 2 4 up down, 2nd is lower
                        } else { // s1 > e1
                                if (e1 > s2 && s2 >= e2) ok = true;  // 8 6 4 4, 8 6 4 2 down
                                else if (e1 > s2 && e1 > e2) ok = true;  // 8 6 2 4 down up, no overlap,2nd is lower
                                else if (s1 < s2 && s1 < e2) ok = true;  // 8 6 10 10, 8 6 10 12, 8 6 12 10, 1st down, no overlap,2nd is higher  
                        }
                }
                if (!ok){
//                      log.error("interval check not ok: ", s1,e1,s2,e2,"same side:",sameSide);
                }
                return ok;
        }
       
        private ExtNumbers simulateRemovalOfHouseNumber(int hn, boolean left){
                ExtNumbers help = new ExtNumbers(housenumberRoad);
                help.prev = prev;
                help.next = next;
               
                List<HousenumberMatch> modifiedHouses = new ArrayList<>(getHouses(left));
                Iterator<HousenumberMatch> iter = modifiedHouses.iterator();
                while (iter.hasNext()){
                        HousenumberMatch house = iter.next();
                        if (house.getHousenumber() == hn)
                                iter.remove();
                }
                help.setNumbers(modifiedHouses, startInRoad, endInRoad, left);
                help.setNumbers(getHouses(!left), startInRoad, endInRoad, !left);
                return help;
        }

        public boolean hasNumbers(){
                return !getNumbers().isEmpty();
        }
       
        /**
         * Try to add node(s) to decrease the distance of the calculated
         * position of an address.
         * @return
         */

        public ExtNumbers splitLargeGaps(){
                if (!hasNumbers())
                        return this;
                // calculate the length of each road segment and
                // the overall length covered by this interval
                int numSegments = endInRoad - startInRoad;
                double[] segmentLenghts = new double[numSegments];
                double fullLength = 0;
                for (int i = startInRoad; i < endInRoad; i++){
                        Coord c1 = getRoad().getPoints().get(i);
                        Coord c2 = getRoad().getPoints().get(i+1);
                        double len = c1.distance(c2);
                        segmentLenghts[i-startInRoad] = len;
                        fullLength += len;
                }
                if (fullLength < MAX_LOCATE_ERROR){
                        if (log.isDebugEnabled())
                                log.debug("segment",this.getNumbers(), "with length",formatLen(fullLength),"is considered OK");
                       
                        return this;
                }
                TreeMap<Integer, Double> searchPositions = new TreeMap<>();
                boolean ok = calcSearchPositions(fullLength, searchPositions);
               
                if (!ok)
                        return this;
               
                double worstDelta = 0;
                worstHouse = null;
                for (int side = 0; side < 2; side++){
                        boolean left = side == 0;
                        List<HousenumberMatch> houses = getHouses(left);
                        for (HousenumberMatch house : houses){
                                double distToStart = 0;
                                for (int k = startInRoad; k < house.getSegment(); k++)
                                        distToStart += segmentLenghts[k-startInRoad];
                                if (house.getSegmentFrac() > 0){
                                        try {
                                                distToStart += Math.min(1, house.getSegmentFrac()) * segmentLenghts[house.getSegment() - startInRoad];         
                                        } catch (Exception e) {
                                                log.error(e);
                                        }
                               
                                }
                                Double searchDist = searchPositions.get(house.getHousenumber());
                                if (searchDist == null){
                                        log.warn("can't compute address search result of",house);
                                } else {
                                        double delta = distToStart - searchDist;
                                        house.setSearchDist(delta);
                                        if (Math.abs(delta) > worstDelta){
                                                worstDelta = Math.abs(delta);
                                                worstHouse = house;
                                        }
                                }
                        }
                }
                if (worstDelta > MAX_LOCATE_ERROR){
                        if (log.isInfoEnabled())
                                log.info("trying to optimize address search for house number in road",getRoad(),worstHouse,"error before opt is",formatLen(worstDelta));
                        return tryChange(SR_OPT_LEN);
                }
                if (log.isDebugEnabled())
                        log.debug("segment",this.getNumbers(), "with length",formatLen(fullLength),"is OK, worst address search for house number in road",getRoad(),worstHouse,"error is",formatLen(worstDelta));
                return this;
        }

        /**
         * Try to simulate a Garmin address search for each number covered by the interval.
         * @param fullLength
         * @param searchPositions filled by this routine
         * @return false if calculation failed
         */

        private boolean calcSearchPositions(double fullLength, TreeMap<Integer, Double> searchPositions){
                Numbers ivl = getNumbers();
                for (int side = 0; side < 2; side++){
                        boolean left = side == 0;
                        NumberStyle style = ivl.getNumberStyle(left);
                        if (style != NumberStyle.NONE){
                                int start = ivl.getStart(left);
                                int end = ivl.getEnd(left);
                                int step = style == NumberStyle.BOTH ? 1 : 2;
                                if (step != 1 && start % 2 != end % 2){
                                        log.error("internal error, bad interval in optimization",this);
                                        return false;
                                }
                                if (start == end){
                                        searchPositions.put(start, fullLength / 2);
                                } else {
                                        int parts = Math.abs(end - start) / step;
                                        double partLen = fullLength / parts;
                                        if (start > end)
                                                step = -step;
                                        int hn = start;
                                        double dist = 0;
                                        while (true) {
                                                searchPositions.put(hn, dist);
                                                if (hn == end)
                                                        break;
                                                dist += partLen;
                                                hn += step;
                                        }
                                        if (parts > 1)
                                                assert Math.abs(fullLength - dist) < 0.1;
                                }
                        }
                }
                return true;
        }
       
        /**
         * Use Bresenham algorithm to get the Garmin points which are close to the line
         * described by c1 and c2 and the point p.
         * @param c1
         * @param c2
         * @param p
         * @return point with smallest perpendicular distance to line
         */

        public static Coord rasterLineNearPoint(Coord c1, Coord c2, Coord p, boolean includeEndPoints){
                int x0 = c1.getLongitude();
                int y0 = c1.getLatitude();
                int x1 = c2.getLongitude();
                int y1 = c2.getLatitude();
                Coord c1Dspl = c1.getDisplayedCoord();
                Coord c2Dspl = c2.getDisplayedCoord();
                int x = x0, y = y0;
                int dx =  Math.abs(x1-x), sx = x<x1 ? 1 : -1;
                int dy = -Math.abs(y1-y), sy = y<y1 ? 1 : -1;
                int err = dx+dy, e2; /* error value e_xy */
                double minDistLine = Double.MAX_VALUE;
                double minDistTarget = Double.MAX_VALUE;
                int bestX = Integer.MAX_VALUE, bestY = Integer.MAX_VALUE;
               
                for(;;){  /* loop */
                        if (!includeEndPoints && x==x1 && y==y1)
                                break;
                        if (Math.abs(y - p.getLatitude()) <= 1  || Math.abs(x - p.getLongitude()) <= 1){
                                Coord t = new Coord(y, x);
                                double distToTarget = t.distance(p);

                                if (includeEndPoints || x != x0 || y != y0){
                                        if (distToTarget < 10){
                                                double distLine = t.distToLineSegment(c1Dspl, c2Dspl);
                                                if (distLine < minDistLine || distLine == minDistLine && distToTarget < minDistTarget || distLine < 0.2 && distToTarget < minDistTarget){
                                                        bestX = x;
                                                        bestY = y;
                                                        minDistLine = distLine;
                                                        minDistTarget = distToTarget;
                                                }
                                        }
                                }
                        }
                        if (x==x1 && y==y1) break;
                        e2 = 2*err;
                        if (e2 > dy) { err += dy; x += sx; } /* e_xy+e_x > 0 */
                        if (e2 < dx) { err += dx; y += sy; } /* e_xy+e_y < 0 */
                }
                if (minDistLine == Double.MAX_VALUE)
                        return null;
                Coord best = new Coord(bestY, bestX);
                return best;
        }


        /**
         * Use Bresenham algorithm to get the Garmin points which are close to the line
         * described by c1 and c2 and the point p.
         * @param c1
         * @param c2
         * @param p
         * @param maxBefore tolerated distance before p
         * @param maxAfter tolerated distance after p
         * @return sorted map with closest points
         */

        public static TreeMap<Double,List<Coord>> rasterLineNearPoint2(Coord c1, Coord c2, Coord p, double maxBefore, double maxAfter){
                int x0 = c1.getLongitude();
                int y0 = c1.getLatitude();
                int x1 = c2.getLongitude();
                int y1 = c2.getLatitude();
                Coord c1Dspl = c1.getDisplayedCoord();
                Coord c2Dspl = c2.getDisplayedCoord();
                int x = x0, y = y0;
                int dx =  Math.abs(x1-x), sx = x<x1 ? 1 : -1;
                int dy = -Math.abs(y1-y), sy = y<y1 ? 1 : -1;
                int err = dx+dy, e2; /* error value e_xy */
               
                TreeMap<Double,List<Coord>> sortedByDistToLine = new TreeMap<>();
                boolean beforeTarget = true;
                double lastDist = Double.NaN;
                for(;;){  /* loop */
                        if (Math.abs(y - p.getLatitude()) <= 1  || Math.abs(x - p.getLongitude()) <= 1){
                                Coord t = new Coord(y, x);
                                double distToTarget = t.distance(p);

                                if (beforeTarget){
                                        if (!Double.isNaN(lastDist) && lastDist < distToTarget)
                                                beforeTarget = false;
                                }
                                if (beforeTarget && distToTarget < maxBefore || !beforeTarget && distToTarget < maxAfter){
                                        Double distLine = t.distToLineSegment(c1Dspl, c2Dspl);
                                        sortedByDistToLine.computeIfAbsent(distLine, k -> new ArrayList<>()).add(t);
                                }
                                lastDist = distToTarget;
                        }
                        if (x==x1 && y==y1)
                                break;
                        e2 = 2*err;
                        if (e2 > dy) { err += dy; x += sx; } /* e_xy+e_x > 0 */
                        if (e2 < dx) { err += dx; y += sy; } /* e_xy+e_y < 0 */
                }
                return sortedByDistToLine;
        }

        private static int countOccurence(List<HousenumberMatch> houses, int num){
                int count = 0;
                for (HousenumberMatch house : houses){
                        if (house.getHousenumber() == num)
                                count++;
                }
                return count;
        }
       
        /**
         * @param length
         * @return string with length, e.g. "0.23 m" or "116.12 m"
         */

        private static String formatLen(double length){
                return HousenumberGenerator.formatLen(length);
        }

        /**
         * Try to detect the case that many house numbers have no specific order.
         *
         */

        public void detectRandom() {
                int countFilledIvls = 0;
                int countFilledSides = 0;
                int countNotInOrder = 0;
                for (ExtNumbers curr = this; curr != null; curr = curr.next){
                        if (!curr.hasNumbers())
                                continue;
                        countFilledIvls++;
                        if (curr.notInOrder(Numbers.LEFT))
                                countNotInOrder++;
                        if (curr.notInOrder(Numbers.RIGHT))
                                countNotInOrder++;
                        if (curr.getHouses(Numbers.LEFT).size() > 1)
                                ++countFilledSides;
                        if (curr.getHouses(Numbers.RIGHT).size() > 1)
                                ++countFilledSides;
                       
                }
                if (countNotInOrder > 0){
                        if (countNotInOrder > countFilledIvls || countNotInOrder > 2 || countFilledSides == countNotInOrder)
                                housenumberRoad.setRandom(true);
                }
        }
       
        private boolean isPlausible(){
                if (!getNumbers().isPlausible())
                        return false;
                if (multipleZipOrCity(true) || multipleZipOrCity(false))
                        return false;  

                return true;
        }

        /**
         * Split this segment into two.
         * @param splitSegment
         */

        private ExtNumbers split(int splitSegment){
                getRoad().getPoints().get(splitSegment).setNumberNode(true);
               
                ExtNumbers first = divide();
                // distribute the houses. The caller has to make sure that
                // they are assigned to the wanted segment and sorted.
                for (int side = 0; side < 2; side++){
                        boolean left = side == 0;
                        List<HousenumberMatch> houses = getHouses(left);
                        if (houses.isEmpty())
                                continue;
                        int used = first.setNumbers(houses, startInRoad, splitSegment, left);
                        first.next.setNumbers(houses.subList(used, houses.size()), splitSegment, endInRoad, left);
                }

                return first;
        }

        public String toString(){
                return getRoad().toString() + getHouses(Numbers.LEFT).toString() + getHouses(Numbers.RIGHT).toString();
        }

}