Subversion Repositories mkgmap

Rev

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

/*
 * Copyright (C) 2010 - 2012.
 *
 * 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.reader.osm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.Exit;
import uk.me.parabola.log.Logger;
import uk.me.parabola.util.ElementQuadTree;
import uk.me.parabola.util.EnhancedProperties;

/**
 * Operations mostly on highways that have to be performed during reading
 * the OSM input file.
 *
 * Some of this would be much better done in a style file or by extending the style system.
 */

public class HighwayHooks implements OsmReadingHooks {
        private static final Logger log = Logger.getLogger(HighwayHooks.class);

        private final List<Element> motorways = new ArrayList<>(); // will all be Ways
        private final List<Element> exits = new ArrayList<>();

        private boolean makeOppositeCycleways;
        private ElementSaver saver;
        private boolean linkPOIsToWays;

        private Node currentNodeInWay;

        @Override
        public boolean init(ElementSaver saver, EnhancedProperties props, Style style) {
                this.saver = saver;
                if(props.getProperty("make-all-cycleways", false)) {
                        log.error("option make-all-cycleways is deprecated, please use make-opposite-cycleways");
                        makeOppositeCycleways = true;
                }
                else {
                        makeOppositeCycleways = props.getProperty("make-opposite-cycleways", false);
                }
               
                linkPOIsToWays = props.getProperty("link-pois-to-ways", false);
                currentNodeInWay = null;

                return true;
        }

        @Override
        public Set<String> getUsedTags() {
                Set<String> usedTags = new HashSet<>(Arrays.asList("highway", "access", "barrier", "oneway", "junction", "name",
                                Exit.TAG_ROAD_REF, "ref", "motorroad"));
                if (makeOppositeCycleways) {
                        // need the additional tags
                        usedTags.add("cycleway");
                        usedTags.add("bicycle");
                        usedTags.add("oneway:bicycle");
                        usedTags.add("bicycle:oneway");
                        usedTags.add("cycleway:left");
                        usedTags.add("cycleway:right");
                }
                return usedTags;
        }
       
        @Override
        public void onAddNode(Node node) {
                String highway = node.getTag("highway");
                if (highway != null && ("motorway_junction".equals(highway) || "services".equals(highway) || "rest_area".equals(highway))) {
                        exits.add(node);
                        node.addTag("mkgmap:osmid", String.valueOf(node.getId()));
                }
        }

        @Override
        public void onNodeAddedToWay(Way way, long id) {
                if (!linkPOIsToWays)
                        return;
                       
                currentNodeInWay = saver.getNode(id);
                if (currentNodeInWay == null)
                        return;
                Coord co = currentNodeInWay.getLocation();
               
                // if this Coord is also a POI, replace it with an
                // equivalent CoordPOI that contains a reference to
                // the POI's Node so we can access the POI's tags
                if (!(co instanceof CoordPOI)) {
                        // for now, only do this for nodes that have
                        // certain tags otherwise we will end up creating
                        // a CoordPOI for every node in the way
                        final String[] coordPOITags = { "barrier", "highway" };
                        for (String cpt : coordPOITags) {
                                if (currentNodeInWay.getTag(cpt) != null) {
                                        // the POI has one of the approved tags so
                                        // replace the Coord with a CoordPOI
                                        CoordPOI cp = new CoordPOI(co);
                                        saver.addPoint(id, cp);

                                        // we also have to jump through hoops to
                                        // make a new version of Node because we
                                        // can't replace the Coord that defines
                                        // its location
                                        Node newNode = new Node(id, cp);
                                        newNode.copyTags(currentNodeInWay);
                                        saver.addNode(newNode);
                                        // tell the CoordPOI what node it's
                                        // associated with
                                        cp.setNode(newNode);
                                        co = cp;
                                        // if original node is in exits, replace it
                                        if (exits.remove(currentNodeInWay))
                                                exits.add(newNode);
                                        currentNodeInWay = newNode;
                                        break;
                                }
                        }
                }

                if (co instanceof CoordPOI) {
                        // flag this Way as having a CoordPOI so it
                        // will be processed later
                        way.addTag("mkgmap:way-has-pois", "true");
                        if (log.isInfoEnabled())
                                log.info("link-pois-to-ways: Linking POI", currentNodeInWay.toBrowseURL(), "to way at", co.toOSMURL());
                }
        }

        @Override
        public void onAddWay(Way way) {
                String highway = way.getTag("highway");
                if (highway != null) {
                        // if the way is a roundabout but isn't already
                        // flagged as "oneway", flag it here
                        if ("roundabout".equals(way.getTag("junction")) && way.getTag("oneway") == null) {
                                way.addTag("oneway", "yes");
                        }

                        if (makeOppositeCycleways && !"cycleway".equals(highway)){
                                String onewayTag = way.getTag("oneway");
                                if (way.tagIsLikeYes("oneway") || "-1".equals(onewayTag)){
                                        String cycleway = way.getTag("cycleway");
                                        // we have a oneway street, check if it allows bicycles to travel in opposite direction
                                        if ("no".equals(way.getTag("oneway:bicycle"))
                                                        || "no".equals(way.getTag("bicycle:oneway"))
                                                        || "opposite".equals(cycleway) || "opposite_lane".equals(cycleway)
                                                        || "opposite_track".equals(cycleway)
                                                        || "opposite_lane".equals(way.getTag("cycleway:left"))
                                                        || "opposite_lane".equals(way.getTag("cycleway:right"))
                                                        || "opposite_track".equals(way.getTag("cycleway:left"))
                                                        || "opposite_track".equals(way.getTag("cycleway:right"))) {
                                                way.addTag("mkgmap:make-cycle-way", "yes");
                                        }
                                }
                        }
                }

                if ("motorway".equals(highway) || "trunk".equals(highway) || "primary".equals(highway) || way.tagIsLikeYes("motorroad"))
                        motorways.add(way);
                else if (linkPOIsToWays && ("services".equals(highway) || "rest_area".equals(highway))) {
                        exits.add(way);
                        way.addTag("mkgmap:osmid", String.valueOf(way.getId()));
                        }
        }

        @Override
        public void end() {
                finishExits();
                exits.clear();
                motorways.clear();
        }

        private static final int XTRA = 150; // very approx 300m
        private void finishExits() {
                if (exits.isEmpty() || motorways.isEmpty())
                        return;
                ElementQuadTree majorRoads = new ElementQuadTree(saver.getBoundingBox(), motorways);
                for (Element e : exits) {
                        String refTag = Exit.TAG_ROAD_REF;
                        if (e.getTag(refTag) == null) {
                                String exitName = e.getTag("name");
                                if (exitName == null)
                                        exitName = e.getTag("ref");

                                String ref = null;
                                Way motorway = null;
                                Area bBox;
                                if (e instanceof Node)
                                        bBox = Area.getBBox(Collections.singletonList(((Node) e).getLocation()));
                                else
                                        bBox = Area.getBBox(((Way) e).getPoints());
                                String highway = e.getTag("highway");
                                final boolean isServices = "services".equals(highway) || "rest_area".equals(highway);
                                if (isServices) // services will be just off the road, so increase size
                                        bBox = new Area(bBox.getMinLat() - XTRA, bBox.getMinLong() - XTRA, bBox.getMaxLat() + XTRA, bBox.getMaxLong() + XTRA);
                                List<Way> possibleRoads = new ArrayList<>();
                                for (Element w : majorRoads.get(bBox)) {
                                        motorway = (Way) w;
                                        ref = motorway.getTag("ref");
                                        if (ref != null) {
                                                if (isServices) {
                                                        possibleRoads.add(motorway);  // save all possibilities
                                                } else { // probably on 2+ roads, save possibilities to find the more major road (doesn't have to be motorway)
                                                        if (!(e instanceof Node))
                                                                log.warn("Motorway exit", exitName, "expected to be a Node", e);
                                                        else if (motorway.getPoints().contains(((Node) e).getLocation()))
                                                                possibleRoads.add(motorway);
                                                }
                                        }
                                }
                               
                                if (possibleRoads.size() > 1) {
                                        if (isServices) { // pick the closest road
                                                Coord serviceCoord;
                                                if (e instanceof Node)
                                                        serviceCoord = ((Node) e).getLocation();
                                                else
                                                        // Simple-minded logic to see if a Way that probably defines [part of] a
                                                        // services area, hence might become a POI if option --link-pois-to-ways,
                                                        // is near the specified road.
                                                        // Just pick an arbitary Coord on the services boundary and find the nearest
                                                        // any Coord in the roads.
                                                        // No need to check if it is near the line between far-apart Coords because
                                                        // there also needs to be a junction so that can get off the road to the
                                                        // services.
                                                        serviceCoord = ((Way)e).getFirstPoint();
                                                long closestRoad = Long.MAX_VALUE;
                                                for (Way road : possibleRoads) {
                                                        long closestCoord = Long.MAX_VALUE;
                                                        for (Coord pointOnRoad : road.getPoints()) {
                                                                long dist = pointOnRoad.distanceInHighPrecSquared(serviceCoord);
                                                                if (dist < closestCoord)
                                                                        closestCoord = dist;
                                                        }
                                                        if (closestCoord < closestRoad) {
                                                                closestRoad = closestCoord;
                                                                motorway = road;
                                                                ref = motorway.getTag("ref");
                                                        }
                                                }
                                        } else { // pick the most major road
                                                int bestRoad = Integer.MAX_VALUE;
                                                for (Way road : possibleRoads) {
                                                        String roadType = road.getTag("highway");
                                                        int thisRoad = 4;
                                                        if ("motorway".equals(roadType))
                                                                thisRoad = 0;
                                                        else if (road.tagIsLikeYes("motorroad"))
                                                                thisRoad = 1;
                                                        else if ("trunk".equals(roadType))
                                                                thisRoad = 2;
                                                        else if ("primary".equals(roadType))
                                                                thisRoad = 3;
                                                        if (thisRoad < bestRoad) {
                                                                bestRoad = thisRoad;
                                                                motorway = road;
                                                                ref = motorway.getTag("ref");
                                                        }
                                                }
                                        }
                                        //log.info("Exit", exit, possibleRoads.size(), "options, chosen:", motorway, ref);
                                } // else 0 or 1 road; ref/motorway null or set correctly
                                if (ref != null) {
                                        log.info("Adding", refTag + "=" + ref, "to exit", exitName);
                                        e.addTag(refTag, ref);
                                } else if(motorway != null) {
                                        log.warn("Motorway exit", exitName, "is positioned on a motorway that doesn't have a 'ref' tag", e);
                                }
                        }
                }
        }
}