// License: GPL. For details, see LICENSE file. package com.innovant.josm.jrt.core; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import org.jgrapht.Graph; import org.jgrapht.alg.BellmanFordShortestPath; import org.jgrapht.alg.DijkstraShortestPath; import org.jgrapht.graph.DirectedWeightedMultigraph; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.Way; import com.innovant.josm.jrt.osm.OsmEdge; import com.innovant.josm.plugin.routing.RoutingLayer; import com.innovant.josm.plugin.routing.RoutingModel; /** * Class utility to work with graph routers. * * @author Juangui * @author Jose Vidal * @author Hassan S */ public class RoutingGraph { /** * Routing Profile */ private final RoutingProfile routingProfile; /** * Diferent algorithms to apply to the graph. */ public enum Algorithm { ROUTING_ALG_DIJKSTRA, ROUTING_ALG_BELLMANFORD } /** * Search criteria for the route. */ public enum RouteType { FASTEST, SHORTEST } /** * */ private RouteType routeType; /** * Associated Osm DataSet */ private final DataSet data; /** * Logger. */ static Logger logger = Logger.getLogger(RoutingGraph.class); private static Collection<String> excludedHighwayValues = Arrays.asList(new String[]{ "bus_stop", "traffic_signals", "street_lamp", "stop", "construction", "platform", "give_way", "proposed", "milestone", "speed_camera", "abandoned" }); /** * Graph state * <code>true</code> Graph in memory. * <code>false</code> Graph not created. */ // public boolean graphState; /** * OSM Graph. */ // private DirectedWeightedMultigraph<Node, OsmEdge> graph; // private WeightedMultigraph<Node, OsmEdge> graph; private Graph<Node, OsmEdge> graph; private RoutingGraphDelegator rgDelegator = null; /** * Graph getter */ public Graph<Node, OsmEdge> getGraph() { return graph; } private void addEdgeBidirectional(Way way, Node from, Node to) { addEdge(way, from, to); addEdge(way, to, from); } private void addEdgeReverseOneway(Way way, Node from, Node to) { addEdge(way, to, from); } private void addEdgeNormalOneway(Way way, Node from, Node to) { addEdge(way, from, to); } /** * Speeds */ private Map<String, Double> waySpeeds; /** * Default Constructor. */ public RoutingGraph(DataSet data) { // this.graphState = false; this.graph = null; this.data = data; routeType = RouteType.SHORTEST; routingProfile = new RoutingProfile("default"); routingProfile.setOnewayUse(true); // Don't ignore oneways by default this.setWaySpeeds(routingProfile.getWaySpeeds()); logger.debug("Created RoutingGraph"); } /** * Create OSM graph for routing */ public void createGraph() { logger.debug("Creating Graph..."); graph = new DirectedWeightedMultigraph<>(OsmEdge.class); rgDelegator = new RoutingGraphDelegator(graph); rgDelegator.setRouteType(this.routeType); // iterate all ways and segments for all nodes: for (Way way : data.getWays()) { // skip way if not suitable for routing. if (way == null || way.isDeleted() || !this.isvalidWay(way) || way.getNodes().size() < 1) continue; // INIT Node from = null; Node to = null; List<Node> nodes = way.getNodes(); int nodes_count = nodes.size(); /* * Assume node is A B C D E. The procedure should be * * case 1 - bidirectional ways: * 1) Add vertex A B C D E * 2) Link A<->B, B<->C, C<->D, D<->E as Edges * * case 2 - oneway reverse: * 1) Add vertex A B C D E * 2) Link B->A,C->B,D->C,E->D as Edges. result: A<-B<-C<-D<-E * * case 3 - oneway normal: * 1) Add vertex A B C D E * 2) Link A->B, B->C, C->D, D->E as Edges. result: A->B->C->D->E * * */ String oneway_val = way.get("oneway"); /* get (oneway=?) tag for this way. */ String junction_val = way.get("junction"); /* get (junction=?) tag for this way. */ from = nodes.get(0); /* 1st node A */ graph.addVertex(from); /* add vertex A */ for (int i = 1; i < nodes_count; i++) { /* loop from B until E */ to = nodes.get(i); /* 2nd node B */ if (to != null && !to.isDeleted()) { graph.addVertex(to); /* add vertex B */ //this is where we link the vertices if (!routingProfile.isOnewayUsed()) { //"Ignore oneways" is selected addEdgeBidirectional(way, from, to); } else if (oneway_val == null && junction_val == "roundabout") { //Case (roundabout): oneway=implicit yes addEdgeNormalOneway(way, from, to); } else if (oneway_val == null || oneway_val == "false" || oneway_val == "no" || oneway_val == "0") { //Case (bi-way): oneway=false OR oneway=unset OR oneway=0 OR oneway=no addEdgeBidirectional(way, from, to); } else if (oneway_val == "-1") { //Case (oneway reverse): oneway=-1 addEdgeReverseOneway(way, from, to); } else if (oneway_val == "1" || oneway_val == "yes" || oneway_val == "true") { //Case (oneway normal): oneway=yes OR 1 OR true addEdgeNormalOneway(way, from, to); } from = to; /* we did A<->B, next loop we will do B<->C, so from=B,to=C for next loop. */ } } // end of looping thru nodes } // end of looping thru ways logger.debug("End Create Graph"); logger.debug("Vertex: "+graph.vertexSet().size()); logger.debug("Edges: "+graph.edgeSet().size()); } /** * Compute weight and add edge to the graph */ private void addEdge(Way way, Node from, Node to) { LatLon fromLL = from.getCoor(); LatLon toLL = from.getCoor(); if (fromLL == null || toLL == null) { return; } OsmEdge edge = new OsmEdge(way, from, to); double length = edge.getLength(); edge.setSpeed(12.1); graph.addEdge(from, to, edge); // weight = getWeight(way); double weight = getWeight(way, length); setWeight(edge, length); logger.debug("edge for way " + way.getId() + "(from node " + from.getId() + " to node " + to.getId() + ") has weight: " + weight); ((DirectedWeightedMultigraph<Node, OsmEdge>) graph).setEdgeWeight(edge, weight); } /** * Set the weight for the given segment depending on the highway type * and the length of the segment. The higher the value, the less it is used * in routing. * * @param way * the way. */ private void setWeight(OsmEdge osmedge, double length) { osmedge.setLength(length); if (this.waySpeeds.containsKey(osmedge.getWay().get("highway"))) osmedge.setSpeed(this.waySpeeds.get(osmedge.getWay().get("highway"))); } /** * Returns the weight for the given segment depending on the highway type * and the length of the segment. The higher the value, the less it is used * in routing. * * @param way * the way. */ private double getWeight(Way way, double length) { // Default speed if no setting is found double speed = 1; switch (routeType) { case SHORTEST: // Same speed for all types of ways if (this.waySpeeds.containsKey("residential")) speed = this.waySpeeds.get("residential"); break; case FASTEST: // Each type of way may have a different speed if (this.waySpeeds.containsKey(way.get("highway"))) speed = this.waySpeeds.get(way.get("highway")); logger.debug("Speed="+speed); break; default: break; } // Return the time spent to traverse the way return length / speed; } /** * Check if a Way is correct. * * @param way * The way. * @return <code>true</code> is valid. <code>false</code> is not valid. */ public boolean isvalidWay(Way way) { //if (!way.isTagged()) <---not needed me thinks // return false; String highway = way.get("highway"); return (highway != null && !excludedHighwayValues.contains(highway)) || way.get("junction") != null || way.get("service") != null; } /** * Apply selected routing algorithm to the graph. * * @param nodes * Nodes used to calculate path. * @param algorithm * Algorithm used to compute the path, * RoutingGraph.Algorithm.ROUTING_ALG_DIJKSTRA or * RoutingGraph.Algorithm.ROUTING_ALG_BELLMANFORD * @return new path. */ public List<OsmEdge> applyAlgorithm(List<Node> nodes, Algorithm algorithm) { List<OsmEdge> path = new ArrayList<>(); Graph<Node, OsmEdge> g; double totalWeight = 0; RoutingLayer layer = (RoutingLayer) Main.getLayerManager().getActiveLayer(); RoutingModel routingModel = layer.getRoutingModel(); if (graph == null || routingModel.getOnewayChanged()) this.createGraph(); logger.debug("apply algorithm between nodes "); for (Node node : nodes) { logger.debug(node.getId()); } logger.debug("-----------------------------------"); // Assign the graph to g g = graph; switch (algorithm) { case ROUTING_ALG_DIJKSTRA: logger.debug("Using Dijkstra algorithm"); DijkstraShortestPath<Node, OsmEdge> routingk = null; for (int index = 1; index < nodes.size(); ++index) { routingk = new DijkstraShortestPath<>(g, nodes .get(index - 1), nodes.get(index)); if (routingk.getPathEdgeList() == null) { logger.debug("no path found!"); break; } path.addAll(routingk.getPathEdgeList()); totalWeight += routingk.getPathLength(); } break; case ROUTING_ALG_BELLMANFORD: logger.debug("Using Bellman Ford algorithm"); for (int index = 1; index < nodes.size(); ++index) { path = BellmanFordShortestPath.findPathBetween(rgDelegator, nodes .get(index - 1), nodes.get(index)); if (path == null) { logger.debug("no path found!"); return null; } } break; default: logger.debug("Wrong algorithm"); break; } logger.debug("shortest path found: " + path + "\nweight: " + totalWeight); return path; } /** * Return the number of vertices. * @return the number of vertices. */ public int getVertexCount() { int value = 0; if (graph != null) value = graph.vertexSet().size(); return value; } /** * Return the number of edges. * @return the number of edges. */ public int getEdgeCount() { int value = 0; if (graph != null) value = graph.edgeSet().size(); return value; } /** * @param routeType the routeType to set */ public void setTypeRoute(RouteType routetype) { this.routeType = routetype; this.rgDelegator.setRouteType(routetype); } /** * @return the routeType */ public RouteType getTypeRoute() { return routeType; } public Map<String, Double> getWaySpeeds() { return waySpeeds; } public void setWaySpeeds(Map<String, Double> waySpeeds) { this.waySpeeds = waySpeeds; } public void resetGraph() { graph = null; } public RoutingProfile getRoutingProfile() { return routingProfile; } }