WheelchairFlagEncoder.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.flagencoders;
import com.graphhopper.reader.ReaderNode;
import com.graphhopper.reader.ReaderWay;
import com.graphhopper.routing.util.EncodingManager;
import com.graphhopper.storage.IntsRef;
import com.graphhopper.util.PMap;
import org.apache.log4j.Logger;
import org.heigit.ors.routing.graphhopper.extensions.reader.osmfeatureprocessors.OSMAttachedSidewalkProcessor;
import org.heigit.ors.routing.graphhopper.extensions.reader.osmfeatureprocessors.OSMPedestrianProcessor;
import org.heigit.ors.routing.graphhopper.extensions.util.PriorityCode;
import java.util.HashSet;
import java.util.Set;
import static com.graphhopper.routing.ev.RouteNetwork.*;
import static org.heigit.ors.routing.graphhopper.extensions.util.PriorityCode.*;
public class WheelchairFlagEncoder extends FootFlagEncoder {
public static final String KEY_HORSE = "horse";
private static final Logger LOGGER = Logger.getLogger(WheelchairFlagEncoder.class.getName());
public static final int MEAN_SPEED = 4;
public static final String KEY_WHEELCHAIR = "wheelchair";
public static final String KEY_FOOTWAY = "footway";
public static final String KEY_PEDESTRIAN = "pedestrian";
public static final String KEY_LIVING_STREET = "living_street";
public static final String KEY_BRIDLEWAY = "bridleway";
public static final String KEY_SIDEWALK = "sidewalk";
public static final String KEY_HIGHWAY = "highway";
public static final String KEY_ROUTE = "route";
public static final String KEY_BICYCLE = "bicycle";
public static final String KEY_DESIGNATED = "designated";
public static final String KEY_OFFICIAL = "official";
public static final String KEY_CROSSING = "crossing";
public static final String KEY_CYCLEWAY = "cycleway";
public static final String KEY_SURFACE = "surface";
public static final String KEY_SMOOTHNESS = "smoothness";
public static final String KEY_TRACKTYPE = "tracktype";
public static final String DEBUG_MSG_SKIPPED = "way skipped (%s): %d - Tags: %s";
private double problematicSpeedFactor = 1;
private double preferredSpeedFactor = 1;
private final OSMAttachedSidewalkProcessor osmAttachedSidewalkProcessor = new OSMAttachedSidewalkProcessor();
private final OSMPedestrianProcessor osmPedestrianProcessor = new OSMPedestrianProcessor();
protected Set<String> acceptedPublicTransport = new HashSet<>(5);
/**
* Fully suitable for wheelchair users
*/
private final Set<String> fullyWheelchairAccessibleHighways = new HashSet<>();
/**
* Suitable for wheelchair users. However highways falling into this category that explicitly indicate a sidewalk is available will be prefered
*/
private final Set<String> assumedWheelchairAccessibleHighways = new HashSet<>();
/**
* Highways that fall into this category will only be considered if further information about surface/smoothness is available
*/
private final Set<String> limitedWheelchairAccessibleHighways = new HashSet<>();
/**
* Highways that fall into this category will only be considered if further information about surface/smoothness is available
*/
private final Set<String> restrictedWheelchairHighways = new HashSet<>();
/**
* Highways that fall into this category cannot be accessed by Wheelchair users (e.g. steps)
*/
private final Set<String> nonWheelchairAccessibleHighways = new HashSet<>();
/**
* Surfaces that should be avoided
*/
private final Set<String> problematicSurfaces = new HashSet<>();
/**
* Surfaces that are absolutely inaccessible
*/
private final Set<String> inaccessibleSurfaces = new HashSet<>();
/**
* Smoothnesses that are absolutely inaccessible
*/
private final Set<String> inaccessibleSmoothnesses = new HashSet<>();
/**
* Smoothnesses that should be avoided
*/
private final Set<String> problematicSmoothnesses = new HashSet<>();
/**
* preferred Surfaces
*/
private final Set<String> preferredSurfaces = new HashSet<>();
/**
* preferred Smoothnesses
*/
private final Set<String> preferredSmoothnesses = new HashSet<>();
/**
* Tracktypes that are absolutely inaccessible
*/
private final Set<String> inaccessibleTracktypes = new HashSet<>();
/**
* SAC scales that are absolutely inaccessible
*/
private final Set<String> inaccessibleSacScales = new HashSet<>();
/**
* Tracktypes that should be avoided
*/
private final Set<String> problematicTracktypes = new HashSet<>();
/**
* Barriers (nodes) that are not accessible. Routes that would these nodes are not possible.
*/
private final Set<String> inaccessibleBarriers = new HashSet<>(5);
private final Set<String> accessibilityRelatedAttributes = new HashSet<>();
public WheelchairFlagEncoder(PMap configuration) {
this(configuration.getInt("speed_bits", 4),
configuration.getDouble("speed_factor", 1));
problematicSpeedFactor = configuration.getDouble("problematic_speed_factor", 1);
preferredSpeedFactor = configuration.getDouble("preferred_speed_factor", 1);
setProperties(configuration);
}
/**
* Should be only instantiated via EncodingManager
*/
public WheelchairFlagEncoder() {
this(4, 1);
}
public WheelchairFlagEncoder(int speedBits, double speedFactor) {
super(speedBits, speedFactor);
// test for the following restriction keys
restrictions.add(KEY_WHEELCHAIR);
intendedValues.add("limited");
// http://wiki.openstreetmap.org/wiki/Key:barrier
// http://taginfo.openstreetmap.org/keys/?key=barrier#values
blockByDefaultBarriers.add("fence");
blockByDefaultBarriers.add("wall");
blockByDefaultBarriers.add("hedge");
blockByDefaultBarriers.add("retaining_wall");
blockByDefaultBarriers.add("city_wall");
blockByDefaultBarriers.add("ditch");
blockByDefaultBarriers.add("hedge_bank");
blockByDefaultBarriers.add("guard_rail");
blockByDefaultBarriers.add("wire_fence");
blockByDefaultBarriers.add("embankment");
// http://wiki.openstreetmap.org/wiki/Key:barrier
// http://taginfo.openstreetmap.org/keys/?key=barrier#values
// potential barriers do not block, if no further information is available
passByDefaultBarriers.add("gate");
passByDefaultBarriers.add("bollard");
passByDefaultBarriers.add("lift_gate");
passByDefaultBarriers.add("cycle_barrier");
passByDefaultBarriers.add("entrance");
passByDefaultBarriers.add("cattle_grid");
passByDefaultBarriers.add("swing_gate");
passByDefaultBarriers.add("chain");
passByDefaultBarriers.add("bump_gate");
// add these to absolute barriers
inaccessibleBarriers.add("stile");
inaccessibleBarriers.add("block");
inaccessibleBarriers.add("kissing_gate");
inaccessibleBarriers.add("turnstile");
inaccessibleBarriers.add("hampshire_gate");
acceptedPublicTransport.add("platform");
// acceptedPublicTransport.add("halt"); --> usually describes a building, not a platform
// acceptedPublicTransport.add("station"); --> usually describes a building, not a platform
// acceptedPublicTransport.add("subway_entrance"); --> usually describes a sub entrance (building), not a platform
// acceptedPublicTransport.add("tram_stop"); --> usually describes the stop itself, not the platform
// include funicular, rail, light_rail, subway, narrow_gauge, aerialway=cablecar (tram is already included via AbstractFlagEncoder) with high costs?
// --> this would be multi-modal routing, which is currently discouraged for ORS
// fully wheelchair accessible, needs double check with tracktype, surface, smoothness
fullyWheelchairAccessibleHighways.add(KEY_FOOTWAY); // fußweg, separat modelliert
fullyWheelchairAccessibleHighways.add(KEY_PEDESTRIAN); // fußgängerzone
fullyWheelchairAccessibleHighways.add(KEY_LIVING_STREET); //spielstraße
fullyWheelchairAccessibleHighways.add("residential"); // Straße im Wohngebiet
fullyWheelchairAccessibleHighways.add("unclassified"); // unklassifizierter Fahrweg, meistens schmal
fullyWheelchairAccessibleHighways.add("service"); // Zufahrtsweg
fullyWheelchairAccessibleHighways.add("tertiary"); // Kreisstraße
fullyWheelchairAccessibleHighways.add("tertiary_link"); // Kreisstraßenabfahrt
fullyWheelchairAccessibleHighways.add("road"); // neue Straße, Klassifizierung bisher unklar
assumedWheelchairAccessibleHighways.add("trunk"); // Schnellstraße
assumedWheelchairAccessibleHighways.add("trunk_link"); // Schnellstraßenabfahrt
assumedWheelchairAccessibleHighways.add("primary"); // Bundesstraße
assumedWheelchairAccessibleHighways.add("primary_link"); //Bundessstraßenabfahrt
assumedWheelchairAccessibleHighways.add("secondary"); // Staatsstraße
assumedWheelchairAccessibleHighways.add("secondary_link"); // Staatsstraßenabfahrt
// potentially not suitable for wheelchair users
limitedWheelchairAccessibleHighways.add("path"); // Wanderweg
limitedWheelchairAccessibleHighways.add("track"); // Feldweg
limitedWheelchairAccessibleHighways.add(KEY_BRIDLEWAY); // Reitweg
// highways that are not suitable for wheelchair users
nonWheelchairAccessibleHighways.add("steps"); // Treppen
nonWheelchairAccessibleHighways.add("construction"); // Baustellen
// attributes to be checked for limited wheelchair accessible highways
accessibilityRelatedAttributes.add(KEY_SURFACE);
accessibilityRelatedAttributes.add(KEY_SMOOTHNESS);
accessibilityRelatedAttributes.add(KEY_TRACKTYPE);
accessibilityRelatedAttributes.add("incline");
accessibilityRelatedAttributes.add("sloped_curb");
accessibilityRelatedAttributes.add("sloped_kerb");
// inaccessible SAC scales
inaccessibleSacScales.add("mountain_hiking");
inaccessibleSacScales.add("demanding_mountain_hiking");
inaccessibleSacScales.add("alpine_hiking");
inaccessibleSacScales.add("demanding_alpine_hiking");
inaccessibleSacScales.add("difficult_alpine_hiking");
// fill Set of inaccessible Surfaces etc
problematicSurfaces.add("cobblestone");
problematicSurfaces.add("unhewn_cobblestone");
problematicSurfaces.add("sett");
problematicSurfaces.add("unpaved");
problematicSurfaces.add("gravel");
problematicSurfaces.add("compacted");
problematicSurfaces.add("pebblestone");
problematicSurfaces.add("grass_paver");
problematicSurfaces.add("woodchips");
inaccessibleSurfaces.add("earth");
inaccessibleSurfaces.add("grass");
inaccessibleSurfaces.add("dirt");
inaccessibleSurfaces.add("mud");
inaccessibleSurfaces.add("sand");
inaccessibleSurfaces.add("snow");
inaccessibleSurfaces.add("ice");
inaccessibleSurfaces.add("salt");
preferredSurfaces.add("asphalt");
preferredSurfaces.add("paved");
problematicSmoothnesses.add("intermediate");
inaccessibleSmoothnesses.add("bad");
inaccessibleSmoothnesses.add("very_bad");
inaccessibleSmoothnesses.add("horrible");
inaccessibleSmoothnesses.add("very_horrible");
preferredSmoothnesses.add("excellent");
problematicTracktypes.add("grade2");
problematicTracktypes.add("grade3");
inaccessibleTracktypes.add("grade4");
inaccessibleTracktypes.add("grade5");
routeMap.put(INTERNATIONAL, PREFER.getValue());
routeMap.put(NATIONAL, PREFER.getValue());
routeMap.put(REGIONAL, PREFER.getValue());
routeMap.put(LOCAL, PREFER.getValue());
routeMap.put(OTHER, PREFER.getValue());
}
@Override
public double getMeanSpeed() {
return MEAN_SPEED;
}
/**
* Some ways are okay but not separate for pedestrians.
*/
@Override
public EncodingManager.Access getAccess(ReaderWay way) {
// check access restrictions
if (way.hasTag(restrictions, restrictedValues) && !(way.hasTag(restrictions, intendedValues) || way.hasTag(KEY_SIDEWALK, usableSidewalkValues))) {
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("access restrictions", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
String highwayValue = way.getTag(KEY_HIGHWAY);
if (highwayValue == null) {
// ferries and shuttle_trains
if (way.hasTag(KEY_ROUTE, ferries)) {
// check whether information on wheelchair accessbility is available
if (way.hasTag(KEY_WHEELCHAIR)) {
// wheelchair=yes, designated, official, permissive, limited
if (way.hasTag(KEY_WHEELCHAIR, intendedValues)) {
return EncodingManager.Access.FERRY;
}
// wheelchair=no, restricted, private
if (way.hasTag(KEY_WHEELCHAIR, restrictedValues)) {
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("no wheelchair ferry", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
}
if (way.hasTag("foot")) {
// foot=yes, designated, official, permissive, limited
if (way.hasTag("foot", intendedValues)) {
return EncodingManager.Access.FERRY;
}
// foot=no, restricted, private
if (way.hasTag("foot", restrictedValues)) {
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("no pedestrian ferry", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
}
return EncodingManager.Access.WAY;
}
// public transport in general
// railways (platform, station)
if (way.hasTag("public_transport", acceptedPublicTransport) || way.hasTag("railway", acceptedPublicTransport)) {
// check whether information on wheelchair accessbility is available
if (way.hasTag(KEY_WHEELCHAIR)) {
// wheelchair=yes, designated, official, permissive, limited
if (way.hasTag(KEY_WHEELCHAIR, intendedValues)) {
return EncodingManager.Access.WAY;
}
// wheelchair=no, restricted, private
if (way.hasTag(KEY_WHEELCHAIR, restrictedValues)) {
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("no wheelchair public transport", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
}
if (way.hasTag("foot")) {
// foot=yes, designated, official, permissive, limited
if (way.hasTag("foot", intendedValues)) {
return EncodingManager.Access.WAY;
}
// foot=no, restricted, private
if (way.hasTag("foot", restrictedValues)) {
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("no pedestrian public transport", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
}
return EncodingManager.Access.WAY;
}
// no highway, no ferry, no railway? --> do not accept way
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("no highway, no ferry, no railway", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
// highway != null
else {
// http://wiki.openstreetmap.org/wiki/DE:Key:sac_scale
if (way.hasTag("sac_scale", inaccessibleSacScales)) {
// everything except "hiking" is probably not possible for wheelchair user
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("bad sac scale", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
if (way.hasTag(KEY_SURFACE, inaccessibleSurfaces)) {
// earth, grass, dirt, mud, sand, snow, ice
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("bad surface", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
if (way.hasTag(KEY_SMOOTHNESS, inaccessibleSmoothnesses)) {
// bad or worse
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("bad smoothness", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
if (way.hasTag(KEY_TRACKTYPE, inaccessibleTracktypes)) {
// grade4 and grade5
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("bad tracktype", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
// wheelchair=yes, designated, official, permissive, limited
if (way.hasTag(KEY_WHEELCHAIR, intendedValues)) {
return EncodingManager.Access.WAY;
}
// wheelchair=no, restricted, private
if (way.hasTag(KEY_WHEELCHAIR, restrictedValues)) {
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("wheelchair no, restricted or private", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
// do not include nonWheelchairAccessibleHighways
if (nonWheelchairAccessibleHighways.contains(highwayValue)) {
// check for wheelchair accessibility
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("in nonWheelchairAccessibleHighways list", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
// foot=yes, designated, official, permissive, limited
if (way.hasTag("foot", intendedValues)) {
return EncodingManager.Access.WAY;
}
// foot=no, restricted, private
if (way.hasTag("foot", restrictedValues)) {
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("pedestrian no, restricted or private", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
if (way.hasTag(KEY_SIDEWALK, usableSidewalkValues)) {
return EncodingManager.Access.WAY;
}
if (way.hasTag(KEY_SIDEWALK, noSidewalkValues) && assumedWheelchairAccessibleHighways.contains(highwayValue)) {
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("in assumedWheelchairAccessibleHighways list with no sidewalks", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
// explicit motorroads are not usable
if (way.hasTag("motorroad", "yes")) {
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("motorroad", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
// do not get our feet wet, "yes" is already included above
if (isBlockFords() && (way.hasTag(KEY_HIGHWAY, "ford") || way.hasTag("ford"))) {
LOGGER.trace(DEBUG_MSG_SKIPPED.formatted("ford", way.getId(), way.getTags().toString()));
return EncodingManager.Access.CAN_SKIP;
}
// In some countries bridleways cannot be travelled by anything other than a horse, so we should check if they have been explicitly allowed for foot or pedestrian
if (highwayValue.equals(KEY_BRIDLEWAY) && !(way.hasTag("foot", intendedValues) || way.hasTag(KEY_WHEELCHAIR, intendedValues))) {
return EncodingManager.Access.CAN_SKIP;
}
if (fullyWheelchairAccessibleHighways.contains(highwayValue) || assumedWheelchairAccessibleHighways.contains(highwayValue) || limitedWheelchairAccessibleHighways.contains(highwayValue)) {
// check whether information on wheelchair accessbility is available and mark for suitability
way.setTag("wheelchair_accessible", true);
return EncodingManager.Access.WAY;
}
// anything else
return EncodingManager.Access.WAY;
}
}
@Override
public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, EncodingManager.Access access, IntsRef relationFlags) {
if (access.canSkip())
return edgeFlags;
if (!access.isFerry()) {
// TODO: Depending on availability of sidewalk, surface, smoothness, tracktype and incline MEAN_SPEED or SLOW_SPEED should be encoded
// TODO: Maybe also implement AvoidFeaturesWeighting for Wheelchairs
// ***************************************** Runge
// This is a trick, where we try to underrate the speed for highways that do not have tagged sidewalks.
// TODO: this actually affects travel time estimation (might be a good or negative side effect depending on context)
double speed = MEAN_SPEED;
if (way.hasTag(KEY_HIGHWAY)) {
String highway = way.getTag(KEY_HIGHWAY);
if (assumedWheelchairAccessibleHighways.contains(highway) && !way.hasTag(KEY_SIDEWALK, usableSidewalkValues)) {
speed *= 0.8d;
}
if (fullyWheelchairAccessibleHighways.contains(highway)) {
if (highway.equalsIgnoreCase(KEY_FOOTWAY)
|| highway.equalsIgnoreCase(KEY_PEDESTRIAN)
|| highway.equalsIgnoreCase(KEY_LIVING_STREET)
|| highway.equalsIgnoreCase("residential")
) {
speed *= 1.25d;
if (way.hasTag(KEY_FOOTWAY, KEY_CROSSING) || way.hasTag(KEY_HIGHWAY, KEY_CROSSING)) {
speed *= 2d; // should not exceed 10 in total due to encoding restrictions
}
}
// residential, unclassified
else if (!way.hasTag(KEY_SIDEWALK, usableSidewalkValues)) {
speed *= 0.9d;
}
}
if (restrictedWheelchairHighways.contains(highway) && (way.hasTag("foot", intendedValues) || way.hasTag(KEY_WHEELCHAIR, intendedValues))) {
speed *= 1.25d;
if (way.hasTag(KEY_CYCLEWAY, KEY_CROSSING) || way.hasTag(KEY_BRIDLEWAY, KEY_CROSSING) || way.hasTag(KEY_HIGHWAY, KEY_CROSSING)) {
speed *= 2d; // should not exceed 10 in total due to encoding restrictions
}
}
}
if (way.hasTag(KEY_SURFACE, problematicSurfaces)
|| way.hasTag(KEY_SMOOTHNESS, problematicSmoothnesses)
|| way.hasTag(KEY_TRACKTYPE, problematicTracktypes)
)
speed *= problematicSpeedFactor;
if (way.hasTag(KEY_SURFACE, preferredSurfaces)
|| way.hasTag(KEY_SMOOTHNESS, preferredSmoothnesses)
)
speed *= preferredSpeedFactor;
if (speed > 10d)
speed = 10d;
if (speed < 1d)
speed = 1d;
// *****************************************
avgSpeedEnc.setDecimal(false, edgeFlags, speed);
accessEnc.setBool(false, edgeFlags, true);
accessEnc.setBool(true, edgeFlags, true);
Integer priorityFromRelation = routeMap.get(footRouteEnc.getEnum(false, edgeFlags));
priorityWayEncoder.setDecimal(false, edgeFlags, PriorityCode.getFactor(handlePriority(way, priorityFromRelation != null ? priorityFromRelation : 0)));
} else {
double ferrySpeed = ferrySpeedCalc.getSpeed(way);
setSpeed(false, edgeFlags, ferrySpeed);
accessEnc.setBool(false, edgeFlags, true);
accessEnc.setBool(true, edgeFlags, true);
}
return edgeFlags;
}
/**
* Parse tags on nodes. Node tags can add to speed (like traffic_signals) where the value is
* strict negative or block access (like a barrier), then the value is strict positive. This
* method is called in the second parsing step.
*/
@Override
public long handleNodeTags(ReaderNode node) {
long encoded = super.handleNodeTags(node);
// We want to be more strict with fords, as only if it is declared as wheelchair accessible do we want to cross it
if (isBlockFords() && (node.hasTag(KEY_HIGHWAY, "ford") || node.hasTag("ford")) && !node.hasTag(KEY_WHEELCHAIR, intendedValues)) {
encoded = getEncoderBit();
}
return encoded;
}
@Override
protected int handlePriority(ReaderWay way, int priorityFromRelation) {
int positiveFeatures = 0;
int negativeFeatures = 0;
// http://wiki.openstreetmap.org/wiki/DE:Key:traffic_calming
String highwayValue = way.getTag(KEY_HIGHWAY);
double maxSpeed = getMaxSpeed(way);
if (isValidSpeed(maxSpeed)) {
if (maxSpeed > 50) {
negativeFeatures++;
if (maxSpeed > 60) {
negativeFeatures++;
if (maxSpeed > 80) {
negativeFeatures++;
}
}
}
if (maxSpeed <= 20) {
positiveFeatures += 1;
}
}
if (way.hasTag("tunnel", intendedValues)) {
negativeFeatures += 4;
}
if ((way.hasTag(KEY_BICYCLE, KEY_DESIGNATED)
|| way.hasTag(KEY_BICYCLE, KEY_OFFICIAL)
|| way.hasTag(KEY_CYCLEWAY, KEY_DESIGNATED)
|| way.hasTag(KEY_CYCLEWAY, KEY_OFFICIAL)
|| way.hasTag(KEY_HORSE, KEY_DESIGNATED)
|| way.hasTag(KEY_HORSE, KEY_OFFICIAL)
) && !way.hasTag(restrictions, intendedValues)) {
negativeFeatures += 4;
}
// put penalty on these ways if no further information is available
if (limitedWheelchairAccessibleHighways.contains(highwayValue)) {
boolean hasAccessibilityRelatedAttributes = false;
for (String key : accessibilityRelatedAttributes) {
hasAccessibilityRelatedAttributes |= way.hasTag(key);
}
if (!hasAccessibilityRelatedAttributes) {
negativeFeatures += 2;
}
}
if (assumedWheelchairAccessibleHighways.contains(highwayValue)) {
if (highwayValue.equalsIgnoreCase("trunk") || highwayValue.equalsIgnoreCase("trunk_link")) {
negativeFeatures += 5;
} else if (highwayValue.equalsIgnoreCase("primary") || highwayValue.equalsIgnoreCase("primary_link")) {
negativeFeatures += 3;
} else { // secondary, tertiary, road, service
negativeFeatures += 1;
}
}
// do not rate foot features twice
boolean isFootEvaluated = false;
if (fullyWheelchairAccessibleHighways.contains(highwayValue)) {
if (highwayValue.equalsIgnoreCase(KEY_FOOTWAY) || highwayValue.equalsIgnoreCase(KEY_PEDESTRIAN) || highwayValue.equalsIgnoreCase(KEY_LIVING_STREET)) {
positiveFeatures += 5;
isFootEvaluated = true;
} else {
// residential, unclassified
negativeFeatures++;
}
}
if (!isFootEvaluated) {
// key=sidewalk
if (way.hasTag(KEY_SIDEWALK, usableSidewalkValues)) {
positiveFeatures += 5;
}
// key=foot
else if (way.hasTag("foot", KEY_DESIGNATED)) {
positiveFeatures += 5;
} else if (way.hasTag("foot", intendedValues) || way.hasTag(KEY_BICYCLE, KEY_DESIGNATED)) {
positiveFeatures += 2;
}
}
if (!osmAttachedSidewalkProcessor.hasSidewalkInfo(way) && !osmPedestrianProcessor.isPedestrianisedWay(way))
negativeFeatures += 2;
int sum = positiveFeatures - negativeFeatures;
if (sum <= -6) return AVOID_AT_ALL_COSTS.getValue();
else if (sum <= -3) return REACH_DEST.getValue();
else if (sum <= -1) return AVOID_IF_POSSIBLE.getValue();
else if (sum == 0) return UNCHANGED.getValue();
else if (sum <= 2) return PREFER.getValue();
else if (sum <= 5) return VERY_NICE.getValue();
else return BEST.getValue();
}
@Override
public String toString() {
return FlagEncoderNames.WHEELCHAIR;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final WheelchairFlagEncoder other = (WheelchairFlagEncoder) obj;
return toString().equals(other.toString());
}
@Override
public int hashCode() {
return ("WheelchairFlagEncoder" + this).hashCode();
}
}