Cannot find EncodedValue mofa_network in collection: null

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!

Remove this line:

   bikeRouteEnc = getEnumEncodedValue(RouteNetwork.key("mofa"), RouteNetwork.class);

or use RouteNetwork.key("bike"). But that does not make much sense, because you probably don’t want to drive a mofa on cycleways.

1 Like

Hi)
Thank you so much for the quick response.

When I using this:
bikeRouteEnc = getEnumEncodedValue(RouteNetwork.key("bike"), RouteNetwork.class);

Then getting the same exception:

Cannot find EncodedValue bike_network in collection: null

Can’t a mofa ride on the cycleways? Or does it depend on the country?

You can only use the bike network encoded value if you add the OSMBikeNetworkTagParser:
By default it is only added if a BikeCommonFlagEncoder is present:

Any road can be part of a bike network. Some of these roads might be suitable for mofas, others might not. Like I said, you probably don’t need to consider bike networks to build a mofa profile.

1 Like

Hi!
I have updated 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<>();

  protected boolean speedTwoDirections;

  /** 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");
  }

  /**
   * 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;
    }
  }

  /**
   * The key sac_scale=* is used to classify hiking trails in mountainous areas with regard to the
   * difficulties to be expected. The scale discerns six grades in ascending level: From hiking (T1)
   * up to difficult_alpine_hiking (T6)
   *
   * @param sacScale link - https://wiki.openstreetmap.org/wiki/Key:sac_scale
   * @return boolean
   */
  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);
  }

  /**
   * 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;
    }
    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);
    }
    return edgeFlags;
  }

  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");
      accessEnc.setBool(isBackward, edgeFlags, true);
    } else {
      accessEnc.setBool(false, edgeFlags, true);
      accessEnc.setBool(true, edgeFlags, true);
    }
  }

  @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(EncodingManager.getKey(prefix, "average_speed"), speedBits, speedFactor, speedTwoDirections));
  }

  /**
   * 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";
  }

}

Is my FlagEncoder for mofa correct?

And I’m getting this exception
java.lang.IllegalArgumentException: mofa.average_speed value 81.0 too large for encoding. maxValue:30.0

How I can fix it?

Thanks in advance!

You set speed_bits=4 and speed_factor=2. This means there are four bits to store the speed and each of the 15 possible values represents a speed value all of which are 2km/h apart. So the possible speed values are 0, 2, 4, 6, …, 30. To store larger speeds like 81 you either need to use more bits or increase the speed factor (or both).

1 Like

Thank you so much for quick response. But what if maxPossibleSpeed setted to 25? Where is comming the speed - 81?
I will try to understand the problem.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.