Subversion Repositories mkgmap

Rev

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

/*
 * Copyright (C) 2008 Steve Ratcliffe
 *
 * 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.imgfmt.app.net;

import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.CityInfo;
import uk.me.parabola.mkgmap.general.ZipCodeInfo;

/**
 * Describes the house numbering from a node in the road.
 * @author Steve Ratcliffe
 */

public class Numbers {
        private static final Logger log = Logger.getLogger(Numbers.class);
        public static final boolean LEFT = true;
        public static final boolean RIGHT = false;

        private static final int MAX_DELTA = 131071; // see NumberPreparer

        // The node in the road where these numbers apply.  In the polish notation it is the
        // node in the road, whereas in the NET file it is the index of the number node.
        private int nodeNumber; // node in road index
        private Integer indexNumber; // the position in the list of Numbers (starting with 0)
        // the data on side of the road
        private RoadSide leftSide,rightSide;

        private class RoadSide {
                NumDesc numbers;
                // to be added
                CityInfo cityInfo;
                ZipCodeInfo zipCode;
                boolean isEmpty(){
                        return cityInfo == null && zipCode == null && numbers == null;
                }
        }

        private class NumDesc{
                NumberStyle numberStyle;
                int start,end;

                public NumDesc(NumberStyle numberStyle, int start, int end) {
                        this.numberStyle = numberStyle;
                        this.start = start;
                        this.end = end;
                }
                public boolean contained(int hn){
                        boolean isEven = (hn % 2 == 0);
                        if (numberStyle == NumberStyle.BOTH
                                        || numberStyle == NumberStyle.EVEN && isEven
                                        || numberStyle == NumberStyle.ODD && !isEven){
                                if (start <= end) {
                                        if (start <= hn && hn <= end)
                                                return true;
                                }
                                else {
                                        if (end <= hn && hn <= start)
                                                return true;
                                }
                        }
                        return false;
                }

                @Override
                public String toString() {
                        return String.format("%s,%d,%d", numberStyle, start,end);
                }

        }

        public Numbers() {
        }

        /**
         * This constructor takes a comma separated list as in the polish format. Also used in testing as
         * it is an easy way to set all common parameters at once.
         *
         * @param spec Node number, followed by left and then right parameters as in the polish format.
         */

        public Numbers(String spec) {
                String[] strings = spec.split(",");
                nodeNumber = Integer.valueOf(strings[0]);
                NumberStyle numberStyle = NumberStyle.fromChar(strings[1]);
                int start = Integer.valueOf(strings[2]);
                int end = Integer.valueOf(strings[3]);
                setNumbers(LEFT, numberStyle, start, end);
                numberStyle = NumberStyle.fromChar(strings[4]);
                start = Integer.valueOf(strings[5]);
                end = Integer.valueOf(strings[6]);
                setNumbers(RIGHT, numberStyle, start, end);

                if (strings.length > 8){
                        // zip codes
                        String zip = strings[7];
                        if ("-1".equals(zip) == false)
                                setZipCode(LEFT, new ZipCodeInfo(zip));
                        zip = strings[8];
                        if ("-1".equals(zip) == false)
                                setZipCode(RIGHT, new ZipCodeInfo(zip));
                }
                if (strings.length > 9){
                        String city,region,country;
                        int nextPos = 9;
                        city = strings[nextPos];
                        if ("-1".equals(city) == false){
                                region = strings[nextPos + 1];
                                country = strings[nextPos + 2];
                                setCityInfo(LEFT, new CityInfo(city, region, country));
                                nextPos = 12;
                        } else
                                nextPos = 10;
                        city = strings[nextPos];
                        if ("-1".equals(city) == false){
                                region = strings[nextPos + 1];
                                country = strings[nextPos + 2];
                                setCityInfo(RIGHT, new CityInfo(city, region, country));
                        }                      
                }              
        }

        public void setNumbers(boolean left, NumberStyle numberStyle, int start, int end){
                if (numberStyle != NumberStyle.NONE || start != -1 || end != -1){
                        RoadSide rs = assureSideIsAllocated(left);
                        rs.numbers = new NumDesc(numberStyle, start, end);
                } else {
                        RoadSide rs = (left) ? leftSide : rightSide;
                        if (rs != null)
                                rs.numbers = null;
                        removeIfEmpty(left);
                }
        }

        public void setCityInfo(boolean left, CityInfo ci){
                if (ci != null){
                        RoadSide rs = assureSideIsAllocated(left);
                        rs.cityInfo = ci;
                } else {
                        RoadSide rs = (left) ? leftSide : rightSide;
                        if (rs != null)
                                rs.cityInfo = null;
                        removeIfEmpty(left);
                }
        }

        public CityInfo getCityInfo(boolean left){
                RoadSide rs = (left) ? leftSide : rightSide;
                return (rs != null) ? rs.cityInfo : null;
        }

        public void setZipCode(boolean left, ZipCodeInfo zipCode){
                if (zipCode != null){
                        RoadSide rs = assureSideIsAllocated(left);
                        rs.zipCode = zipCode;
                } else {
                        RoadSide rs = (left) ? leftSide : rightSide;
                        if (rs != null)
                                rs.zipCode= null;
                        removeIfEmpty(left);
                }
        }


        public ZipCodeInfo getZipCodeInfo (boolean left){
                RoadSide rs = (left) ? leftSide : rightSide;
                return (rs != null) ? rs.zipCode: null;
        }

        private void removeIfEmpty(boolean left){
                if (left && leftSide != null && leftSide.isEmpty())
                        leftSide = null;
                if (!left && rightSide != null && rightSide.isEmpty())
                        rightSide = null;
        }

        // allocate or return allocated RoadSide instance for the given road side
        private RoadSide assureSideIsAllocated(boolean left){
                if (left && leftSide == null)
                        leftSide = new RoadSide();
                if (!left && rightSide == null)
                        rightSide = new RoadSide();
                return (left) ? leftSide : rightSide;
        }

        public int getNodeNumber() {
                return nodeNumber;
        }

        public void setNodeNumber(int nodeNumber) {
                this.nodeNumber = nodeNumber;
        }

        public int getIndex() {
                if (indexNumber == null) {
                        log.error("WARNING: index not set!!");
                        return nodeNumber;
                }
                return indexNumber;
        }

        public boolean hasIndex() {
                return indexNumber != null;
        }

        /**
         * @param index the nth number node
         */

        public void setIndex(int index) {
                this.indexNumber = index;
        }

        private NumDesc getNumbers(boolean left) {
                RoadSide rs = (left) ? leftSide : rightSide;
                return (rs != null) ? rs.numbers : null;  
        }

        public NumberStyle getNumberStyle(boolean left) {
                NumDesc n = getNumbers(left);
                return (n == null) ? NumberStyle.NONE : n.numberStyle;
        }

        public int getStart(boolean left) {
                NumDesc n = getNumbers(left);
                return (n == null) ? -1 : n.start; // -1 is the default in the polish format
        }

        public int getEnd(boolean left) {
                NumDesc n = getNumbers(left);
                return (n == null) ? -1 : n.end; // -1 is the default in the polish format
        }

        public String toString() {
                String nodeStr = "0";
                if (nodeNumber > 0)
                        nodeStr = String.valueOf(nodeNumber);
                else if (getIndex() > 0)
                        nodeStr = String.format("(n%d)", getIndex());

                nodeStr = String.format("%s,%s,%d,%d,%s,%d,%d",
                                nodeStr,
                                getNumberStyle(LEFT),
                                getStart(LEFT),
                                getEnd(LEFT),
                                getNumberStyle(RIGHT),
                                getStart(RIGHT),
                                getEnd(RIGHT));

                if (getCityInfo(LEFT) != null || getCityInfo(RIGHT) != null
                                || getZipCodeInfo(LEFT) != null || getZipCodeInfo(RIGHT) != null) {
                        nodeStr = String.format("%s,%s,%s", nodeStr,
                                        getPolishZipCode(LEFT), getPolishZipCode(RIGHT));
                        if (getCityInfo(LEFT) != null || getCityInfo(RIGHT) != null) {
                                nodeStr = String.format("%s,%s,%s",nodeStr,
                                                getPolishCityInfo(LEFT),getPolishCityInfo(RIGHT));
                        }
                }
                return nodeStr;                
        }

        public NumberStyle getLeftNumberStyle() {
                return getNumberStyle(LEFT);
        }
        public NumberStyle getRightNumberStyle() {
                return getNumberStyle(RIGHT);
        }
        public int getLeftStart(){
                return getStart(LEFT);
        }
        public int getRightStart(){
                return getStart(RIGHT);
        }
        public int getLeftEnd(){
                return getEnd(LEFT);
        }
        public int getRightEnd(){
                return getEnd(RIGHT);
        }

        public boolean equals(Object obj) {
                if (!(obj instanceof Numbers))
                        return false;

                Numbers other = (Numbers) obj;
                return toString().equals(other.toString());
        }

        public int hashCode() {
                return toString().hashCode();
        }

        public boolean isPlausible(){
                if (!isPlausible(getLeftNumberStyle(), getLeftStart(), getLeftEnd()))
                        return false;
                if (!isPlausible(getRightNumberStyle(), getRightStart(), getRightEnd()))
                        return false;
                if (getLeftNumberStyle() == NumberStyle.NONE
                                || getRightNumberStyle() == NumberStyle.NONE)
                        return true;
                if (getCityInfo(LEFT) != null){
                        if (getCityInfo(LEFT).equals(getCityInfo(RIGHT)) == false)
                                return true;
                } else if (getCityInfo(RIGHT) != null)
                        return true;
                if (getZipCodeInfo(LEFT) != null){
                        if (getZipCodeInfo(LEFT).equals(getZipCodeInfo(RIGHT)) == false)
                                return true;
                } else if (getCityInfo(RIGHT) != null)
                        return true;
                if (getLeftNumberStyle() == getRightNumberStyle() || getLeftNumberStyle() == NumberStyle.BOTH || getRightNumberStyle()==NumberStyle.BOTH){
                        // check if intervals are overlapping
                        int start1, start2,end1,end2;
                        if (getLeftStart() < getLeftEnd()){
                                start1 = getLeftStart();
                                end1 = getLeftEnd();
                        } else {
                                start1 = getLeftEnd();
                                end1 = getLeftStart();
                        }
                        if (getRightStart() < getRightEnd()){
                                start2 = getRightStart();
                                end2 = getRightEnd();
                        } else {
                                start2 = getRightEnd();
                                end2 = getRightStart();
                        }
                        if (start2 > end1 || end2 < start1)
                                return true;
                        if (getLeftStart() == getLeftEnd() && getRightStart() == getRightEnd() && getLeftStart() == getRightStart())
                                return true; // single number on both sides of the road

                        return false;
                }

                return true;
        }

        private static boolean isPlausible(NumberStyle style, int start, int end){
                if (Math.abs(start - end) > MAX_DELTA)
                        return false;
                if (style == NumberStyle.EVEN)
                        return start % 2 == 0 && end % 2 == 0;
                if (style == NumberStyle.ODD)
                        return start % 2 != 0 && end % 2 != 0;
                return true;
        }

        public boolean isContained(int hn, boolean left){
                RoadSide rs = left ? leftSide : rightSide;
                if (rs == null || rs.numbers == null)
                        return false;
                return rs.numbers.contained(hn);
        }
       
        /**
         * @param hn a house number
         * @param left left or right side
         * @return 0 if the number is not within the intervals, 1 if it is on one side, 2 if it on both sides
         */

        public int countMatches(int hn) {
                int matches = 0;
                if (isContained(hn, LEFT))
                        matches++;
                if (isContained(hn, RIGHT))
                        matches++;
                if (matches > 1){
                        if (getLeftStart() == getLeftEnd() && getRightStart() == getRightEnd())
                                matches = 1; // single number on both sides of the road
                }
                return matches;
        }

        /**
         * Compare all fields that describe the interval, but not the position
         * @param other
         * @return true if these fields are equal
         */

        public boolean isSimilar(Numbers other){
                if (other == null)
                        return false;
                if (getLeftNumberStyle() != other.getLeftNumberStyle()
                                || getLeftStart() != other.getLeftStart() || getLeftEnd() != other.getLeftEnd()
                                || getRightNumberStyle() != other.getRightNumberStyle()
                                || getRightStart() != other.getRightStart() || getRightEnd() != other.getRightEnd())
                        return false;
                return true;

        }

        public boolean isEmpty(){
                return getLeftNumberStyle() == NumberStyle.NONE && getRightNumberStyle() == NumberStyle.NONE;
        }


        private String getPolishCityInfo (boolean left){
                CityInfo ci = getCityInfo(left);
                if (ci == null)
                        return "-1";
                StringBuilder sb = new StringBuilder();
                if (ci.getCity() != null)
                        sb.append(ci.getCity());
                sb.append(",");
                if (ci.getRegion() != null)
                        sb.append(ci.getRegion());
                sb.append(",");
                if (ci.getCountry() != null)
                        sb.append(ci.getCountry());
                return sb.toString();
        }

        private String getPolishZipCode (boolean left){
                ZipCodeInfo zip = getZipCodeInfo(left);
                return (zip != null && zip.getZipCode() != null ) ? zip.getZipCode() : "-1";
        }


}