/* * Licensed to GraphHopper GmbH under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. * * GraphHopper GmbH licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.graphhopper.routing.weighting; import com.graphhopper.coll.GHIntHashSet; import com.graphhopper.routing.util.DataFlagEncoder; import com.graphhopper.storage.Graph; import com.graphhopper.storage.GraphEdgeIdFinder; import com.graphhopper.storage.NodeAccess; import com.graphhopper.util.ConfigMap; import com.graphhopper.util.EdgeIteratorState; import com.graphhopper.util.Parameters.Routing; import com.graphhopper.util.shapes.Shape; import java.util.Collections; import java.util.List; /** * Calculates the best route according to a configurable weighting. * * @author Peter Karich */ public class GenericWeighting extends AbstractWeighting { public static final String HEIGHT_LIMIT = "height"; public static final String WEIGHT_LIMIT = "weight"; public static final String WIDTH_LIMIT = "width"; /** * Convert to milliseconds for correct calcMillis. */ protected final static double SPEED_CONV = 3600; protected final double headingPenalty; protected final long headingPenaltyMillis; protected final double maxSpeed; protected final DataFlagEncoder gEncoder; protected final double[] speedArray; protected final int accessType; protected final int eventuallAccessiblePenalty = 10; protected final double height; protected final double weight; protected final double width; private final GHIntHashSet blockedEdges; private final List<Shape> blockedShapes; private NodeAccess na; public GenericWeighting(DataFlagEncoder encoder, ConfigMap cMap) { super(encoder); gEncoder = encoder; headingPenalty = cMap.getDouble(Routing.HEADING_PENALTY, Routing.DEFAULT_HEADING_PENALTY); headingPenaltyMillis = Math.round(headingPenalty * 1000); speedArray = gEncoder.getHighwaySpeedMap(cMap.getMap("highways", Double.class)); double tmpSpeed = 0; for (double speed : speedArray) { if (speed > tmpSpeed) tmpSpeed = speed; } if (tmpSpeed > encoder.getMaxPossibleSpeed()) throw new IllegalArgumentException("Speed bigger than maximum speed: " + tmpSpeed + " > " + encoder.getMaxPossibleSpeed()); maxSpeed = tmpSpeed / SPEED_CONV; accessType = gEncoder.getAccessType("motor_vehicle"); blockedEdges = cMap.get(GraphEdgeIdFinder.BLOCKED_EDGES, new GHIntHashSet(0)); blockedShapes = cMap.get(GraphEdgeIdFinder.BLOCKED_SHAPES, Collections.EMPTY_LIST); height = cMap.getDouble(HEIGHT_LIMIT, 0d); weight = cMap.getDouble(WEIGHT_LIMIT, 0d); width = cMap.getDouble(WIDTH_LIMIT, 0d); } @Override public double getMinWeight(double distance) { return distance / maxSpeed; } @Override public double calcWeight(EdgeIteratorState edgeState, boolean reverse, int prevOrNextEdgeId) { // handle oneways and removed edges via subnetwork removal (existing and allowed highway tags but 'island' edges) if (reverse) { if (!gEncoder.isBackward(edgeState, accessType)) return Double.POSITIVE_INFINITY; } else if (!gEncoder.isForward(edgeState, accessType)) return Double.POSITIVE_INFINITY; if ((gEncoder.isStoreHeight() && overLimit(height, gEncoder.getHeight(edgeState))) || (gEncoder.isStoreWeight() && overLimit(weight, gEncoder.getWeight(edgeState))) || (gEncoder.isStoreWidth() && overLimit(width, gEncoder.getWidth(edgeState)))) return Double.POSITIVE_INFINITY; if (!blockedEdges.isEmpty() && blockedEdges.contains(edgeState.getEdge())) { return Double.POSITIVE_INFINITY; } if (!blockedShapes.isEmpty() && na != null) { for (Shape shape : blockedShapes) { if (shape.contains(na.getLatitude(edgeState.getAdjNode()), na.getLongitude(edgeState.getAdjNode()))) { return Double.POSITIVE_INFINITY; } } } long time = calcMillis(edgeState, reverse, prevOrNextEdgeId); if (time == Long.MAX_VALUE) return Double.POSITIVE_INFINITY; switch (gEncoder.getAccessValue(edgeState.getFlags())) { case NOT_ACCESSIBLE: return Double.POSITIVE_INFINITY; case EVENTUALLY_ACCESSIBLE: time = time * eventuallAccessiblePenalty; } return time; } private boolean overLimit(double height, double heightLimit) { return height > 0 && heightLimit > 0 && height >= heightLimit; } @Override public long calcMillis(EdgeIteratorState edgeState, boolean reverse, int prevOrNextEdgeId) { // TODO to avoid expensive reverse flags include oneway accessibility // but how to include e.g. maxspeed as it depends on direction? Does highway depend on direction? // reverse = edge.isReverse()? !reverse : reverse; int highwayVal = gEncoder.getHighway(edgeState); double speed = speedArray[highwayVal]; if (speed < 0) throw new IllegalStateException("speed was negative? " + edgeState.getEdge() + ", highway:" + highwayVal + ", reverse:" + reverse); if (speed == 0) return Long.MAX_VALUE; // TODO inner city guessing -> lit, maxspeed <= 50, residential etc => create new encoder.isInnerCity(edge) // See #472 use edge.getDouble((encoder), K_MAXSPEED_MOTORVEHICLE_FORWARD, _default) or edge.getMaxSpeed(...) instead? // encoder could be made optional via passing to EdgeExplorer double maxspeed = gEncoder.getMaxspeed(edgeState, accessType, reverse); if (maxspeed > 0 && speed > maxspeed) speed = maxspeed; // TODO test performance difference for rounding long timeInMillis = (long) (edgeState.getDistance() / speed * SPEED_CONV); // add direction penalties at start/stop/via points boolean unfavoredEdge = edgeState.getBool(EdgeIteratorState.K_UNFAVORED_EDGE, false); if (unfavoredEdge) timeInMillis += headingPenaltyMillis; // TODO avoid a certain (or multiple) bounding boxes (less efficient for just a few edges) or a list of edgeIDs (not good for large areas) // bbox.contains(nodeAccess.getLatitude(edge.getBaseNode()), nodeAccess.getLongitude(edge.getBaseNode())) time+=avoidPenalty; // TODO surfaces can reduce average speed // TODO prefer or avoid bike and hike routes if (timeInMillis < 0) throw new IllegalStateException("Some problem with weight calculation: time:" + timeInMillis + ", speed:" + speed); return timeInMillis; } @Override public String getName() { return "generic"; } /** * Use this method to associate a graph with this weighting to calculate e.g. node locations too. */ public void setGraph(Graph graph) { if (graph != null) this.na = graph.getNodeAccess(); } }