Skip to content

Commit

Permalink
Wheelchair routing profile (#1726)
Browse files Browse the repository at this point in the history
* add a routing profile for wheelchairs

* add an icon for wheelchair routing profile in web frontend

* fixes according to review in PR 1726

removed unnecessary comments and code
changed handlePriority method be valuable for wheelchair
fixed wheelchair svg

* removed unnecessary variables
renamed variables to better reflect their usage
exclude steep incline and high kerbs

* evaluate elevation to set speed and deny ways that are too steep with speed = 0

* removed deprecated get/setSpeed usage

calculate slopes correctly now

make two directions for speed encoder configurable for FootFlagEncoder

* adapt test class to new EncodingManager create method

* master bug fix: use under score

* allow speedTwoDirections for FootFlagEncoder

* clean up test

* minor fix
  • Loading branch information
don-philipe authored and karussell committed Nov 13, 2019
1 parent adcca06 commit 556f3c8
Show file tree
Hide file tree
Showing 8 changed files with 1,023 additions and 19 deletions.
Expand Up @@ -57,6 +57,9 @@ else if (name.equals(CAR4WD))
if (name.equals(MOTORCYCLE))
return new MotorcycleFlagEncoder(configuration);

if (name.equals(WHEELCHAIR))
return new WheelchairFlagEncoder(configuration);

throw new IllegalArgumentException("entry in encoder list not supported " + name);
}
}
Expand Up @@ -32,6 +32,7 @@ public interface FlagEncoderFactory {
String FOOT = "foot";
String HIKE = "hike";
String MOTORCYCLE = "motorcycle";
String WHEELCHAIR = "wheelchair";
String GENERIC = "generic";

FlagEncoder createFlagEncoder(String name, PMap configuration);
Expand Down
Expand Up @@ -53,6 +53,7 @@ public class FootFlagEncoder extends AbstractFlagEncoder {
final Map<String, Integer> hikingNetworkToCode = new HashMap<>();
protected HashSet<String> sidewalkValues = new HashSet<>(5);
protected HashSet<String> sidewalksNoValues = new HashSet<>(5);
protected boolean speedTwoDirections;
private DecimalEncodedValue priorityWayEncoder;
private EncodedValueOld relationCodeEncoder;

Expand All @@ -64,9 +65,10 @@ public FootFlagEncoder() {
}

public FootFlagEncoder(PMap properties) {
this((int) properties.getLong("speedBits", 4),
properties.getDouble("speedFactor", 1));
this((int) properties.getLong("speed_bits", 4),
properties.getDouble("speed_factor", 1));
this.setBlockFords(properties.getBool("block_fords", true));
this.speedTwoDirections = properties.getBool("speed_two_directions", false);
}

public FootFlagEncoder(String propertiesStr) {
Expand Down Expand Up @@ -151,8 +153,8 @@ public void createEncodedValues(List<EncodedValue> registerNewEncodedValue, Stri
// first two bits are reserved for route handling in superclass
super.createEncodedValues(registerNewEncodedValue, prefix, index);
// larger value required - ferries are faster than pedestrians
registerNewEncodedValue.add(avgSpeedEnc = new UnsignedDecimalEncodedValue(getKey(prefix, "average_speed"), speedBits, speedFactor, false));
registerNewEncodedValue.add(priorityWayEncoder = new UnsignedDecimalEncodedValue(getKey(prefix, "priority"), 3, PriorityCode.getFactor(1), false));
registerNewEncodedValue.add(avgSpeedEnc = new UnsignedDecimalEncodedValue(getKey(prefix, "average_speed"), speedBits, speedFactor, speedTwoDirections));
registerNewEncodedValue.add(priorityWayEncoder = new UnsignedDecimalEncodedValue(getKey(prefix, "priority"), 3, PriorityCode.getFactor(1), speedTwoDirections));
}

@Override
Expand Down Expand Up @@ -291,26 +293,18 @@ public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, EncodingManager.A
if (access.canSkip())
return edgeFlags;

accessEnc.setBool(false, edgeFlags, true);
accessEnc.setBool(true, edgeFlags, true);
if (!access.isFerry()) {
String sacScale = way.getTag("sac_scale");
if (sacScale != null) {
if ("hiking".equals(sacScale))
avgSpeedEnc.setDecimal(false, edgeFlags, MEAN_SPEED);
else
avgSpeedEnc.setDecimal(false, edgeFlags, SLOW_SPEED);
setSpeed(edgeFlags, true, true, "hiking".equals(sacScale)? MEAN_SPEED : SLOW_SPEED);
} else {
if (way.hasTag("highway", "steps"))
avgSpeedEnc.setDecimal(false, edgeFlags, MEAN_SPEED - 2);
else
avgSpeedEnc.setDecimal(false, edgeFlags, MEAN_SPEED);
setSpeed(edgeFlags, true, true, way.hasTag("highway", "steps")? MEAN_SPEED - 2 : MEAN_SPEED);
}
accessEnc.setBool(false, edgeFlags, true);
accessEnc.setBool(true, edgeFlags, true);
} else {
double ferrySpeed = getFerrySpeed(way);
setSpeed(false, edgeFlags, ferrySpeed);
accessEnc.setBool(false, edgeFlags, true);
accessEnc.setBool(true, edgeFlags, true);
setSpeed(edgeFlags, true, true, ferrySpeed);
}

int priorityFromRelation = 0;
Expand All @@ -321,6 +315,15 @@ public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, EncodingManager.A
return edgeFlags;
}

void setSpeed(IntsRef edgeFlags, boolean fwd, boolean bwd, double speed) {
if (speed > getMaxSpeed())
speed = getMaxSpeed();
if (fwd)
avgSpeedEnc.setDecimal(false, edgeFlags, speed);
if (bwd && speedTwoDirections)
avgSpeedEnc.setDecimal(true, edgeFlags, speed);
}

protected int handlePriority(ReaderWay way, int priorityFromRelation) {
TreeMap<Double, Integer> weightToPrioMap = new TreeMap<>();
if (priorityFromRelation == 0)
Expand Down
Expand Up @@ -43,8 +43,8 @@ public HikeFlagEncoder() {
}

public HikeFlagEncoder(PMap properties) {
this((int) properties.getLong("speedBits", 4),
properties.getDouble("speedFactor", 1));
this((int) properties.getLong("speed_bits", 4),
properties.getDouble("speed_factor", 1));
this.setBlockFords(properties.getBool("block_fords", false));
}

Expand Down
@@ -0,0 +1,286 @@
/*
* 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.ReaderWay;
import com.graphhopper.storage.IntsRef;
import com.graphhopper.util.EdgeIteratorState;
import com.graphhopper.util.PMap;
import com.graphhopper.util.PointList;

import java.util.HashSet;
import java.util.Set;
import java.util.TreeMap;

import static com.graphhopper.routing.util.PriorityCode.REACH_DEST;
import static com.graphhopper.routing.util.PriorityCode.VERY_NICE;

/**
* A flag encoder for wheelchairs.
*
* @author don-philipe
*/
public class WheelchairFlagEncoder extends FootFlagEncoder {
private final Set<String> excludeSurfaces = new HashSet<>();
private final Set<String> excludeSmoothness = new HashSet<>();
private final Set<String> excludeHighwayTags = new HashSet<>();
private final int maxInclinePercent = 6;

/**
* Should be only instantiated via EncodingManager
*/
public WheelchairFlagEncoder() {
this(4, 1);
}

public WheelchairFlagEncoder(PMap properties) {
this((int) properties.getLong("speed_bits", 4),
properties.getDouble("speed_factor", 1));
this.setBlockFords(properties.getBool("block_fords", true));
}

public WheelchairFlagEncoder(int speedBits, double speedFactor) {
super(speedBits, speedFactor);
restrictions.add("wheelchair");

setBlockByDefault(false);
absoluteBarriers.add("handrail");
absoluteBarriers.add("wall");
absoluteBarriers.add("turnstile");
potentialBarriers.add("kerb");
potentialBarriers.add("cattle_grid");
potentialBarriers.add("motorcycle_barrier");

safeHighwayTags.add("footway");
safeHighwayTags.add("pedestrian");
safeHighwayTags.add("living_street");
safeHighwayTags.add("residential");
safeHighwayTags.add("service");
safeHighwayTags.add("platform");

excludeHighwayTags.add("trunk");
excludeHighwayTags.add("trunk_link");
excludeHighwayTags.add("primary");
excludeHighwayTags.add("primary_link");
excludeHighwayTags.add("secondary");
excludeHighwayTags.add("secondary_link");
excludeHighwayTags.add("tertiary");
excludeHighwayTags.add("tertiary_link");
excludeHighwayTags.add("steps");
excludeHighwayTags.add("track");

excludeSurfaces.add("cobblestone");
excludeSurfaces.add("gravel");
excludeSurfaces.add("sand");

excludeSmoothness.add("bad");
excludeSmoothness.add("very_bad");
excludeSmoothness.add("horrible");
excludeSmoothness.add("very_horrible");
excludeSmoothness.add("impassable");

allowedHighwayTags.addAll(safeHighwayTags);
allowedHighwayTags.addAll(excludeHighwayTags);
allowedHighwayTags.add("cycleway");
allowedHighwayTags.add("unclassified");
allowedHighwayTags.add("road");

maxPossibleSpeed = FERRY_SPEED;
speedDefault = MEAN_SPEED;
speedTwoDirections = true;
init();
}

@Override
public int getVersion() {
return 1;
}

/**
* Avoid some more ways than for pedestrian like hiking trails.
*/
@Override
public EncodingManager.Access getAccess(ReaderWay way) {
if (way.hasTag("wheelchair", intendedValues)) {
return EncodingManager.Access.WAY;
}

if (way.getTag("sac_scale") != null) {
return EncodingManager.Access.CAN_SKIP;
}

if (way.hasTag("highway", excludeHighwayTags) && !way.hasTag("sidewalk", sidewalkValues)) {
return EncodingManager.Access.CAN_SKIP;
}

if (way.hasTag("surface", excludeSurfaces)) {
if (!way.hasTag("sidewalk", sidewalkValues)) {
return EncodingManager.Access.CAN_SKIP;
} else {
String sidewalk = way.getTag("sidewalk");
if (way.hasTag("sidewalk:" + sidewalk + ":surface", excludeSurfaces)) {
return EncodingManager.Access.CAN_SKIP;
}
}
}

if (way.hasTag("smoothness", excludeSmoothness)) {
if (!way.hasTag("sidewalk", sidewalkValues)) {
return EncodingManager.Access.CAN_SKIP;
} else {
String sidewalk = way.getTag("sidewalk");
if (way.hasTag("sidewalk:" + sidewalk + ":smoothness", excludeSmoothness)) {
return EncodingManager.Access.CAN_SKIP;
}
}
}

if (way.hasTag("incline")) {
String tagValue = way.getTag("incline");
if (tagValue.endsWith("%") || tagValue.endsWith("°")) {
try {
double incline = Double.parseDouble(tagValue.substring(0, tagValue.length() - 1));
if (tagValue.endsWith("°")) {
incline = Math.tan(incline * Math.PI / 180) * 100;
}

if (-maxInclinePercent > incline || incline > maxInclinePercent) {
return EncodingManager.Access.CAN_SKIP;
}
} catch (NumberFormatException ex) {
}
}
}

if (way.hasTag("kerb", "raised")) {
return EncodingManager.Access.CAN_SKIP;
}

if (way.hasTag("kerb")) {
String tagValue = way.getTag("kerb");
if (tagValue.endsWith("cm") || tagValue.endsWith("mm")) {
try {
float kerbHeight = Float.parseFloat(tagValue.substring(0, tagValue.length() - 2));
if (tagValue.endsWith("mm")) {
kerbHeight /= 100;
}

int maxKerbHeightCm = 3;
if (kerbHeight > maxKerbHeightCm) {
return EncodingManager.Access.CAN_SKIP;
}
} catch (NumberFormatException ex) {
}
}
}

return super.getAccess(way);
}

@Override
public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, EncodingManager.Access access, long relationFlags) {
if (access.canSkip()) {
return edgeFlags;
}

accessEnc.setBool(false, edgeFlags, true);
accessEnc.setBool(true, edgeFlags, true);
if (!access.isFerry()) {
setSpeed(edgeFlags, true, true, MEAN_SPEED);
} else {
double ferrySpeed = getFerrySpeed(way);
setSpeed(edgeFlags, true, true, ferrySpeed);
}

return edgeFlags;
}

/**
* Calculate slopes from elevation data and set speed according to that. In-/declines between smallInclinePercent
* and maxInclinePercent will reduce speed to SLOW_SPEED. In-/declines above maxInclinePercent will result in zero
* speed.
*/
@Override
public void applyWayTags(ReaderWay way, EdgeIteratorState edge) {
PointList pl = edge.fetchWayGeometry(3);

double prevEle = pl.getElevation(0);
double fullDist2D = edge.getDistance();

if (Double.isInfinite(fullDist2D)) {
throw new IllegalStateException("Infinite distance should not happen due to #435. way ID=" + way.getId());
}

if (fullDist2D < 1) {
return;
}

double eleDelta = pl.getElevation(pl.size() - 1) - prevEle;
double elePercent = eleDelta / fullDist2D * 100;
int smallInclinePercent = 3;
if (elePercent > smallInclinePercent && elePercent < maxInclinePercent) {
setFwdBwdSpeed(edge, SLOW_SPEED, MEAN_SPEED);
} else if (elePercent < -smallInclinePercent && elePercent > -maxInclinePercent) {
setFwdBwdSpeed(edge, MEAN_SPEED, SLOW_SPEED);
} else if (elePercent > maxInclinePercent || elePercent < -maxInclinePercent) {
edge.set(accessEnc, false);
edge.setReverse(accessEnc, false);
}
}

/**
* Sets the given speed values to the given edge depending on the forward and backward accessibility of the edge.
*
* @param edge the edge to set speed for
* @param fwdSpeed speed value in forward direction
* @param bwdSpeed speed value in backward direction
*/
private void setFwdBwdSpeed(EdgeIteratorState edge, int fwdSpeed, int bwdSpeed) {
if (edge.get(accessEnc))
edge.set(avgSpeedEnc, fwdSpeed);

if (edge.getReverse(accessEnc))
edge.setReverse(avgSpeedEnc, bwdSpeed);
}

/**
* First get priority from {@link FootFlagEncoder#handlePriority(ReaderWay, int)} then evaluate wheelchair specific
* tags.
*
* @return a priority for the given way
*/
@Override
protected int handlePriority(ReaderWay way, int priorityFromRelation) {
TreeMap<Double, Integer> weightToPrioMap = new TreeMap<>();

weightToPrioMap.put(100d, super.handlePriority(way, priorityFromRelation));

if (way.hasTag("wheelchair", "designated")) {
weightToPrioMap.put(102d, VERY_NICE.getValue());
} else if (way.hasTag("wheelchair", "limited")) {
weightToPrioMap.put(102d, REACH_DEST.getValue());
}

return weightToPrioMap.lastEntry().getValue();
}

@Override
public String toString() {
return "wheelchair";
}
}

0 comments on commit 556f3c8

Please sign in to comment.