/* * 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.util; import com.graphhopper.reader.ReaderRelation; import com.graphhopper.reader.ReaderWay; import com.graphhopper.routing.util.spatialrules.*; import com.graphhopper.routing.weighting.GenericWeighting; import com.graphhopper.util.*; import com.graphhopper.util.shapes.GHPoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.Map.Entry; /** * This encoder tries to store all way information into a 32 or 64bit value. Later extendable to * multiple ints or bytes. The assumption is that edge.getFlags is cheap and can be later replaced * by e.g. one or more (cheap) calls of edge.getData(index). * <p> * Currently limited to motor vehicle but later could handle different modes like foot or bike too. * * @author Peter Karich */ public class DataFlagEncoder extends AbstractFlagEncoder { private static final Logger LOG = LoggerFactory.getLogger(DataFlagEncoder.class); private static final Map<String, Double> DEFAULT_SPEEDS = new LinkedHashMap<String, Double>() { { put("motorway", 100d); put("motorway_link", 70d); put("motorroad", 90d); put("trunk", 70d); put("trunk_link", 65d); put("primary", 65d); put("primary_link", 60d); put("secondary", 60d); put("secondary_link", 50d); put("tertiary", 50d); put("tertiary_link", 40d); put("unclassified", 30d); put("residential", 30d); put("living_street", 5d); put("service", 20d); put("road", 20d); put("forestry", 15d); put("track", 15d); } }; private final Map<String, Integer> surfaceMap = new HashMap<>(); private final Map<String, Integer> highwayMap = new HashMap<>(); private final Map<String, Integer> accessMap = new HashMap<>(); private final List<String> transportModeList = new ArrayList<>(); private final Map<String, Integer> transportModeMap = new HashMap<>(); private final int transportModeTunnelValue; private final int transportModeBridgeValue; private final int transportModeFordValue; private long bit0; private EncodedDoubleValue carFwdMaxspeedEncoder; private EncodedDoubleValue carBwdMaxspeedEncoder; private EncodedDoubleValue heightEncoder; private EncodedDoubleValue weightEncoder; private EncodedDoubleValue widthEncoder; private EncodedValue surfaceEncoder; private EncodedValue highwayEncoder; private EncodedValue transportModeEncoder; private EncodedValue accessEncoder; private boolean storeHeight = false; private boolean storeWeight = false; private boolean storeWidth = false; private EncodedValue spatialEncoder; private SpatialRuleLookup spatialRuleLookup = SpatialRuleLookup.EMPTY; public DataFlagEncoder() { this(5, 5, 0); } public DataFlagEncoder(PMap properties) { this((int) properties.getLong("speed_bits", 5), properties.getDouble("speed_factor", 5), properties.getBool("turn_costs", false) ? 1 : 0); this.properties = properties; this.setStoreHeight(properties.getBool("store_height", false)); this.setStoreWeight(properties.getBool("store_weight", false)); this.setStoreWidth(properties.getBool("store_width", false)); } public DataFlagEncoder(int speedBits, double speedFactor, int maxTurnCosts) { // TODO include turn information super(speedBits, speedFactor, maxTurnCosts); maxPossibleSpeed = 140; // // TODO restrictions (agricultural, emergency, destination, private, delivery, customers) // // highway and certain tags like ferry and shuttle_train which can be used here (no logical overlap) List<String> highwayList = Arrays.asList( /* reserve index=0 for unset roads (not accessible) */ "_default", "motorway", "motorway_link", "motorroad", "trunk", "trunk_link", "primary", "primary_link", "secondary", "secondary_link", "tertiary", "tertiary_link", "unclassified", "residential", "living_street", "service", "road", "track", "forestry", "cycleway", "steps", "path", "footway", "pedestrian", "ferry", "shuttle_train"); int counter = 0; for (String hw : highwayList) { highwayMap.put(hw, counter++); } // We need transport mode additionally to highway e.g. a secondary highway can be a tunnel. // Also 'roundabout' needs a separate bit as a tunnel or a bridge can be a roundabout at the same time. transportModeList.addAll(Arrays.asList("_default", "bridge", "tunnel", "ford", "aerialway")); counter = 0; for (String tm : transportModeList) { transportModeMap.put(tm, counter++); } transportModeTunnelValue = transportModeMap.get("tunnel"); transportModeBridgeValue = transportModeMap.get("bridge"); transportModeFordValue = transportModeMap.get("ford"); List<String> surfaceList = Arrays.asList("_default", "asphalt", "unpaved", "paved", "gravel", "ground", "dirt", "grass", "concrete", "paving_stones", "sand", "compacted", "cobblestone", "mud", "ice"); counter = 0; for (String s : surfaceList) { surfaceMap.put(s, counter++); } restrictions.addAll(Arrays.asList("motorcar", "motor_vehicle", "vehicle", "access")); // Ordered in increasingly restrictive order // Note: if you update this list you have to update the method getAccessValue too List<String> accessList = Arrays.asList( //"designated", "permissive", "customers", "delivery", "yes", "destination", "private", "no" ); counter = 0; for (String s : accessList) { accessMap.put(s, counter++); } accessMap.put("designated", accessMap.get("yes")); accessMap.put("permissive", accessMap.get("yes")); accessMap.put("customers", accessMap.get("destination")); accessMap.put("delivery", accessMap.get("destination")); // accessMap.put("forestry", accessMap.get("agricultural")); } @Override public int defineWayBits(int index, int shift) { // TODO use this approach in other flag encoders too then we can do a global swap for all and bit0 can be at position 0! bit0 = 1L << shift; shift++; // TODO support different vehicle types, currently just roundabout and fwd&bwd for one vehicle type shift = super.defineWayBits(index, shift); carFwdMaxspeedEncoder = new EncodedDoubleValue("car fwd maxspeed", shift, speedBits, speedFactor, 0, maxPossibleSpeed, true); shift += carFwdMaxspeedEncoder.getBits(); carBwdMaxspeedEncoder = new EncodedDoubleValue("car bwd maxspeed", shift, speedBits, speedFactor, 0, maxPossibleSpeed, true); shift += carBwdMaxspeedEncoder.getBits(); /* Value range: [3.0m, 5.4m] */ if (isStoreHeight()) { heightEncoder = new EncodedDoubleValue("height restriction", shift, 7, 0.1, 0, 12, true); shift += heightEncoder.getBits(); } /* Value range: [1.0t, 59.5t] */ if (isStoreWeight()) { weightEncoder = new EncodedDoubleValue("weight restriction", shift, 10, 0.1, 0, 100, true); shift += weightEncoder.getBits(); } /* Value range: [2.5m, 3.5m] */ if (isStoreWidth()) { widthEncoder = new EncodedDoubleValue("width restriction", shift, 6, 0.1, 0, 6, true); shift += widthEncoder.getBits(); } highwayEncoder = new EncodedValue("highway", shift, 5, 1, 0, highwayMap.size(), true); shift += highwayEncoder.getBits(); surfaceEncoder = new EncodedValue("surface", shift, 4, 1, 0, surfaceMap.size(), true); shift += surfaceEncoder.getBits(); transportModeEncoder = new EncodedValue("transport mode", shift, 3, 1, 0, transportModeMap.size(), true); shift += transportModeEncoder.getBits(); accessEncoder = new EncodedValue("access car", shift, 3, 1, 1, 4, true); shift += accessEncoder.getBits(); int tmpMax = spatialRuleLookup.size()-1; int bits = 32 - Integer.numberOfLeadingZeros(tmpMax); spatialEncoder = new EncodedValue("spatial_location", shift, bits, 1, 0, tmpMax, true); shift += spatialEncoder.getBits(); return shift; } @Override public long handleRelationTags(ReaderRelation relation, long oldRelationFlags) { return 0; } @Override public long acceptWay(ReaderWay way) { // important to skip unsupported highways, otherwise too many have to be removed after graph creation // and node removal is not yet designed for that if (getHighwayValue(way) == 0) return 0; return acceptBit; } int getHighwayValue(ReaderWay way) { String highwayValue = way.getTag("highway"); Integer hwValue = highwayMap.get(highwayValue); if (way.hasTag("impassable", "yes") || way.hasTag("status", "impassable")) hwValue = 0; if (hwValue == null) { hwValue = 0; if (way.hasTag("route", ferries)) { String motorcarTag = way.getTag("motorcar"); if (motorcarTag == null) motorcarTag = way.getTag("motor_vehicle"); if (motorcarTag == null && !way.hasTag("foot") && !way.hasTag("bicycle") || "yes".equals(motorcarTag)) hwValue = highwayMap.get("ferry"); } } return hwValue; } int getAccessValue(ReaderWay way) { int accessValue = 0; Integer tmpAccessValue; for (String restriction : restrictions) { tmpAccessValue = accessMap.get(way.getTag(restriction, "yes")); if (tmpAccessValue != null && tmpAccessValue > accessValue) { accessValue = tmpAccessValue; } } if (accessValue == 0) { // TODO Fix transportation mode when adding other forms of transportation switch (getSpatialRule(way).getAccessValue(way.getTag("highway", ""), TransportationMode.MOTOR_VEHICLE, AccessValue.ACCESSIBLE)) { case ACCESSIBLE: accessValue = accessMap.get("yes"); break; case EVENTUALLY_ACCESSIBLE: accessValue = accessMap.get("destination"); break; case NOT_ACCESSIBLE: accessValue = accessMap.get("no"); break; } } return accessValue; } public AccessValue getAccessValue(long flags) { int accessValue = (int) accessEncoder.getValue(flags); switch (accessValue) { case 0: return AccessValue.ACCESSIBLE; // NOT_ACCESSIBLE_KEY case 3: return AccessValue.NOT_ACCESSIBLE; default: return AccessValue.EVENTUALLY_ACCESSIBLE; } } @Override public long handleWayTags(ReaderWay way, long allowed, long relationFlags) { if (!isAccept(allowed)) return 0; try { // HIGHWAY int hwValue = getHighwayValue(way); // exclude any routing like if you have car and need to exclude all rails or ships if (hwValue == 0) return 0; long flags = 0; if (isFerry(allowed)) { hwValue = highwayMap.get("ferry"); } flags = highwayEncoder.setValue(0, hwValue); // MAXSPEED double maxSpeed = parseSpeed(way.getTag("maxspeed")); if (maxSpeed < 0) { // TODO What if no maxspeed is set, but only forward and backward, and both are higher than the usually allowed? maxSpeed = getSpatialRule(way).getMaxSpeed(way.getTag("highway", ""), maxSpeed); } double fwdSpeed = parseSpeed(way.getTag("maxspeed:forward")); if (fwdSpeed < 0 && maxSpeed > 0) fwdSpeed = maxSpeed; if (fwdSpeed > getMaxPossibleSpeed()) fwdSpeed = getMaxPossibleSpeed(); double bwdSpeed = parseSpeed(way.getTag("maxspeed:backward")); if (bwdSpeed < 0 && maxSpeed > 0) bwdSpeed = maxSpeed; if (bwdSpeed > getMaxPossibleSpeed()) bwdSpeed = getMaxPossibleSpeed(); // 0 is reserved for default i.e. no maxspeed sign (does not imply no speed limit) // TODO and 140 should be used for "none" speed limit on German Autobahn if (fwdSpeed > 0) flags = carFwdMaxspeedEncoder.setDoubleValue(flags, fwdSpeed); if (bwdSpeed > 0) flags = carBwdMaxspeedEncoder.setDoubleValue(flags, bwdSpeed); // Road attributes (height, weight, width) if (isStoreHeight()) { List<String> heightTags = Arrays.asList("maxheight", "maxheight:physical"); flags = extractMeter(way, flags, heightEncoder, heightTags); } if (isStoreWeight()) { List<String> weightTags = Arrays.asList("maxweight", "maxgcweight"); flags = extractTons(way, flags, weightEncoder, weightTags); } if (isStoreWidth()) { List<String> widthTags = Arrays.asList("maxwidth", "maxwidth:physical"); flags = extractMeter(way, flags, widthEncoder, widthTags); } // SURFACE String surfaceValue = way.getTag("surface"); Integer sValue = surfaceMap.get(surfaceValue); if (sValue == null) sValue = 0; flags = surfaceEncoder.setValue(flags, sValue); // TRANSPORT MODE int tmValue = 0; for (String tm : transportModeList) { if (way.hasTag(tm)) { tmValue = transportModeMap.get(tm); break; } } flags = transportModeEncoder.setValue(flags, tmValue); // ROUNDABOUT boolean isRoundabout = way.hasTag("junction", "roundabout"); if (isRoundabout) flags = setBool(flags, K_ROUNDABOUT, true); // ONEWAY (currently car only) boolean isOneway = way.hasTag("oneway", oneways) || way.hasTag("vehicle:backward") || way.hasTag("vehicle:forward") || way.hasTag("motor_vehicle:backward") || way.hasTag("motor_vehicle:forward"); if (isOneway || isRoundabout) { boolean isBackward = way.hasTag("oneway", "-1") || way.hasTag("vehicle:forward", "no") || way.hasTag("motor_vehicle:forward", "no"); if (isBackward) flags |= backwardBit; else flags |= forwardBit; } else flags |= directionBitMask; if (!isBit0Empty(flags)) throw new IllegalStateException("bit0 has to be empty on creation"); flags = accessEncoder.setValue(flags, getAccessValue(way)); GHPoint estimatedCenter = way.getTag("estimated_center", null); if (estimatedCenter != null) { SpatialRule rule = spatialRuleLookup.lookupRule(estimatedCenter); flags = spatialEncoder.setValue(flags, spatialRuleLookup.getSpatialId(rule)); } return flags; } catch (Exception ex) { throw new RuntimeException("Error while parsing way " + way.toString(), ex); } } private SpatialRule getSpatialRule(ReaderWay way) { GHPoint estmCentre = way.getTag("estimated_center", null); if (estmCentre != null) { return spatialRuleLookup.lookupRule(estmCentre); } return SpatialRule.EMPTY; } private long extractMeter(ReaderWay way, long flags, EncodedDoubleValue valueEncoder, List<String> keys) { String value = way.getFirstPriorityTag(keys); if (Helper.isEmpty(value)) return flags; double val; try { val = stringToMeter(value); } catch (Exception ex) { LOG.warn("Unable to extract meter from malformed road attribute '{}' for way (OSM_ID = {}).", value, way.getId(), ex); return flags; } try { flags = valueEncoder.setDoubleValue(flags, val); } catch (IllegalArgumentException e) { LOG.warn("Unable to process value '{}' for way (OSM_ID = {}).", val, way.getId(), e); } return flags; } private long extractTons(ReaderWay way, long flags, EncodedDoubleValue valueEncoder, List<String> keys) { String value = way.getFirstPriorityTag(keys); if (Helper.isEmpty(value)) return flags; double val; try { val = stringToTons(value); } catch (Throwable t) { LOG.warn("Unable to extract tons from malformed road attribute '{}' for way (OSM_ID = {}).", value, way.getId(), t); return flags; } try { flags = valueEncoder.setDoubleValue(flags, val); } catch (IllegalArgumentException e) { LOG.warn("Unable to process tons value '{}' for way (OSM_ID = {}).", val, way.getId(), e); } return flags; } public static double stringToTons(String value) { value = value.toLowerCase().replaceAll(" ", "").replaceAll("(tons|ton)", "t"); value = value.replace("mgw", "").trim(); double factor = 1; if (value.endsWith("t")) { value = value.substring(0, value.length() - 1); } else if (value.endsWith("lbs")) { value = value.substring(0, value.length() - 3); factor = 0.00045359237; } return Double.parseDouble(value) * factor; } public static double stringToMeter(String value) { value = value.toLowerCase().replaceAll(" ", "").replaceAll("(meters|meter|mtrs|mtr|mt|m\\.)", "m"); double factor = 1; double offset = 0; value = value.replaceAll("(\\\"|\'\')", "in").replaceAll("(\'|feet)", "ft"); if (value.startsWith("~") || value.contains("approx")) { value = value.replaceAll("(\\~|approx)", "").trim(); factor = 0.8; } if (value.endsWith("in")) { int startIndex = value.indexOf("ft"); String inchValue; if (startIndex < 0) { startIndex = 0; } else { startIndex += 2; } inchValue = value.substring(startIndex, value.length() - 2); value = value.substring(0, startIndex); offset = Double.parseDouble(inchValue) * 0.0254; } if (value.endsWith("ft")) { value = value.substring(0, value.length() - 2); factor *= 0.3048; } else if (value.endsWith("m")) { value = value.substring(0, value.length() - 1); } if (value.isEmpty()) { return offset; } else { return Double.parseDouble(value) * factor + offset; } } /** * This method returns the spatialId stored in the specified flags or -1 if not enabled for this encoder. */ public int getSpatialId(long flags) { if (spatialEncoder == null) return -1; return (int) spatialEncoder.getValue(flags); } /** * This method set the spatial ID (e.g. country ID) of the specified flags to the specified id. Fetch the unique * spatial ID via spatialRuleLookup.lookup().getSpatialId */ public long setSpatialId(long flags, int id) { return spatialEncoder.setValue(flags, id); } @Override public long reverseFlags(long flags) { // see #728 for an explanation return flags ^ bit0; } /** * Interpret flags in forward direction if bit0 is empty. This method is used when accessing * direction dependent values and avoid reverse flags, see #728. */ private boolean isBit0Empty(long flags) { return (flags & bit0) == 0; } public int getHighway(EdgeIteratorState edge) { return (int) highwayEncoder.getValue(edge.getFlags()); } /** * Do not use within weighting as this is suboptimal from performance point of view. */ public String getHighwayAsString(EdgeIteratorState edge) { int val = getHighway(edge); for (Entry<String, Integer> e : highwayMap.entrySet()) { if (e.getValue() == val) return e.getKey(); } return null; } public double[] getHighwaySpeedMap(Map<String, Double> map) { if (map == null) throw new IllegalArgumentException("Map cannot be null when calling getHighwaySpeedMap"); double[] res = new double[highwayMap.size()]; for (Entry<String, Double> e : map.entrySet()) { Integer integ = highwayMap.get(e.getKey()); if (integ == null) throw new IllegalArgumentException("Graph not prepared for highway=" + e.getKey()); if (e.getValue() < 0) throw new IllegalArgumentException("Negative speed " + e.getValue() + " not allowed. highway=" + e.getKey()); res[integ] = e.getValue(); } return res; } public int getSurface(EdgeIteratorState edge) { return (int) surfaceEncoder.getValue(edge.getFlags()); } public String getSurfaceAsString(EdgeIteratorState edge) { int val = getSurface(edge); for (Entry<String, Integer> e : surfaceMap.entrySet()) { if (e.getValue() == val) return e.getKey(); } return null; } public int getTransportMode(EdgeIteratorState edge) { return (int) transportModeEncoder.getValue(edge.getFlags()); } public boolean isTransportModeTunnel(EdgeIteratorState edge) { return transportModeEncoder.getValue(edge.getFlags()) == this.transportModeTunnelValue; } public boolean isTransportModeBridge(EdgeIteratorState edge) { return transportModeEncoder.getValue(edge.getFlags()) == this.transportModeBridgeValue; } public boolean isTransportModeFord(long flags) { return transportModeEncoder.getValue(flags) == this.transportModeFordValue; } public String getTransportModeAsString(EdgeIteratorState edge) { int val = getTransportMode(edge); for (Entry<String, Integer> e : transportModeMap.entrySet()) { if (e.getValue() == val) return e.getKey(); } return null; } public double[] getTransportModeMap(Map<String, Double> map) { double[] res = new double[transportModeMap.size()]; for (Entry<String, Double> e : map.entrySet()) { Integer integ = transportModeMap.get(e.getKey()); if (integ == null) throw new IllegalArgumentException("Graph not prepared for transport_mode=" + e.getKey()); if (e.getValue() < 0) throw new IllegalArgumentException("Negative speed " + e.getValue() + " not allowed. transport_mode=" + e.getKey()); res[integ] = e.getValue(); } return res; } public boolean isRoundabout(EdgeIteratorState edge) { // use direct call instead of isBool return (edge.getFlags() & roundaboutBit) != 0; } public int getAccessType(String accessStr) { // access, motor_vehicle, bike, foot, hgv, bus return 0; } public final boolean isForward(EdgeIteratorState edge, int accessType) { // TODO shift dependent on the accessType // use only one bit for foot? long flags = edge.getFlags(); return (flags & (isBit0Empty(flags) ? forwardBit : backwardBit)) != 0; } @Override public final boolean isForward(long flags) { // TODO remove old method return (flags & (isBit0Empty(flags) ? forwardBit : backwardBit)) != 0; } public final boolean isBackward(EdgeIteratorState edge, int accessType) { long flags = edge.getFlags(); return (flags & (isBit0Empty(flags) ? backwardBit : forwardBit)) != 0; } @Override public final boolean isBackward(long flags) { // TODO remove old method return (flags & (isBit0Empty(flags) ? backwardBit : forwardBit)) != 0; } public double getMaxspeed(EdgeIteratorState edge, int accessType, boolean reverse) { long flags = edge.getFlags(); if (!isBit0Empty(flags)) reverse = !reverse; double val; if (reverse) val = carBwdMaxspeedEncoder.getDoubleValue(flags); else val = carFwdMaxspeedEncoder.getDoubleValue(flags); if (val < 0) throw new IllegalStateException("maxspeed cannot be negative, edge:" + edge.getEdge() + ", access type" + accessType + ", reverse:" + reverse); // default is 0 but return invalid speed explicitely (TODO can we do this at the value encoder level?) if (val == 0) return -1; return val; } public double getHeight(EdgeIteratorState edge) { long flags = edge.getFlags(); return heightEncoder.getDoubleValue(flags); } public double getWeight(EdgeIteratorState edge) { long flags = edge.getFlags(); return weightEncoder.getDoubleValue(flags); } public double getWidth(EdgeIteratorState edge) { long flags = edge.getFlags(); return widthEncoder.getDoubleValue(flags); } @Override public long flagsDefault(boolean forward, boolean backward) { // just pick car mode to set access values? // throw new RuntimeException("do not call flagsDefault"); return setAccess(0, forward, backward); } @Override public long setAccess(long flags, boolean forward, boolean backward) { // TODO we should interpret access for *any* vehicle // TODO in subnetwork we need to remove access for certain weighting profiles (or set of roads?) boolean isForward = isBit0Empty(flags); if (!isForward) { boolean tmp = forward; forward = backward; backward = tmp; } flags = forward ? flags | forwardBit : flags & ~forwardBit; flags = backward ? flags | backwardBit : flags & ~backwardBit; return flags; } @Override public long setSpeed(long flags, double speed) { throw new RuntimeException("do not call setSpeed"); } @Override protected long setLowSpeed(long flags, double speed, boolean reverse) { throw new RuntimeException("do not call setLowSpeed"); } @Override public double getSpeed(long flags) { throw new UnsupportedOperationException("Calculate speed via more customizable Weighting.calcMillis method"); } @Override public long setReverseSpeed(long flags, double speed) { throw new RuntimeException("do not call setReverseSpeed"); } @Override public double getReverseSpeed(long flags) { throw new RuntimeException("do not call getReverseSpeed"); } @Override public long setProperties(double speed, boolean forward, boolean backward) { throw new RuntimeException("do not call setProperties"); } @Override protected double getMaxSpeed(ReaderWay way) { throw new RuntimeException("do not call getMaxSpeed(ReaderWay)"); } @Override public double getMaxSpeed() { throw new RuntimeException("do not call getMaxSpeed"); } public double getMaxPossibleSpeed() { return maxPossibleSpeed; } @Override public boolean supports(Class<?> feature) { boolean ret = super.supports(feature); if (ret) return true; return GenericWeighting.class.isAssignableFrom(feature); } public DataFlagEncoder setStoreHeight(boolean storeHeight) { this.storeHeight = storeHeight; return this; } public boolean isStoreHeight() { return storeHeight; } public DataFlagEncoder setStoreWeight(boolean storeWeight) { this.storeWeight = storeWeight; return this; } public boolean isStoreWeight() { return storeWeight; } public DataFlagEncoder setStoreWidth(boolean storeWidth) { this.storeWidth = storeWidth; return this; } public boolean isStoreWidth() { return storeWidth; } public DataFlagEncoder setSpatialRuleLookup(SpatialRuleLookup spatialRuleLookup) { this.spatialRuleLookup = spatialRuleLookup; return this; } @Override public InstructionAnnotation getAnnotation(long flags, Translation tr) { if (isTransportModeFord(flags)) { return new InstructionAnnotation(1, tr.tr("way_contains_ford")); } return super.getAnnotation(flags, tr); } @Override protected String getPropertiesString() { return super.getPropertiesString() + "|store_height=" + storeHeight + "|store_weight=" + storeWeight + "|store_width=" + storeWidth; } @Override public int getVersion() { return 3; } @Override public String toString() { return "generic"; } /** * This method creates a Config map out of the PMap. Later on this conversion should not be * necessary when we read JSON. */ public ConfigMap readStringMap(PMap weightingMap) { Map<String, Double> map = new HashMap<>(); for (Entry<String, Double> e : DEFAULT_SPEEDS.entrySet()) { map.put(e.getKey(), weightingMap.getDouble("highways." + e.getKey(), e.getValue())); } ConfigMap cMap = new ConfigMap(); cMap.put("highways", map); cloneDoubleAttribute(weightingMap, cMap, GenericWeighting.HEIGHT_LIMIT, 0d); cloneDoubleAttribute(weightingMap, cMap, GenericWeighting.WEIGHT_LIMIT, 0d); cloneDoubleAttribute(weightingMap, cMap, GenericWeighting.WIDTH_LIMIT, 0d); return cMap; } private void cloneDoubleAttribute(PMap weightingMap, ConfigMap cMap, String key, double _default) { if (weightingMap.has(key)) cMap.put(key, weightingMap.getDouble(key, _default)); } }