Hi there!
I want to add custom FlagEncoder for Mofa and extended AbstractFlagEncoder.
Mainly copied code from BikeCommonFlagEncoder & BikeFlagEncoder.
Here is my code:
/**
* Flag encoder for electric scooter
* Based on - https://wiki.openstreetmap.org/wiki/Key:mofa
* Full list of vehicle types - https://wiki.openstreetmap.org/wiki/Key:access#Land-based_transportation
*
*/
@Component
public class MofaFlagEncoder extends AbstractFlagEncoder {
protected static final int PUSHING_SECTION_SPEED = 4;
/**
* Участки дороги, где водитель эл. скутера должен слезть со скутера и толкать его.
* Син. Pushing section highways, Schiebestrecke
*/
protected final Set<String> pushingSectionsHighways = new HashSet<>();
/**
* Дороги, которых необходимо избегать
*/
protected final Set<String> avoidHighwayTags = new HashSet<>();
/**
* Предпочитать указанные типы дорог
*/
protected final Set<String> preferHighwayTags = new HashSet<>();
/**
* Противоположные переулки
*/
protected final Set<String> oppositeLanes = new HashSet<>();
/**
* Грунтовые поверхности
*/
protected final Set<String> unpavedSurfaceTags = new HashSet<>();
/**
* Key:trackType
* More info - https://wiki.openstreetmap.org/wiki/Key:tracktype
*/
private final Map<String, Integer> trackTypeSpeeds = new HashMap<>();
/**
* Key:surface
* More info - https://wiki.openstreetmap.org/wiki/Key:surface
*/
private final Map<String, Integer> surfaceSpeeds = new HashMap<>();
/**
* Key:highway
* More info https://wiki.openstreetmap.org/wiki/Key:highway
*/
private final Map<String, Integer> highwaySpeeds = new HashMap<>();
private final Map<RouteNetwork, Integer> routeMap = new HashMap<>();
private int avoidSpeedLimit;
protected boolean speedTwoDirections;
DecimalEncodedValue priorityEnc;
EnumEncodedValue<RouteNetwork> bikeRouteEnc;
private String classBicycleKey;
/**
* Constructors
*/
public MofaFlagEncoder() {
this(4, 2, 0);
}
public MofaFlagEncoder(PMap properties) {
this(properties.getInt("speed_bits", 4),
properties.getInt("speed_factor", 2),
properties.getBool("turn_costs", false) ? 1 : 0);
blockBarriersByDefault(properties.getBool("block_barriers", true));
blockPrivate(properties.getBool("block_private", true));
blockFords(properties.getBool("block_fords", false));
}
private MofaFlagEncoder(int speedBits, double speedFactor, int maxTurnCosts) {
super(speedBits, speedFactor, maxTurnCosts);
// участки, в которых нужно слезть со скутера и толкать его.
pushingSectionsHighways.add("path");
pushingSectionsHighways.add("footway");
pushingSectionsHighways.add("pedestrian");
pushingSectionsHighways.add("steps");
pushingSectionsHighways.add("platform");
// избегаемые дороги
avoidHighwayTags.add("trunk");
avoidHighwayTags.add("trunk_link");
avoidHighwayTags.add("primary");
avoidHighwayTags.add("primary_link");
avoidHighwayTags.add("secondary");
avoidHighwayTags.add("secondary_link");
// предпочтаемые дороги
preferHighwayTags.add("service");
preferHighwayTags.add("tertiary");
preferHighwayTags.add("tertiary_link");
preferHighwayTags.add("residential");
preferHighwayTags.add("unclassified");
// абсолютные барьеры
absoluteBarriers.add("kissing_gate");
absoluteBarriers.add("fence");
absoluteBarriers.add("stile");
absoluteBarriers.add("turnstile");
// потенциальные барьеры
potentialBarriers.add("gate");
potentialBarriers.add("swing_gate");
potentialBarriers.add("cattle_grid");
potentialBarriers.add("bollard");
potentialBarriers.add("cycle_barrier");
potentialBarriers.add("motorcycle_barrier");
potentialBarriers.add("block");
potentialBarriers.add("bus_trap");
potentialBarriers.add("sump_buster");
// strict set, usually vehicle and agricultural/forestry are ignored by cyclists
restrictions.addAll(Arrays.asList("bicycle", "vehicle", "access"));
/**
* See https://wiki.openstreetmap.org/wiki/Key:access#List_of_possible_values
* for full possible values list
*/
// ограниченные значения
restrictedValues.add("no");
restrictedValues.add("restricted");
restrictedValues.add("military");
restrictedValues.add("emergency");
restrictedValues.add("private");
// предназначенные значения
intendedValues.add("yes");
intendedValues.add("designated");
intendedValues.add("official");
intendedValues.add("permissive");
intendedValues.add("use_sidepath");
/**
* противоположные переулки
* More info - https://wiki.openstreetmap.org/wiki/Tag:cycleway%3Dopposite_lane
*/
oppositeLanes.add("opposite");
oppositeLanes.add("opposite_lane");
oppositeLanes.add("opposite_track");
/**
* грунтовые поверхноссти
* Key:surface unpaved
* More info - https://wiki.openstreetmap.org/wiki/Key:surface
*/
unpavedSurfaceTags.add("unpaved");
unpavedSurfaceTags.add("gravel");
unpavedSurfaceTags.add("ground");
unpavedSurfaceTags.add("dirt");
unpavedSurfaceTags.add("grass");
unpavedSurfaceTags.add("compacted");
unpavedSurfaceTags.add("earth");
unpavedSurfaceTags.add("fine_gravel");
unpavedSurfaceTags.add("grass_paver");
unpavedSurfaceTags.add("ice");
unpavedSurfaceTags.add("mud");
unpavedSurfaceTags.add("salt");
unpavedSurfaceTags.add("sand");
unpavedSurfaceTags.add("wood");
maxPossibleSpeed = 25;
trackTypeSpeeds.put("grade1", 15); // вымощеный
trackTypeSpeeds.put("grade2", 12); // сейчас немощеный ...
trackTypeSpeeds.put("grade3", 8);
trackTypeSpeeds.put("grade4", 6);
trackTypeSpeeds.put("grade5", 4); // песок или трава
surfaceSpeeds.put("paved", 15);
surfaceSpeeds.put("asphalt", 15);
surfaceSpeeds.put("cobblestone", 8);
surfaceSpeeds.put("cobblestone:flattened", 10);
surfaceSpeeds.put("sett", 10);
surfaceSpeeds.put("concrete", 15);
surfaceSpeeds.put("concrete:lanes", 16);
surfaceSpeeds.put("concrete:plates", 16);
surfaceSpeeds.put("paving_stones", 12);
surfaceSpeeds.put("paving_stones:30", 12);
surfaceSpeeds.put("unpaved", 14);
surfaceSpeeds.put("compacted", 16);
surfaceSpeeds.put("dirt", 10);
surfaceSpeeds.put("earth", 12);
surfaceSpeeds.put("fine_gravel", 15);
surfaceSpeeds.put("grass", 8);
surfaceSpeeds.put("grass_paver", 8);
surfaceSpeeds.put("gravel", 12);
surfaceSpeeds.put("ground", 12);
surfaceSpeeds.put("ice", PUSHING_SECTION_SPEED / 2);
surfaceSpeeds.put("metal", 10);
surfaceSpeeds.put("mud", 10);
surfaceSpeeds.put("pebblestone", 16);
surfaceSpeeds.put("salt", 6);
surfaceSpeeds.put("sand", 6);
surfaceSpeeds.put("wood", 6);
highwaySpeeds.put("living_street", 6);
highwaySpeeds.put("steps", PUSHING_SECTION_SPEED / 2);
avoidHighwayTags.add("steps");
final int CYCLEWAY_SPEED = 15; // Make sure cycleway and path use same speed value, see #634
highwaySpeeds.put("cycleway", CYCLEWAY_SPEED);
highwaySpeeds.put("path", 15);
highwaySpeeds.put("footway", 6);
highwaySpeeds.put("platform", 6);
highwaySpeeds.put("pedestrian", 6);
highwaySpeeds.put("track", 12);
highwaySpeeds.put("service", 14);
highwaySpeeds.put("residential", 15);
// no other highway applies:
highwaySpeeds.put("unclassified", 16);
// unknown road:
highwaySpeeds.put("road", 12);
highwaySpeeds.put("trunk", 15);
highwaySpeeds.put("trunk_link", 15);
highwaySpeeds.put("primary", 15);
highwaySpeeds.put("primary_link", 15);
highwaySpeeds.put("secondary", 15);
highwaySpeeds.put("secondary_link", 15);
highwaySpeeds.put("tertiary", 15);
highwaySpeeds.put("tertiary_link", 15);
// special case see tests and #191
highwaySpeeds.put("motorway", 15);
highwaySpeeds.put("motorway_link", 15);
avoidHighwayTags.add("motorway");
avoidHighwayTags.add("motorway_link");
highwaySpeeds.put("bridleway", 6);
avoidHighwayTags.add("bridleway");
routeMap.put(INTERNATIONAL, BEST.getValue());
routeMap.put(NATIONAL, BEST.getValue());
routeMap.put(REGIONAL, VERY_NICE.getValue());
routeMap.put(LOCAL, PREFER.getValue());
speedDefault = highwaySpeeds.get("cycleway");
avoidSpeedLimit = 30;
classBicycleKey = "class:bicycle";
}
/**
* Analyze properties of a way and create the edge flags. This method is called in the second parsing step
* @param edgeFlags
* @param way
* @param access
* @return
*/
@Override
public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, EncodingManager.Access access) {
if (access.canSkip()) {
return edgeFlags;
}
Integer priorityFromRelation = routeMap.get(bikeRouteEnc.getEnum(false, edgeFlags));
double wayTypeSpeed = getSpeed(way);
if (!access.isFerry()) {
wayTypeSpeed = applyMaxSpeed(way, wayTypeSpeed);
handleSpeed(edgeFlags, way, wayTypeSpeed);
} else {
double ferrySpeed = getFerrySpeed(way);
handleSpeed(edgeFlags, way, ferrySpeed);
accessEnc.setBool(false, edgeFlags, true);
accessEnc.setBool(true, edgeFlags, true);
priorityFromRelation = AVOID_IF_POSSIBLE.getValue();
}
priorityEnc.setDecimal(false, edgeFlags, PriorityCode.getFactor(handlePriority(way, wayTypeSpeed, priorityFromRelation)));
return edgeFlags;
}
/**
* Decide whether a way is routable for a given mode of travel.
* This skips some ways before handleWayTags is called.
* @param way
* @return the encoded value to indicate if this encoder allows travel or not.
*/
@Override
public EncodingManager.Access getAccess(ReaderWay way) {
String highwayValue = way.getTag("highway");
if (highwayValue == null) {
EncodingManager.Access accept = EncodingManager.Access.CAN_SKIP;
if (way.hasTag("route", ferries)) {
// if bike is NOT explicitly tagged allow bike but only if foot is not specified
String bikeTag = way.getTag("bicycle");
if (bikeTag == null && !way.hasTag("foot") || intendedValues.contains(bikeTag)) {
accept = EncodingManager.Access.FERRY;
}
}
// special case not for all acceptedRailways, only platform
if (way.hasTag("railway", "platform")) {
accept = EncodingManager.Access.WAY;
}
if (way.hasTag("man_made", "pier")) {
accept = EncodingManager.Access.WAY;
}
if (!accept.canSkip()) {
if (way.hasTag(restrictions, restrictedValues) && !getConditionalTagInspector().isRestrictedWayConditionallyPermitted(way)) {
return EncodingManager.Access.CAN_SKIP;
}
return accept;
}
return EncodingManager.Access.CAN_SKIP;
}
if (!highwaySpeeds.containsKey(highwayValue)) {
return EncodingManager.Access.CAN_SKIP;
}
String sacScale = way.getTag("sac_scale");
if (sacScale != null) {
if ((way.hasTag("highway", "cycleway"))
&& (way.hasTag("sac_scale", "hiking"))) {
return EncodingManager.Access.WAY;
}
if (!isSacScaleAllowed(sacScale)) {
return EncodingManager.Access.CAN_SKIP;
}
}
// use the way if it is tagged for bikes
if (way.hasTag("bicycle", intendedValues) ||
way.hasTag("bicycle", "dismount") ||
way.hasTag("highway", "cycleway")) {
return EncodingManager.Access.WAY;
}
// accept only if explicitly tagged for bike usage
if ("motorway".equals(highwayValue) || "motorway_link".equals(highwayValue) || "bridleway".equals(highwayValue)) {
return EncodingManager.Access.CAN_SKIP;
}
if (way.hasTag("motorroad", "yes")) {
return EncodingManager.Access.CAN_SKIP;
}
// do not use fords with normal bikes, flagged fords are in included above
if (isBlockFords() && (way.hasTag("highway", "ford") || way.hasTag("ford"))) {
return EncodingManager.Access.CAN_SKIP;
}
// check access restrictions
if (way.hasTag(restrictions, restrictedValues) && !getConditionalTagInspector().isRestrictedWayConditionallyPermitted(way)) {
return EncodingManager.Access.CAN_SKIP;
}
if (getConditionalTagInspector().isPermittedWayConditionallyRestricted(way)) {
return EncodingManager.Access.CAN_SKIP;
} else {
return EncodingManager.Access.WAY;
}
}
boolean isSacScaleAllowed(String sacScale) {
// other scales are nearly impossible by an ordinary bike, see http://wiki.openstreetmap.org/wiki/Key:sac_scale
return "hiking".equals(sacScale);
}
@Override
public void createEncodedValues(List<EncodedValue> registerNewEncodedValue, String prefix, int index) {
// first two bits are reserved for route handling in superclass
super.createEncodedValues(registerNewEncodedValue, prefix, index);
registerNewEncodedValue.add(avgSpeedEnc = new UnsignedDecimalEncodedValue(getKey(prefix, "average_speed"), speedBits, speedFactor, speedTwoDirections));
registerNewEncodedValue.add(priorityEnc = new UnsignedDecimalEncodedValue(getKey(prefix, "priority"), 3, PriorityCode.getFactor(1), false));
bikeRouteEnc = getEnumEncodedValue(RouteNetwork.key("mofa"), RouteNetwork.class);
}
private int getSpeed(ReaderWay way) {
int speed = PUSHING_SECTION_SPEED;
String highwayTag = way.getTag("highway");
Integer highwaySpeed = highwaySpeeds.get(highwayTag);
// Under certain conditions we need to increase the speed of pushing sections to the speed of a "highway=cycleway"
if (way.hasTag("highway", pushingSectionsHighways)
&& ((way.hasTag("foot", "yes") && way.hasTag("segregated", "yes"))
|| (way.hasTag("bicycle", intendedValues))))
highwaySpeed = highwaySpeeds.get("cycleway");
String s = way.getTag("surface");
Integer surfaceSpeed = 0;
if (!Helper.isEmpty(s)) {
surfaceSpeed = surfaceSpeeds.get(s);
if (surfaceSpeed != null) {
speed = surfaceSpeed;
// boost handling for good surfaces but avoid boosting if pushing section
if (highwaySpeed != null && surfaceSpeed > highwaySpeed) {
if (pushingSectionsHighways.contains(highwayTag))
speed = highwaySpeed;
else
speed = surfaceSpeed;
}
}
} else {
String tt = way.getTag("tracktype");
if (!Helper.isEmpty(tt)) {
Integer tInt = trackTypeSpeeds.get(tt);
if (tInt != null)
speed = tInt;
} else if (highwaySpeed != null) {
if (!way.hasTag("service"))
speed = highwaySpeed;
else
speed = highwaySpeeds.get("living_street");
}
}
// Until now we assumed that the way is no pushing section
// Now we check that, but only in case that our speed computed so far is bigger compared to the PUSHING_SECTION_SPEED
if (speed > PUSHING_SECTION_SPEED
&& (way.hasTag("highway", pushingSectionsHighways) || way.hasTag("bicycle", "dismount"))) {
if (!way.hasTag("bicycle", intendedValues)) {
// Here we set the speed for pushing sections and set speed for steps as even lower:
speed = way.hasTag("highway", "steps") ? PUSHING_SECTION_SPEED / 2 : PUSHING_SECTION_SPEED;
} else if (way.hasTag("bicycle", "designated") || way.hasTag("bicycle", "official") ||
way.hasTag("segregated", "yes") || way.hasTag("bicycle", "yes")) {
// Here we handle the cases where the OSM tagging results in something similar to "highway=cycleway"
if (way.hasTag("segregated", "yes"))
speed = highwaySpeeds.get("cycleway");
else
speed = way.hasTag("bicycle", "yes") ? 10 : highwaySpeeds.get("cycleway");
// overwrite our speed again in case we have a valid surface speed and if it is smaller as computed so far
if ((surfaceSpeed > 0) && (surfaceSpeed < speed))
speed = surfaceSpeed;
}
}
return speed;
}
private void handleSpeed(IntsRef edgeFlags, ReaderWay way, double speed) {
avgSpeedEnc.setDecimal(false, edgeFlags, speed);
// handle OneWays
boolean isOneway = way.hasTag("oneway", oneways)
|| way.hasTag("oneway:bicycle", oneways)
|| way.hasTag("vehicle:backward")
|| way.hasTag("vehicle:forward")
|| way.hasTag("bicycle:forward") && (way.hasTag("bicycle:forward", "yes") || way.hasTag("bicycle:forward", "no"));
if ((isOneway || roundaboutEnc.getBool(false, edgeFlags))
&& !way.hasTag("oneway:bicycle", "no")
&& !way.hasTag("bicycle:backward")
&& !way.hasTag("cycleway", oppositeLanes)
&& !way.hasTag("cycleway:left", oppositeLanes)
&& !way.hasTag("cycleway:right", oppositeLanes))
{
boolean isBackward = way.hasTag("oneway", "-1")
|| way.hasTag("oneway:bicycle", "-1")
|| way.hasTag("vehicle:forward", "no")
|| way.hasTag("bicycle:forward", "no");
if (isBackward) {
accessEnc.setBool(true, edgeFlags, true);
} else {
accessEnc.setBool(false, edgeFlags, true);
}
} else {
accessEnc.setBool(false, edgeFlags, true);
accessEnc.setBool(true, edgeFlags, true);
}
}
/**
* In this method we prefer cycleways or roads with designated bike access and avoid big roads
* or roads with trams or pedestrian.
*
* @return new priority based on priorityFromRelation and on the tags in ReaderWay.
*/
private int handlePriority(ReaderWay way, double wayTypeSpeed, Integer priorityFromRelation) {
TreeMap<Double, Integer> weightToPrioMap = new TreeMap<>();
if (priorityFromRelation == null)
weightToPrioMap.put(0d, UNCHANGED.getValue());
else
weightToPrioMap.put(110d, priorityFromRelation);
collect(way, wayTypeSpeed, weightToPrioMap);
// pick priority with biggest order value
return weightToPrioMap.lastEntry().getValue();
}
/**
* @param weightToPrioMap associate a weight with every priority.
* This sorted map allows subclasses to 'insert' more important priorities as well
* as overwrite determined priorities.
*/
private void collect(ReaderWay way, double wayTypeSpeed, TreeMap<Double, Integer> weightToPrioMap) {
String service = way.getTag("service");
String highway = way.getTag("highway");
if (way.hasTag("bicycle", "designated") || way.hasTag("bicycle", "official")) {
if ("path".equals(highway))
weightToPrioMap.put(100d, VERY_NICE.getValue());
else
weightToPrioMap.put(100d, PREFER.getValue());
}
if ("cycleway".equals(highway)) {
if (way.hasTag("foot", intendedValues) && !way.hasTag("segregated", "yes"))
weightToPrioMap.put(100d, PREFER.getValue());
else
weightToPrioMap.put(100d, VERY_NICE.getValue());
}
double maxSpeed = getMaxSpeed(way);
if (preferHighwayTags.contains(highway) || (isValidSpeed(maxSpeed) && maxSpeed <= 30)) {
if (!isValidSpeed(maxSpeed) || maxSpeed < avoidSpeedLimit) {
weightToPrioMap.put(40d, PREFER.getValue());
if (way.hasTag("tunnel", intendedValues))
weightToPrioMap.put(40d, UNCHANGED.getValue());
}
} else if (avoidHighwayTags.contains(highway)
|| isValidSpeed(maxSpeed) && maxSpeed >= avoidSpeedLimit && !"track".equals(highway)) {
weightToPrioMap.put(50d, REACH_DEST.getValue());
if (way.hasTag("tunnel", intendedValues))
weightToPrioMap.put(50d, AVOID_AT_ALL_COSTS.getValue());
}
String cycleway = way.getFirstPriorityTag(Arrays.asList("cycleway", "cycleway:left", "cycleway:right"));
if (Arrays.asList("lane", "shared_lane", "share_busway", "shoulder").contains(cycleway)) {
weightToPrioMap.put(100d, UNCHANGED.getValue());
} else if ("track".equals(cycleway)) {
weightToPrioMap.put(100d, PREFER.getValue());
}
if (pushingSectionsHighways.contains(highway)
|| "parking_aisle".equals(service)) {
int pushingSectionPrio = AVOID_IF_POSSIBLE.getValue();
if (way.hasTag("bicycle", "use_sidepath")) {
pushingSectionPrio = PREFER.getValue();
}
if (way.hasTag("bicycle", "yes") || way.hasTag("bicycle", "permissive"))
pushingSectionPrio = PREFER.getValue();
if (way.hasTag("bicycle", "designated") || way.hasTag("bicycle", "official"))
pushingSectionPrio = VERY_NICE.getValue();
if (way.hasTag("foot", "yes")) {
pushingSectionPrio = Math.max(pushingSectionPrio - 1, WORST.getValue());
if (way.hasTag("segregated", "yes"))
pushingSectionPrio = Math.min(pushingSectionPrio + 1, BEST.getValue());
}
weightToPrioMap.put(100d, pushingSectionPrio);
}
if (way.hasTag("railway", "tram"))
weightToPrioMap.put(50d, AVOID_AT_ALL_COSTS.getValue());
String classBicycleValue = way.getTag(classBicycleKey);
if (classBicycleValue != null) {
// We assume that humans are better in classifying preferences compared to our algorithm above -> weight = 100
weightToPrioMap.put(100d, convertClassValueToPriority(classBicycleValue).getValue());
} else {
String classBicycle = way.getTag("class:bicycle");
if (classBicycle != null)
weightToPrioMap.put(100d, convertClassValueToPriority(classBicycle).getValue());
}
// Increase the priority for scenic routes or in case that maxspeed limits our average speed as compensation. See #630
if (way.hasTag("scenic", "yes") || maxSpeed > 0 && maxSpeed < wayTypeSpeed) {
if (weightToPrioMap.lastEntry().getValue() < BEST.getValue())
// Increase the prio by one step
weightToPrioMap.put(110d, weightToPrioMap.lastEntry().getValue() + 1);
}
}
// Conversion of class value to priority. See http://wiki.openstreetmap.org/wiki/Class:bicycle
private PriorityCode convertClassValueToPriority(String tagvalue) {
int classvalue;
try {
classvalue = Integer.parseInt(tagvalue);
} catch (NumberFormatException e) {
return UNCHANGED;
}
switch (classvalue) {
case 3:
return BEST;
case 2:
return VERY_NICE;
case 1:
return PREFER;
case 0:
return UNCHANGED;
case -1:
return AVOID_IF_POSSIBLE;
case -2:
return REACH_DEST;
case -3:
return AVOID_AT_ALL_COSTS;
default:
return UNCHANGED;
}
}
/**
* Version of this FlagEncoder
*
* @return the version of this FlagEncoder to enforce none-compatibility when new attributes are introduced
*/
@Override
public int getVersion() {
return 1;
}
/**
* Transportation mode - https://wiki.openstreetmap.org/wiki/Key:access#Land-based_transportation
* @return TransportationMode
*/
@Override
public TransportationMode getTransportationMode() {
return TransportationMode.BICYCLE;
}
/**
* FlagEncoder name
* @return String
*/
@Override
public String toString() {
return "mofa";
}
}
On application start I’m getting IllegalArgumentException Cannot find EncodedValue mofa_network in collection: null
How I can avoid this exception?
GH version - 2.4
Thanks in advance!