HeavyVehicleGraphStorageBuilder.java
/* This file is part of Openrouteservice.
*
* Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
* This library 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 Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License along with this library;
* if not, see <https://www.gnu.org/licenses/>.
*/
package org.heigit.ors.routing.graphhopper.extensions.storages.builders;
import com.graphhopper.GraphHopper;
import com.graphhopper.reader.ReaderWay;
import com.graphhopper.storage.GraphExtension;
import com.graphhopper.util.EdgeIteratorState;
import com.graphhopper.util.Helper;
import org.heigit.ors.routing.graphhopper.extensions.HeavyVehicleAttributes;
import org.heigit.ors.routing.graphhopper.extensions.VehicleDimensionRestrictions;
import org.heigit.ors.routing.graphhopper.extensions.storages.HeavyVehicleAttributesGraphStorage;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HeavyVehicleGraphStorageBuilder extends AbstractGraphStorageBuilder {
private boolean includeRestrictions = true;
private HeavyVehicleAttributesGraphStorage storage;
private int hgvType = 0;
private int hgvDestination = 0;
private boolean hasRestrictionValues;
private final double[] restrictionValues = new double[VehicleDimensionRestrictions.COUNT];
private final List<String> motorVehicleRestrictions = new ArrayList<>(5);
private final Set<String> motorVehicleRestrictedValues = new HashSet<>(5);
private final Set<String> motorVehicleHgvValues = new HashSet<>(6);
private final Set<String> noValues = new HashSet<>(5);
private final Set<String> yesValues = new HashSet<>(5);
private final Pattern patternDimension;
public HeavyVehicleGraphStorageBuilder() {
motorVehicleRestrictions.addAll(Arrays.asList("motorcar", "motor_vehicle", "vehicle", "access"));
motorVehicleRestrictedValues.add("private");
motorVehicleRestrictedValues.add("no");
motorVehicleRestrictedValues.add("restricted");
motorVehicleRestrictedValues.add("military");
motorVehicleHgvValues.addAll(Arrays.asList("hgv", "goods", "bus", "agricultural", "forestry", "delivery"));
noValues.addAll(Arrays.asList("no", "private"));
yesValues.addAll(Arrays.asList("yes", "designated"));
patternDimension = Pattern.compile("(?:\\s*(\\d+)\\s*(?:feet|ft\\.|ft|'))?(?:(\\d+)\\s*(?:inches|in\\.|in|''|\"))?");
}
public GraphExtension init(GraphHopper graphhopper) throws Exception {
if (storage != null)
throw new Exception("GraphStorageBuilder has been already initialized.");
if (parameters != null) {
String value = parameters.get("restrictions");
if (!Helper.isEmpty(value))
includeRestrictions = Boolean.parseBoolean(value);
}
storage = new HeavyVehicleAttributesGraphStorage(includeRestrictions);
return storage;
}
public void processWay(ReaderWay way) {
// reset values
hgvType = 0;
hgvDestination = 0;
if (hasRestrictionValues) {
restrictionValues[0] = 0.0;
restrictionValues[1] = 0.0;
restrictionValues[2] = 0.0;
restrictionValues[3] = 0.0;
restrictionValues[4] = 0.0;
hasRestrictionValues = false;
}
boolean hasHighway = way.hasTag("highway");
if (hasHighway) {
// process motor vehicle restrictions before any more specific vehicle type tags which override the former
// if there are any generic motor vehicle restrictions restrict all types...
if (way.hasTag(motorVehicleRestrictions, motorVehicleRestrictedValues))
hgvType = HeavyVehicleAttributes.ANY;
//...or all but the explicitly listed ones
if (way.hasTag(motorVehicleRestrictions, motorVehicleHgvValues)) {
int flag = 0;
for (String key : motorVehicleRestrictions) {
String[] values = way.getTagValues(key);
for (String val : values) {
if (motorVehicleHgvValues.contains(val))
flag |= HeavyVehicleAttributes.getFromString(val);
}
}
hgvType = HeavyVehicleAttributes.ANY & ~flag;
}
Iterator<Entry<String, Object>> it = way.getProperties();
while (it.hasNext()) {
Map.Entry<String, Object> pairs = it.next();
String key = pairs.getKey();
String value = pairs.getValue().toString();
/*
* https://wiki.openstreetmap.org/wiki/Restrictions
*/
int valueIndex = -1;
switch (key) {
case "maxheight" -> valueIndex = VehicleDimensionRestrictions.MAX_HEIGHT;
case "maxweight", "maxweight:hgv" -> valueIndex = VehicleDimensionRestrictions.MAX_WEIGHT;
case "maxwidth" -> valueIndex = VehicleDimensionRestrictions.MAX_WIDTH;
case "maxlength", "maxlength:hgv" -> valueIndex = VehicleDimensionRestrictions.MAX_LENGTH;
case "maxaxleload" -> valueIndex = VehicleDimensionRestrictions.MAX_AXLE_LOAD;
default -> {
}
}
// given tag is a weight/dimension restriction
if (valueIndex >= 0 && includeRestrictions && !("none".equals(value) || "default".equals(value))) {
double parsedValue = -1;
// sanitize decimal separators
if (value.contains(","))
value = value.replace(',', '.');
// weight restrictions
if (valueIndex == VehicleDimensionRestrictions.MAX_WEIGHT || valueIndex == VehicleDimensionRestrictions.MAX_AXLE_LOAD) {
if (value.contains("t")) {
value = value.replace('t', ' ');
} else if (value.contains("lbs")) {
value = value.replace("lbs", " ");
parsedValue = parseDouble(value) / 2204.622;
}
}
// dimension restrictions
else {
if (value.contains("m")) {
value = value.replace('m', ' ');
} else {
Matcher m = patternDimension.matcher(value);
if (m.matches() && m.lookingAt()) {
double feet = parseDouble(m.group(1));
double inches = 0;
if (m.groupCount() > 1 && m.group(2) != null) {
inches = parseDouble(m.group(2));
}
parsedValue = feet * 0.3048 + inches * 0.0254;
}
}
}
if (parsedValue == -1)
parsedValue = parseDouble(value);
// it was possible to extract a reasonable value
if (parsedValue > 0) {
restrictionValues[valueIndex] = parsedValue;
hasRestrictionValues = true;
}
}
if (motorVehicleHgvValues.contains(key)) {
//TODO: account for <vehicle_type>:[forward/backward] keys
//TODO: allow access:<vehicle_type> as described in #703. Might be necessary to adjust the upstream PBF parsing part as well.
String vehicleType = getVehicleType(key, value);
String accessValue = getVehicleAccess(vehicleType, value);
setAccessFlags(vehicleType, accessValue);
if (vehicleType.equals(value))// e.g. hgv=delivery implies that hgv other than delivery vehicles are blocked
setAccessFlags(key, "no");
} else if (key.equals("hazmat") && "no".equals(value)) {
hgvType |= HeavyVehicleAttributes.HAZMAT;
}
}
}
}
public void processEdge(ReaderWay way, EdgeIteratorState edge) {
storage.setEdgeValue(edge.getEdge(), hgvType, hgvDestination, restrictionValues);
}
private String getVehicleType(String key, String value) {
return motorVehicleHgvValues.contains(value) ? value : key;// hgv=[delivery/agricultural/forestry]
}
private String getVehicleAccess(String vehicleType, String value) {
if (vehicleType.equals(value) || yesValues.contains(value))
return "yes";
else if (noValues.contains(value))
return "no";
return null;
}
/**
* Toggle on/off the bit corresponding to a given hgv type defined by {@code flag} inside binary restriction masks
* based on the value of {@code tag}. "no" sets the bit in {@code _hgvType}, while "yes" unsets it.
*
* @param vehicle a String describing one of the vehicle types defined in {@code HeavyVehicleAttributes}
* @param access a String describing the access restriction
*/
private void setAccessFlags(String vehicle, String access) {
int flag = HeavyVehicleAttributes.getFromString(vehicle);
if (access != null) {
if ("no".equals(access))
hgvType |= flag;
else if ("yes".equals(access))
hgvType &= ~flag;
}
}
private double parseDouble(String str) {
double d;
try {
d = Double.parseDouble(str);
} catch (NumberFormatException e) {
d = 0.0;
}
return d;
}
@Override
public String getName() {
return "HeavyVehicle";
}
}