Subversion Repositories mkgmap

Rev

Rev 1650 | 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 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.
 *
 *
 * Author: Steve Ratcliffe
 * Create date: 13-Jul-2008
 */

package uk.me.parabola.mkgmap.general;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.CoordNode;
import uk.me.parabola.imgfmt.app.net.NOD1Part;
import uk.me.parabola.imgfmt.app.net.RoadDef;
import uk.me.parabola.imgfmt.app.net.RouteArc;
import uk.me.parabola.imgfmt.app.net.RouteCenter;
import uk.me.parabola.imgfmt.app.net.RouteNode;
import uk.me.parabola.imgfmt.app.net.RouteRestriction;
import uk.me.parabola.log.Logger;
import uk.me.parabola.util.EnhancedProperties;

/**
 * This holds the road network.  That is all the roads and the nodes
 * that connect them together.
 *
 * @see <a href="http://www.movable-type.co.uk/scripts/latlong.html">Distance / bearing calculations</a>
 * @author Steve Ratcliffe
 */

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

        public static final int NO_EMERGENCY = 0;
        public static final int NO_DELIVERY = 1;
        public static final int NO_CAR = 2;
        public static final int NO_BUS = 3;
        public static final int NO_TAXI = 4;
        public static final int NO_FOOT = 5;
        public static final int NO_BIKE = 6;
        public static final int NO_TRUCK = 7;
        public static final int NO_CARPOOL = 8;
        public static final int NO_MAX = 9;

        private final Map<Long, RouteNode> nodes = new LinkedHashMap<Long, RouteNode>();

        // boundary nodes
        // a node should be in here iff the nodes boundary flag is set
        private final List<RouteNode> boundary = new ArrayList<RouteNode>();
        //private final List<MapRoad> mapRoads = new ArrayList<MapRoad>();

        private final List<RoadDef> roadDefs = new ArrayList<RoadDef>();
        private List<RouteCenter> centers = new ArrayList<RouteCenter>();
        private int adjustTurnHeadings ;
        private boolean checkRoundabouts;
        private boolean checkRoundaboutFlares;
        private int maxFlareLengthRatio ;
        private boolean reportSimilarArcs;
        private boolean outputCurveData;
        private int reportDeadEnds ;

        public void config(EnhancedProperties props) {
                String ath = props.getProperty("adjust-turn-headings");
                if(ath != null) {
                        if(ath.length() > 0)
                                adjustTurnHeadings = Integer.decode(ath);
                        else
                                adjustTurnHeadings = RouteNode.ATH_DEFAULT_MASK;
                }
                checkRoundabouts = props.getProperty("check-roundabouts", false);
                checkRoundaboutFlares = props.getProperty("check-roundabout-flares", false);
                maxFlareLengthRatio = props.getProperty("max-flare-length-ratio", 0);

                reportDeadEnds = props.getProperty("report-dead-ends", 1);

                reportSimilarArcs = props.getProperty("report-similar-arcs", false);

                outputCurveData = !props.getProperty("no-arc-curves", false);
        }

        public void addRoad(MapRoad road) {
                //mapRoads.add(road);
                roadDefs.add(road.getRoadDef()); //XXX

                CoordNode lastCoord = null;
                int lastIndex = 0;
                double roadLength = 0;
                double arcLength = 0;
                int pointsHash = 0;

                List<Coord> coordList = road.getPoints();
                int npoints = coordList.size();
                for (int index = 0; index < npoints; index++) {
                        Coord co = coordList.get(index);

                        if (index > 0) {
                                double d = co.distance(coordList.get(index-1));
                                arcLength += d;
                                roadLength += d;
                        }

                        long id = co.getId();

                        pointsHash += co.hashCode();

                        if (id == 0)
                                // not a routing node
                                continue;

                        // The next coord determines the heading
                        // If this is the not the first node, then create an arc from
                        // the previous node to this one (and back again).
                        if (lastCoord != null) {
                                long lastId = lastCoord.getId();
                                if(log.isDebugEnabled()) {
                                        log.debug("lastId = " + lastId + " curId = " + id);
                                        log.debug("from " + lastCoord.toDegreeString()
                                                          + " to " + co.toDegreeString());
                                        log.debug("arclength=" + arcLength + " roadlength=" + roadLength);
                                }

                                RouteNode node1 = getNode(lastId, lastCoord);
                                RouteNode node2 = getNode(id, co);

                                if(node1 == node2)
                                        log.error("Road " + road.getRoadDef() + " contains consecutive identical nodes at " + co.toOSMURL() + " - routing will be broken");
                                else if(arcLength == 0)
                                        log.error("Road " + road.getRoadDef() + " contains zero length arc at " + co.toOSMURL());


                                Coord bearingPoint = coordList.get(lastIndex + 1);
                                if(lastCoord.equals(bearingPoint)) {
                                        // bearing point is too close to last node to be
                                        // useful - try some more points
                                        for(int bi = lastIndex + 2; bi <= index; ++bi) {
                                                if(!lastCoord.equals(coordList.get(bi))) {
                                                        bearingPoint = coordList.get(bi);
                                                        break;
                                                }
                                        }
                                }
                                int forwardBearing = (int)lastCoord.bearingTo(bearingPoint);
                                int inverseForwardBearing = (int)bearingPoint.bearingTo(lastCoord);

                                bearingPoint = coordList.get(index - 1);
                                if(co.equals(bearingPoint)) {
                                        // bearing point is too close to this node to be
                                        // useful - try some more points
                                        for(int bi = index - 2; bi > lastIndex; --bi) {
                                                if(!co.equals(coordList.get(bi))) {
                                                        bearingPoint = coordList.get(bi);
                                                        break;
                                                }
                                        }
                                }
                                int reverseBearing = (int)co.bearingTo(bearingPoint);
                                int inverseReverseBearing = (int)bearingPoint.bearingTo(co);

                                // Create forward arc from node1 to node2
                                RouteArc arc = new RouteArc(road.getRoadDef(),
                                                                                        node1,
                                                                                        node2,
                                                                                        forwardBearing,
                                                                                        inverseReverseBearing,
                                                                                        arcLength,
                                                                                        outputCurveData,
                                                                                        pointsHash);
                                arc.setForward();
                                node1.addArc(arc);
                                node2.addIncomingArc(arc);

                                // Create the reverse arc
                                arc = new RouteArc(road.getRoadDef(),
                                                                   node2, node1,
                                                                   reverseBearing,
                                                                   inverseForwardBearing,
                                                                   arcLength,
                                                                   outputCurveData,
                                                                   pointsHash);
                                node2.addArc(arc);
                                node1.addIncomingArc(arc);
                        } else {
                                // This is the first node in the road
                                road.getRoadDef().setNode(getNode(id, co));
                        }

                        lastCoord = (CoordNode) co;
                        lastIndex = index;
                        arcLength = 0;
                        pointsHash = co.hashCode();
                }
                road.getRoadDef().setLength(roadLength);
        }

        private RouteNode getNode(long id, Coord coord) {
                RouteNode node = nodes.get(id);
                if (node == null) {
                        node = new RouteNode(coord);
                        nodes.put(id, node);
                        if (node.isBoundary())
                                boundary.add(node);
                }
                return node;
        }

        public List<RoadDef> getRoadDefs() {
                return roadDefs;
        }

        /**
         * Split the network into RouteCenters.
         *
         * The resulting centers must satisfy several constraints,
         * documented in NOD1Part.
         */

        private void splitCenters() {
                if (nodes.isEmpty())
                        return;
                assert centers.isEmpty() : "already subdivided into centers";

                NOD1Part nod1 = new NOD1Part();

                for (RouteNode node : nodes.values()) {
                        if(!node.isBoundary()) {
                                if(checkRoundabouts)
                                        node.checkRoundabouts();
                                if(checkRoundaboutFlares)
                                        node.checkRoundaboutFlares(maxFlareLengthRatio);
                                if(reportSimilarArcs)
                                        node.reportSimilarArcs();
                                if(reportDeadEnds != 0)
                                        node.reportDeadEnds(reportDeadEnds);
                        }
                        if(adjustTurnHeadings != 0)
                                node.tweezeArcs(adjustTurnHeadings);
                        nod1.addNode(node);
                }
                centers = nod1.subdivide();
        }

        public List<RouteCenter> getCenters() {
                if (centers.isEmpty())
                        splitCenters();
                return centers;
        }

        /**
         * Get the list of nodes on the boundary of the network.
         *
         * Currently empty.
         */

        public List<RouteNode> getBoundary() {
                return boundary;
        }

        public void addRestriction(CoordNode fromNode, CoordNode toNode, CoordNode viaNode, byte exceptMask) {
                RouteNode fn = nodes.get(fromNode.getId());
                RouteNode tn = nodes.get(toNode.getId());
                RouteNode vn = nodes.get(viaNode.getId());

                assert fn != null : "can't locate 'from' RouteNode with id " + fromNode.getId();
                assert tn != null : "can't locate 'to' RouteNode with id " + toNode.getId();
                assert vn != null : "can't locate 'via' RouteNode with id " + viaNode.getId();

                RouteArc fa = vn.getArcTo(fn); // inverse arc gets used
                RouteArc ta = vn.getArcTo(tn);

                assert fa != null : "can't locate arc from 'via' node to 'from' node";
                assert ta != null : "can't locate arc from 'via' node to 'to' node";

                vn.addRestriction(new RouteRestriction(fa, ta, exceptMask));
    }

        public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) {
                RouteNode node = nodes.get(junctionNodeId);
                assert node != null :  "Can't find node with id " + junctionNodeId;

                node.addThroughRoute(roadIdA, roadIdB);
        }
}