CommonBikeFlagEncoder.java

  1. /*
  2.  *  Licensed to GraphHopper GmbH under one or more contributor
  3.  *  license agreements. See the NOTICE file distributed with this work for
  4.  *  additional information regarding copyright ownership.
  5.  *
  6.  *  GraphHopper GmbH licenses this file to you under the Apache License,
  7.  *  Version 2.0 (the "License"); you may not use this file except in
  8.  *  compliance with the License. You may obtain a copy of the License at
  9.  *
  10.  *       http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  *  Unless required by applicable law or agreed to in writing, software
  13.  *  distributed under the License is distributed on an "AS IS" BASIS,
  14.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15.  *  See the License for the specific language governing permissions and
  16.  *  limitations under the License.
  17.  */
  18. package org.heigit.ors.routing.graphhopper.extensions.flagencoders.bike;

  19. import com.graphhopper.reader.ReaderWay;
  20. import com.graphhopper.routing.ev.*;
  21. import com.graphhopper.routing.util.BikeCommonFlagEncoder;
  22. import com.graphhopper.routing.util.EncodingManager;
  23. import com.graphhopper.routing.util.TransportationMode;
  24. import com.graphhopper.routing.weighting.PriorityWeighting;
  25. import com.graphhopper.storage.ConditionalEdges;
  26. import com.graphhopper.storage.IntsRef;
  27. import com.graphhopper.util.Helper;
  28. import com.graphhopper.util.PMap;
  29. import com.graphhopper.util.Translation;
  30. import org.apache.log4j.Logger;
  31. import org.heigit.ors.routing.graphhopper.extensions.util.PriorityCode;

  32. import java.io.File;
  33. import java.io.FileWriter;
  34. import java.util.*;

  35. import static com.graphhopper.routing.ev.RouteNetwork.*;
  36. import static com.graphhopper.routing.util.EncodingManager.getKey;
  37. import static org.heigit.ors.routing.graphhopper.extensions.util.PriorityCode.*;

  38. /**
  39.  * Defines bit layout of bicycles (not motorcycles) for speed, access and relations (network).
  40.  * <p>
  41.  *
  42.  * @author Peter Karich
  43.  * @author Nop
  44.  * @author ratrun
  45.  */
  46. public abstract class CommonBikeFlagEncoder extends BikeCommonFlagEncoder {
  47.     /**
  48.      * Reports whether this edge is unpaved.
  49.      */
  50.     protected static final int PUSHING_SECTION_SPEED = 4;
  51.     public static final String KEY_BICYCLE = "bicycle";
  52.     public static final String KEY_DESIGNATED = "designated";
  53.     public static final String KEY_OFFICIAL = "official";
  54.     public static final String KEY_UNPAVED = "unpaved";
  55.     public static final String KEY_LIVING_STREET = "living_street";
  56.     public static final String KEY_SERVICE = "service";
  57.     public static final String KEY_STEPS = "steps";
  58.     public static final String KEY_CYCLEWAY = "cycleway";
  59.     public static final String KEY_TRACK = "track";
  60.     public static final String KEY_MOTORWAY = "motorway";
  61.     public static final String KEY_MOTORWAY_LINK = "motorway_link";
  62.     public static final String KEY_HIGHWAY = "highway";
  63.     public static final String KEY_ROUTE = "route";
  64.     public static final String KEY_RAILWAY = "railway";
  65.     public static final String KEY_BICYCLE_ROAD = "bicycle_road";
  66.     public static final String KEY_JUNCTION = "junction";
  67.     public static final String KEY_SEGREGATED = "segregated";
  68.     public static final String KEY_ONEWAY_BICYCLE = "oneway:bicycle";
  69.     public static final String KEY_BRIDLEWAY = "bridleway";

  70.     // Pushing section highways are parts where you need to get off your bike and push it (German: Schiebestrecke)
  71.     protected final HashSet<String> pushingSectionsHighways = new HashSet<>();
  72.     protected final HashSet<String> oppositeLanes = new HashSet<>();
  73.     protected final Set<String> preferHighwayTags = new HashSet<>();
  74.     protected final Set<String> avoidHighwayTags = new HashSet<>();
  75.     protected final Set<String> unpavedSurfaceTags = new HashSet<>();
  76.     private final Map<String, SpeedValue> trackTypeSpeeds = new HashMap<>();
  77.     private final Map<String, SpeedValue> surfaceSpeeds = new HashMap<>();
  78.     private final Set<String> roadValues = new HashSet<>();
  79.     private final Map<String, SpeedValue> highwaySpeeds = new HashMap<>();
  80.     // convert network tag of bicycle routes into a way route code
  81.     DecimalEncodedValue priorityWayEncoder;
  82.     BooleanEncodedValue unpavedEncoder;
  83.     private IntEncodedValue wayTypeEncoder;
  84.     // Car speed limit which switches the preference from UNCHANGED to AVOID_IF_POSSIBLE
  85.     private int avoidSpeedLimit;
  86.     EnumEncodedValue<RouteNetwork> bikeRouteEnc;
  87.     Map<RouteNetwork, Integer> routeMap = new HashMap<>();
  88.     protected boolean conditionalAccess = false;
  89.     // This is the specific bicycle class
  90.     private String classBicycleKey;

  91.     private BooleanEncodedValue conditionalAccessEncoder;

  92.     private static final boolean DEBUG_OUTPUT = false;
  93.     FileWriter logWriter;

  94.     // MARQ24 MOD START
  95.     // MARQ24 ADDON in the case of the RoadBike Encoder we want to skip some
  96.     // conditions...
  97.     private final boolean isRoadBikeEncoder = this instanceof RoadBikeFlagEncoder; // TODO: design: parent class should not need to know of child
  98.     protected static final Logger LOGGER = Logger.getLogger(CommonBikeFlagEncoder.class.getName());
  99.     // MARQ24 MOD END

  100.     // MARQ24 MOD START
  101.     protected CommonBikeFlagEncoder(int speedBits, double speedFactor, int maxTurnCosts) {
  102.         this(speedBits, speedFactor, maxTurnCosts, false);
  103.     }
  104.     // MARQ24 MOD END

  105.     protected void setProperties(PMap properties) {
  106.         blockFords(properties.getBool("block_fords", true));
  107.         conditionalAccess = properties.getBool(ConditionalEdges.ACCESS, false);
  108.     }

  109.     // MARQ24 MOD START
  110.     protected CommonBikeFlagEncoder(int speedBits, double speedFactor, int maxTurnCosts, boolean considerElevation) {
  111.         // MARQ24 MOD END
  112.         super(speedBits, speedFactor, maxTurnCosts);
  113.         // strict set, usually vehicle and agricultural/forestry are ignored by cyclists
  114.         restrictions.addAll(Arrays.asList(KEY_BICYCLE, "vehicle", "access"));
  115.         restrictedValues.add("private");
  116.         restrictedValues.add("no");
  117.         restrictedValues.add("restricted");
  118.         restrictedValues.add("military");
  119.         restrictedValues.add("emergency");

  120.         intendedValues.add("yes");
  121.         intendedValues.add(KEY_DESIGNATED);
  122.         intendedValues.add(KEY_OFFICIAL);
  123.         intendedValues.add("permissive");

  124.         oppositeLanes.add("opposite");
  125.         oppositeLanes.add("opposite_lane");
  126.         oppositeLanes.add("opposite_track");

  127.         passByDefaultBarriers.add("gate");
  128.         passByDefaultBarriers.add("swing_gate");

  129.         blockByDefaultBarriers.add("stile");
  130.         blockByDefaultBarriers.add("turnstile");

  131.         unpavedSurfaceTags.add(KEY_UNPAVED);
  132.         unpavedSurfaceTags.add("gravel");
  133.         unpavedSurfaceTags.add("ground");
  134.         unpavedSurfaceTags.add("dirt");
  135.         unpavedSurfaceTags.add("grass");
  136.         unpavedSurfaceTags.add("compacted");
  137.         unpavedSurfaceTags.add("earth");
  138.         unpavedSurfaceTags.add("fine_gravel");
  139.         unpavedSurfaceTags.add("grass_paver");
  140.         unpavedSurfaceTags.add("ice");
  141.         unpavedSurfaceTags.add("mud");
  142.         unpavedSurfaceTags.add("salt");
  143.         unpavedSurfaceTags.add("sand");
  144.         unpavedSurfaceTags.add("wood");

  145.         roadValues.add(KEY_LIVING_STREET);
  146.         roadValues.add("road");
  147.         roadValues.add(KEY_SERVICE);
  148.         roadValues.add("unclassified");
  149.         roadValues.add("residential");
  150.         roadValues.add("trunk");
  151.         roadValues.add("trunk_link");
  152.         roadValues.add("primary");
  153.         roadValues.add("primary_link");
  154.         roadValues.add("secondary");
  155.         roadValues.add("secondary_link");
  156.         roadValues.add("tertiary");
  157.         roadValues.add("tertiary_link");

  158.         maxPossibleSpeed = 30;

  159.         // MARQ24 MOD START
  160.         // we have to check, WHAT this really does - since when 'enabled' we get 'cracy' detours like this one
  161.         // http://localhost:3035/directions?n1=51.563406&n2=8.713585&n3=16&a=51.566454,8.705764,51.559224,8.707244&b=1a&c=0&g1=-1&g2=0&h2=3&k1=en-US&k2=km
  162.         if (considerElevation) {
  163.             maxPossibleSpeed = (int) getDownhillMaxSpeed();
  164.         }
  165.         // MARQ24 MOD END

  166.         setTrackTypeSpeed("grade1", 18); // paved
  167.         setTrackTypeSpeed("grade2", 12); // now unpaved ...
  168.         setTrackTypeSpeed("grade3", 8);
  169.         setTrackTypeSpeed("grade4", 6);
  170.         setTrackTypeSpeed("grade5", 4); // like sand/grass

  171.         setSurfaceSpeed("paved", 18);
  172.         setSurfaceSpeed("asphalt", 18);
  173.         setSurfaceSpeed("cobblestone", 8);
  174.         setSurfaceSpeed("cobblestone:flattened", 10);
  175.         setSurfaceSpeed("sett", 10);
  176.         setSurfaceSpeed("concrete", 18);
  177.         setSurfaceSpeed("concrete:lanes", 16);
  178.         setSurfaceSpeed("concrete:plates", 16);
  179.         setSurfaceSpeed("paving_stones", 12);
  180.         setSurfaceSpeed("paving_stones:30", 12);
  181.         setSurfaceSpeed(KEY_UNPAVED, 14);
  182.         setSurfaceSpeed("compacted", 16);
  183.         setSurfaceSpeed("dirt", 10);
  184.         setSurfaceSpeed("earth", 12);
  185.         setSurfaceSpeed("fine_gravel", 18);
  186.         setSurfaceSpeed("grass", 8);
  187.         setSurfaceSpeed("grass_paver", 8);
  188.         setSurfaceSpeed("gravel", 12);
  189.         setSurfaceSpeed("ground", 12);
  190.         setSurfaceSpeed("ice", PUSHING_SECTION_SPEED / 2);
  191.         setSurfaceSpeed("metal", 10);
  192.         setSurfaceSpeed("mud", 10);
  193.         setSurfaceSpeed("pebblestone", 16);
  194.         setSurfaceSpeed("salt", 6);
  195.         setSurfaceSpeed("sand", 6);
  196.         setSurfaceSpeed("wood", 6);

  197.         setHighwaySpeed(KEY_LIVING_STREET, 6);
  198.         setHighwaySpeed(KEY_STEPS, PUSHING_SECTION_SPEED / 2);

  199.         final int CYCLEWAY_SPEED = 18;  // Make sure cycleway and path use same speed value, see #634
  200.         setHighwaySpeed(KEY_CYCLEWAY, CYCLEWAY_SPEED);
  201.         setHighwaySpeed("path", 10);
  202.         setHighwaySpeed("footway", 6);
  203.         setHighwaySpeed("pedestrian", 6);
  204.         setHighwaySpeed(KEY_TRACK, 12);
  205.         setHighwaySpeed(KEY_SERVICE, 14);
  206.         setHighwaySpeed("residential", 18);
  207.         // no other highway applies:
  208.         setHighwaySpeed("unclassified", 16);
  209.         // unknown road:
  210.         setHighwaySpeed("road", 12);

  211.         setHighwaySpeed("trunk", 18);
  212.         setHighwaySpeed("trunk_link", 18);
  213.         setHighwaySpeed("primary", 18);
  214.         setHighwaySpeed("primary_link", 18);
  215.         setHighwaySpeed("secondary", 18);
  216.         setHighwaySpeed("secondary_link", 18);
  217.         setHighwaySpeed("tertiary", 18);
  218.         setHighwaySpeed("tertiary_link", 18);

  219.         // special case see tests and #191
  220.         setHighwaySpeed(KEY_MOTORWAY, 18);
  221.         setHighwaySpeed(KEY_MOTORWAY_LINK, 18);
  222.         avoidHighwayTags.add(KEY_MOTORWAY);
  223.         avoidHighwayTags.add(KEY_MOTORWAY_LINK);

  224.         // bridleways are allowed to ride over in some cases
  225.         setHighwaySpeed(KEY_BRIDLEWAY, 6);
  226.         avoidHighwayTags.add(KEY_BRIDLEWAY);

  227.         routeMap.put(INTERNATIONAL, BEST.getValue());
  228.         routeMap.put(NATIONAL, BEST.getValue());
  229.         routeMap.put(REGIONAL, VERY_NICE.getValue());
  230.         routeMap.put(LOCAL, PREFER.getValue());
  231.         routeMap.put(DEPRECATED, REACH_DEST.getValue());
  232.         routeMap.put(MTB, UNCHANGED.getValue());
  233.         routeMap.put(FERRY, AVOID_IF_POSSIBLE.getValue());

  234.         setAvoidSpeedLimit(71);

  235.         if (DEBUG_OUTPUT) {
  236.             try {
  237.                 File file = new File("CommonBikeFlagEncoder.log");
  238.                 logWriter = new FileWriter(file);
  239.             } catch (Exception ex) {
  240.                 LOGGER.warn("Failed to write log file.");
  241.             }
  242.         }
  243.     }

  244.     @Override
  245.     public TransportationMode getTransportationMode() {
  246.         return TransportationMode.BIKE;
  247.     }

  248.     @Override
  249.     public void createEncodedValues(List<EncodedValue> registerNewEncodedValue, String prefix, int index) {
  250.         super.createEncodedValues(registerNewEncodedValue, prefix, index);
  251.         registerNewEncodedValue.add(avgSpeedEnc = new UnsignedDecimalEncodedValue(getKey(prefix, "average_speed"), speedBits, speedFactor, false));
  252.         unpavedEncoder = new SimpleBooleanEncodedValue(getKey(prefix, "paved"), false);
  253.         registerNewEncodedValue.add(unpavedEncoder);
  254.         wayTypeEncoder = new UnsignedIntEncodedValue(getKey(prefix, "waytype"), 2, false);
  255.         registerNewEncodedValue.add(wayTypeEncoder);
  256.         priorityWayEncoder = new UnsignedDecimalEncodedValue(getKey(prefix, "priority"), 4, PriorityCode.getFactor(1), false);
  257.         registerNewEncodedValue.add(priorityWayEncoder);
  258.         if (conditionalAccess) {
  259.             conditionalAccessEncoder = new SimpleBooleanEncodedValue(EncodingManager.getKey(prefix, ConditionalEdges.ACCESS), true);
  260.             registerNewEncodedValue.add(conditionalAccessEncoder);
  261.         }
  262.         bikeRouteEnc = getEnumEncodedValue(RouteNetwork.key("bike"), RouteNetwork.class);
  263.     }

  264.     @Override
  265.     public EncodingManager.Access getAccess(ReaderWay way) {
  266.         String highwayValue = way.getTag(KEY_HIGHWAY);
  267.         if (highwayValue == null) {
  268.             EncodingManager.Access acceptPotentially = EncodingManager.Access.CAN_SKIP;

  269.             if (way.hasTag(KEY_ROUTE, ferries)) {
  270.                 // if bike is NOT explicitly tagged allow bike but only if foot is not specified
  271.                 String bikeTag = way.getTag(KEY_BICYCLE);
  272.                 if (bikeTag == null && !way.hasTag("foot") || "yes".equals(bikeTag)) {
  273.                     acceptPotentially = EncodingManager.Access.FERRY;
  274.                 }
  275.             }

  276.             // special case not for all acceptedRailways, only platform
  277.             if (way.hasTag(KEY_RAILWAY, "platform")) {
  278.                 acceptPotentially = EncodingManager.Access.WAY;
  279.             }

  280.             if (way.hasTag("man_made", "pier")) {
  281.                 acceptPotentially = EncodingManager.Access.WAY;
  282.             }

  283.             if (!acceptPotentially.canSkip()) {
  284.                 if (way.hasTag(restrictions, restrictedValues))
  285.                     acceptPotentially = isRestrictedWayConditionallyPermitted(way, acceptPotentially);
  286.                 return acceptPotentially;
  287.             }

  288.             return EncodingManager.Access.CAN_SKIP;
  289.         }

  290.         if (!highwaySpeeds.containsKey(highwayValue)) {
  291.             return EncodingManager.Access.CAN_SKIP;
  292.         }

  293.         String sacScale = way.getTag("sac_scale");
  294.         if (sacScale != null) {
  295.             if ((way.hasTag(KEY_HIGHWAY, KEY_CYCLEWAY)) && (way.hasTag("sac_scale", "hiking"))) {
  296.                 return EncodingManager.Access.WAY;
  297.             }
  298.             if (!isSacScaleAllowed(sacScale)) {
  299.                 return EncodingManager.Access.CAN_SKIP;
  300.             }
  301.         }

  302.         // use the way if it is tagged for bikes
  303.         if (way.hasTag(KEY_BICYCLE, intendedValues)
  304.                 || way.hasTag(KEY_BICYCLE, "dismount")
  305.                 || way.hasTag(KEY_HIGHWAY, KEY_CYCLEWAY)
  306.                 // MARQ24 MOD START
  307.                 // Runge: http://www.openstreetmap.org/way/1700503
  308.                 || way.hasTag(KEY_BICYCLE_ROAD, "yes")
  309.             // MARQ24 MOD END
  310.         ) {
  311.             return isPermittedWayConditionallyRestricted(way);
  312.         }

  313.         // accept only if explicitly tagged for bike usage
  314.         if (KEY_MOTORWAY.equals(highwayValue) || KEY_MOTORWAY_LINK.equals(highwayValue) || KEY_BRIDLEWAY.equals(highwayValue)) {
  315.             return EncodingManager.Access.CAN_SKIP;
  316.         }

  317.         if (way.hasTag("motorroad", "yes")) {
  318.             return EncodingManager.Access.CAN_SKIP;
  319.         }

  320.         // do not use fords with normal bikes, flagged fords are in included above
  321.         if (isBlockFords() && (way.hasTag(KEY_HIGHWAY, "ford") || way.hasTag("ford"))) {
  322.             return EncodingManager.Access.CAN_SKIP;
  323.         }

  324.         // check access restrictions
  325.         if (way.hasTag(restrictions, restrictedValues))
  326.             return isRestrictedWayConditionallyPermitted(way);

  327.         return isPermittedWayConditionallyRestricted(way);
  328.     }

  329.     boolean isSacScaleAllowed(String sacScale) {
  330.         // other scales are nearly impossible by an ordinary bike, see http://wiki.openstreetmap.org/wiki/Key:sac_scale
  331.         return "hiking".equals(sacScale);
  332.     }

  333.     /**
  334.      * Apply maxspeed: In contrast to the implementation of the AbstractFlagEncoder, we assume that
  335.      * we can reach the maxspeed for bicycles in case that the road type speed is higher and not
  336.      * just only 90%.
  337.      * <p>
  338.      *
  339.      * @param way   needed to retrieve tags
  340.      * @param speed speed guessed e.g. from the road type or other tags
  341.      * @return The assumed average speed.
  342.      */
  343.     @Override
  344.     protected double applyMaxSpeed(ReaderWay way, double speed) {
  345.         double maxSpeed = getMaxSpeed(way);
  346.         if (isValidSpeed(maxSpeed) && maxSpeed < speed) {
  347.             return maxSpeed;
  348.         }
  349.         return speed;
  350.     }

  351.     @Override
  352.     public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, EncodingManager.Access access) {
  353.         if (access.canSkip()) {
  354.             return edgeFlags;
  355.         }

  356.         Integer priorityFromRelationInt = routeMap.get(bikeRouteEnc.getEnum(false, edgeFlags));
  357.         int priorityFromRelation = priorityFromRelationInt == null ? 0 : priorityFromRelationInt;

  358.         double wayTypeSpeed = getSpeed(way);
  359.         if (!access.isFerry()) {
  360.             wayTypeSpeed = applyMaxSpeed(way, wayTypeSpeed);
  361.             handleSpeed(edgeFlags, way, wayTypeSpeed);
  362.             handleBikeRelated(edgeFlags, way, priorityFromRelation > UNCHANGED.getValue());
  363.             if (access.isConditional() && conditionalAccessEncoder != null)
  364.                 conditionalAccessEncoder.setBool(false, edgeFlags, true);
  365.             boolean isRoundabout = way.hasTag(KEY_JUNCTION, "roundabout") || way.hasTag(KEY_JUNCTION, "circular");
  366.             if (isRoundabout) {
  367.                 roundaboutEnc.setBool(false, edgeFlags, true);
  368.             }
  369.         } else {
  370.             double ferrySpeed = ferrySpeedCalc.getSpeed(way);
  371.             handleSpeed(edgeFlags, way, ferrySpeed);
  372.         }

  373.         int priority = handlePriority(way, wayTypeSpeed, priorityFromRelation);
  374.         if (DEBUG_OUTPUT) {
  375.             try {
  376.                 logWriter.write("WayID %d RelationPrio %d FinalPrio %d %n".formatted(way.getId(), priorityFromRelation, priority));
  377.                 logWriter.flush();
  378.             } catch (Exception ex) {
  379.                 LOGGER.warn("Failed to write log file.");
  380.             }
  381.         }
  382.         priorityWayEncoder.setDecimal(false, edgeFlags, PriorityCode.getFactor(priority));
  383.         return edgeFlags;
  384.     }

  385.     int getSpeed(ReaderWay way) {
  386.         int speed = Integer.MIN_VALUE;
  387.         String highwayTag = way.getTag(KEY_HIGHWAY);
  388.         SpeedValue highwaySpeed = highwaySpeeds.get(highwayTag);

  389.         boolean isPushingWay = isPushingSection(way);
  390.         boolean isCyclewayLikeWay = false;
  391.         boolean isSteps = way.hasTag(KEY_HIGHWAY, KEY_STEPS);

  392.         // Under certain conditions we need to increase the speed of pushing sections to the speed of a "highway=cycleway"
  393.         // MARQ24 - so if this is a pushing section (like path, track or footway) BUT have additional bicycle attributes
  394.         // then we treat this way as it was tagged as "cycleway"...
  395.         if (isPushingWay &&
  396.                 (
  397.                         (way.hasTag("foot", "yes") && way.hasTag(KEY_SEGREGATED, "yes"))
  398.                                 || way.hasTag(KEY_BICYCLE, "yes")
  399.                                 || way.hasTag(KEY_BICYCLE, KEY_DESIGNATED)
  400.                                 || way.hasTag(KEY_BICYCLE, KEY_OFFICIAL)
  401.                 )
  402.                 && !isSteps
  403.         ) {
  404.             isCyclewayLikeWay = true;
  405.             highwaySpeed = getHighwaySpeed(KEY_CYCLEWAY);
  406.         }

  407.         String s = way.getTag("surface");
  408.         if (!Helper.isEmpty(s)) {
  409.             SpeedValue surfaceSpeed = surfaceSpeeds.get(s);
  410.             if (surfaceSpeed != null && (!isPushingWay || isCyclewayLikeWay)) {
  411.                 // ok if no specific highway speed is set we will use the surface speed...
  412.                 if (highwaySpeed == null) {
  413.                     speed = surfaceSpeed.speed;
  414.                 } else {
  415.                     speed = calcHighwaySpeedBasedOnSurface(highwaySpeed, surfaceSpeed);
  416.                 }
  417.             }
  418.         } else {
  419.             // no SURFACE TAG present...
  420.             String tt = way.getTag("tracktype");
  421.             if (!Helper.isEmpty(tt)) {
  422.                 SpeedValue tracktypeSpeed = trackTypeSpeeds.get(tt);
  423.                 if (tracktypeSpeed != null && (!isPushingWay || isCyclewayLikeWay)) {
  424.                     if (highwaySpeed == null) {
  425.                         speed = tracktypeSpeed.speed;
  426.                     } else {
  427.                         speed = calcHighwaySpeedBasedOnSurface(highwaySpeed, tracktypeSpeed);
  428.                     }
  429.                 }
  430.             }
  431.         }

  432.         // if the speed have not been set yet...
  433.         if (speed == Integer.MIN_VALUE) {
  434.             if (highwaySpeed != null) {
  435.                 if (!way.hasTag(KEY_SERVICE)) {
  436.                     speed = highwaySpeed.speed;
  437.                 } else {
  438.                     // MARQ24: with other words any 'service' tagged ways will be
  439.                     // considered as min "living street" speed (or slower)...
  440.                     speed = Math.min(highwaySpeeds.get(KEY_LIVING_STREET).speed, highwaySpeed.speed);
  441.                 }
  442.             } else {
  443.                 speed = PUSHING_SECTION_SPEED;
  444.             }
  445.         }

  446.         // MARQ24 MOD START
  447.         if (speed <= PUSHING_SECTION_SPEED) {
  448.             if (!isSteps) {
  449.                 // MARQ24 if we are still on pushing section speed, then we at least double the speed in the case
  450.                 // that the way is 'segregated' (but of course we sill ignore steps)...
  451.                 // MARQ24 MOD END
  452.                 // Increase speed in case of segregated
  453.                 if (way.hasTag(KEY_SEGREGATED, "yes")) {
  454.                     speed = PUSHING_SECTION_SPEED * 2;
  455.                 }
  456.                 // MARQ24 MOD START
  457.             } else {
  458.                 // MARQ24: make sure that steps will always get a very slow speed...
  459.                 speed = PUSHING_SECTION_SPEED / 2;
  460.             }
  461.         }
  462.         // MARQ24 MOD END

  463.         return speed;
  464.     }

  465.     /*
  466.     The order of speed arguments MAKE a difference !!! When highway & surface SpeedValue have the UpdateType=BOTH
  467.     then the return value will be always the speed of the last speed argument (surface.speed)!!!
  468.     */
  469.     public int calcHighwaySpeedBasedOnSurface(SpeedValue highway, SpeedValue surface) {
  470.         if (highway.speed.equals(surface.speed)) {
  471.             return highway.speed;
  472.         } else if (highway.speed > surface.speed) {
  473.             // highway = 18 (residential)
  474.             // surface = 4 (gravel)
  475.             switch (highway.type) {
  476.                 case UPGRADE_ONLY:
  477.                     return highway.speed;

  478.                 case DOWNGRADE_ONLY:
  479.                 case BOTH:
  480.                 default:
  481.                     return switch (surface.type) {
  482.                         case UPGRADE_ONLY -> highway.speed;
  483.                         case DOWNGRADE_ONLY, BOTH -> surface.speed;
  484.                         default -> surface.speed;
  485.                     };
  486.             }
  487.         } else {
  488.             // highway = 8 (cycleway)
  489.             // surface = 18 (asphalt)
  490.             switch (highway.type) {
  491.                 case DOWNGRADE_ONLY:
  492.                     return highway.speed;
  493.                 case UPGRADE_ONLY:
  494.                 case BOTH:
  495.                 default:
  496.                     return switch (surface.type) {
  497.                         case DOWNGRADE_ONLY -> highway.speed;
  498.                         case UPGRADE_ONLY, BOTH -> surface.speed;
  499.                         default -> surface.speed;
  500.                     };
  501.             }
  502.         }
  503.     }

  504.     String getWayName(int pavementType, int wayType, Translation tr) {
  505.         String pavementName = "";
  506.         if (pavementType == 1)
  507.             pavementName = tr.tr(KEY_UNPAVED);

  508.         String wayTypeName = switch (wayType) {
  509.             case 0 -> "";
  510.             case 1 -> tr.tr("off_bike");
  511.             case 2 -> tr.tr(KEY_CYCLEWAY);
  512.             case 3 -> tr.tr("small_way");
  513.             default -> "";
  514.         };

  515.         if (pavementName.isEmpty()) {
  516.             if (wayType == 0 || wayType == 3)
  517.                 return "";
  518.             return wayTypeName;
  519.         } else if (wayTypeName.isEmpty())
  520.             return pavementName;
  521.         else
  522.             return wayTypeName + ", " + pavementName;
  523.     }

  524.     /**
  525.      * In this method we prefer cycleways or roads with designated bike access and avoid big roads
  526.      * or roads with trams or pedestrian.
  527.      * <p>
  528.      *
  529.      * @return new priority based on priorityFromRelation and on the tags in ReaderWay.
  530.      */
  531.     protected int handlePriority(ReaderWay way, double wayTypeSpeed, int priorityFromRelation) {
  532.         TreeMap<Double, Integer> weightToPrioMap = new TreeMap<>();
  533.         if (priorityFromRelation == 0)
  534.             weightToPrioMap.put(0d, UNCHANGED.getValue());
  535.         else
  536.             weightToPrioMap.put(110d, priorityFromRelation);

  537.         collect(way, wayTypeSpeed, weightToPrioMap);

  538.         // pick priority with biggest order value
  539.         return weightToPrioMap.lastEntry().getValue();
  540.     }

  541.     // Conversion of class value to priority. See http://wiki.openstreetmap.org/wiki/Class:bicycle
  542.     private PriorityCode convertClassValueToPriority(String tagvalue) {
  543.         int classvalue;
  544.         try {
  545.             classvalue = Integer.parseInt(tagvalue);
  546.         } catch (NumberFormatException e) {
  547.             return UNCHANGED;
  548.         }

  549.         return switch (classvalue) {
  550.             case 3 -> BEST;
  551.             case 2 -> VERY_NICE;
  552.             case 1 -> PREFER;
  553.             case 0 -> UNCHANGED;
  554.             case -1 -> AVOID_IF_POSSIBLE;
  555.             case -2 -> REACH_DEST;
  556.             case -3 -> AVOID_AT_ALL_COSTS;
  557.             default -> UNCHANGED;
  558.         };
  559.     }

  560.     /**
  561.      * @param weightToPrioMap associate a weight with every priority. This sorted map allows
  562.      *                        subclasses to 'insert' more important priorities as well as overwrite determined priorities.
  563.      */
  564.     void collect(ReaderWay way, double wayTypeSpeed, TreeMap<Double, Integer> weightToPrioMap) {
  565.         String service = way.getTag(KEY_SERVICE);
  566.         String highway = way.getTag(KEY_HIGHWAY);
  567.         // MARQ24 MOD START
  568.         if (!isRoadBikeEncoder) {
  569.             // MARQ24 MOD END
  570.             // MARQ24 MOD START
  571.             if (way.hasTag(KEY_BICYCLE, KEY_DESIGNATED) || way.hasTag(KEY_BICYCLE, KEY_OFFICIAL) || way.hasTag(KEY_BICYCLE_ROAD, "yes")) {
  572.                 // MARQ24 MOD END
  573.                 if ("path".equals(highway)) {
  574.                     weightToPrioMap.put(100d, VERY_NICE.getValue());
  575.                 } else {
  576.                     weightToPrioMap.put(100d, PREFER.getValue());
  577.                 }
  578.             }
  579.             if (KEY_CYCLEWAY.equals(highway)) {
  580.                 if (way.hasTag("foot", intendedValues) && !way.hasTag(KEY_SEGREGATED, "yes")) {
  581.                     weightToPrioMap.put(100d, PREFER.getValue());
  582.                 } else {
  583.                     weightToPrioMap.put(100d, VERY_NICE.getValue());
  584.                 }
  585.             }
  586.             // MARQ24 MOD START
  587.         }
  588.         // MARQ24 MOD END

  589.         double maxSpeed = getMaxSpeed(way);
  590.         if (preferHighwayTags.contains(highway) || this.isValidSpeed(maxSpeed) && maxSpeed <= 30) {
  591.             if (!this.isValidSpeed(maxSpeed) || maxSpeed < avoidSpeedLimit) {
  592.                 weightToPrioMap.put(40d, PREFER.getValue());
  593.                 if (way.hasTag("tunnel", intendedValues)) {
  594.                     weightToPrioMap.put(40d, UNCHANGED.getValue());
  595.                 }
  596.             }
  597.         } else if (avoidHighwayTags.contains(highway) || maxSpeed >= avoidSpeedLimit && !KEY_TRACK.equals(highway)) {
  598.             weightToPrioMap.put(50d, REACH_DEST.getValue());
  599.             if (way.hasTag("tunnel", intendedValues)) {
  600.                 weightToPrioMap.put(50d, AVOID_AT_ALL_COSTS.getValue());
  601.             }
  602.         }

  603.         if (pushingSectionsHighways.contains(highway)
  604.                 || "parking_aisle".equals(service)) {
  605.             int pushingSectionPrio = AVOID_IF_POSSIBLE.getValue();
  606.             // MARQ24 MOD START
  607.             if (!isRoadBikeEncoder) {
  608.                 // MARQ24 MOD END
  609.                 if (way.hasTag(KEY_BICYCLE, "use_sidepath") || way.hasTag(KEY_BICYCLE, "yes") || way.hasTag(KEY_BICYCLE, "permissive")) {
  610.                     pushingSectionPrio = PREFER.getValue();
  611.                 }
  612.                 if (way.hasTag(KEY_BICYCLE, KEY_DESIGNATED) || way.hasTag(KEY_BICYCLE, KEY_OFFICIAL)) {
  613.                     pushingSectionPrio = VERY_NICE.getValue();
  614.                 }
  615.                 // MARQ24 MOD START
  616.             }
  617.             // MARQ24 MOD END

  618.             if (way.hasTag("foot", "yes")) {
  619.                 pushingSectionPrio = Math.max(pushingSectionPrio - 1, WORST.getValue());
  620.                 if (!isRoadBikeEncoder && way.hasTag(KEY_SEGREGATED, "yes")) {
  621.                     pushingSectionPrio = Math.min(pushingSectionPrio + 1, BEST.getValue());
  622.                 }
  623.             }
  624.             weightToPrioMap.put(100d, pushingSectionPrio);
  625.         }

  626.         if (way.hasTag(KEY_RAILWAY, "tram")) {
  627.             weightToPrioMap.put(50d, AVOID_AT_ALL_COSTS.getValue());
  628.         }

  629.         String classBicycleValue = way.getTag(classBicycleKey);
  630.         if (classBicycleValue != null) {
  631.             // We assume that humans are better in classifying preferences compared to our algorithm above -> weight = 100
  632.             weightToPrioMap.put(100d, convertClassValueToPriority(classBicycleValue).getValue());
  633.         } else {
  634.             String classBicycle = way.getTag("class:bicycle");
  635.             if (classBicycle != null) {
  636.                 weightToPrioMap.put(100d, convertClassValueToPriority(classBicycle).getValue());
  637.             }
  638.         }

  639.         // Increase the priority for scenic routes or in case that maxspeed limits our average speed as compensation. See #630
  640.         if ((way.hasTag("scenic", "yes") || (maxSpeed > 0 && maxSpeed < wayTypeSpeed)) && weightToPrioMap.lastEntry().getValue() < BEST.getValue()) {
  641.             // Increase the prio by one step
  642.             weightToPrioMap.put(110d, weightToPrioMap.lastEntry().getValue() + 1);
  643.         }
  644.     }

  645.     /**
  646.      * Handle surface and wayType encoding
  647.      */
  648.     void handleBikeRelated(IntsRef edgeFlags, ReaderWay way, boolean partOfCycleRelation) {
  649.         String surfaceTag = way.getTag("surface");
  650.         String highway = way.getTag(KEY_HIGHWAY);
  651.         String trackType = way.getTag("tracktype");

  652.         // Populate unpavedBit
  653.         if (KEY_TRACK.equals(highway) && (!"grade1".equals(trackType))
  654.                 || "path".equals(highway) && surfaceTag == null
  655.                 || unpavedSurfaceTags.contains(surfaceTag)) {
  656.             unpavedEncoder.setBool(false, edgeFlags, true);
  657.         }

  658.         WayType wayType;
  659.         if (roadValues.contains(highway)) {
  660.             wayType = WayType.ROAD;
  661.         } else {
  662.             wayType = WayType.OTHER_SMALL_WAY;
  663.         }

  664.         boolean isPushingSection = isPushingSection(way);
  665.         if (isPushingSection || KEY_STEPS.equals(highway)) {
  666.             wayType = WayType.PUSHING_SECTION;
  667.         } else {
  668.             // boost "none identified" partOfCycleRelation
  669.             if (!isRoadBikeEncoder && partOfCycleRelation) {
  670.                 wayType = WayType.CYCLEWAY;
  671.             }

  672.             if (way.hasTag(KEY_BICYCLE, intendedValues) || KEY_CYCLEWAY.equals(highway)) {
  673.                 wayType = WayType.CYCLEWAY;
  674.             }
  675.         }
  676.         wayTypeEncoder.setInt(false, edgeFlags, wayType.getValue());
  677.     }

  678.     boolean isPushingSection(ReaderWay way) {
  679.         // MARQ24 MOD START
  680.         return way.hasTag(KEY_HIGHWAY, pushingSectionsHighways) || way.hasTag(KEY_RAILWAY, "platform") || way.hasTag(KEY_BICYCLE, "dismount") || way.hasTag(KEY_ROUTE, ferries); // Runge
  681.         // MARQ24 MOD END
  682.     }

  683.     protected void handleSpeed(IntsRef edgeFlags, ReaderWay way, double speed) {
  684.         String bicycleForwardTag = "bicycle:forward";
  685.         avgSpeedEnc.setDecimal(false, edgeFlags, speed);
  686.         // handle oneways
  687.         boolean isOneway = way.hasTag("oneway", oneways)
  688.                 || way.hasTag(KEY_ONEWAY_BICYCLE, oneways)
  689.                 || way.hasTag("vehicle:backward")
  690.                 || way.hasTag("vehicle:forward")
  691.                 || way.hasTag(bicycleForwardTag, "yes")
  692.                 || way.hasTag(bicycleForwardTag, "no");
  693.         //MARQ24 MOD START
  694.         if (!way.hasTag(KEY_BICYCLE_ROAD, "yes") && (isOneway || way.hasTag(KEY_JUNCTION, "roundabout"))
  695.                 //MARQ24 MOD END
  696.                 && !way.hasTag(KEY_ONEWAY_BICYCLE, "no")
  697.                 && !way.hasTag("bicycle:backward")
  698.                 && !way.hasTag(KEY_CYCLEWAY, oppositeLanes)
  699.                 && !way.hasTag("cycleway:left", oppositeLanes)
  700.                 && !way.hasTag("cycleway:right", oppositeLanes)) {
  701.             boolean isBackward = way.hasTag("oneway", "-1")
  702.                     || way.hasTag(KEY_ONEWAY_BICYCLE, "-1")
  703.                     || way.hasTag("vehicle:forward", "no")
  704.                     || way.hasTag(bicycleForwardTag, "no");
  705.             accessEnc.setBool(isBackward, edgeFlags, true);
  706.         } else {
  707.             accessEnc.setBool(false, edgeFlags, true);
  708.             accessEnc.setBool(true, edgeFlags, true);
  709.         }
  710.     }

  711.     protected void setHighwaySpeed(String highway, int speed) {
  712.         highwaySpeeds.put(highway, new SpeedValue(speed));
  713.     }

  714.     protected void setHighwaySpeed(String highway, SpeedValue speed) {
  715.         highwaySpeeds.put(highway, speed);
  716.     }

  717.     SpeedValue getHighwaySpeed(String key) {
  718.         return highwaySpeeds.get(key);
  719.     }

  720.     protected void setTrackTypeSpeed(String tracktype, int speed) {
  721.         trackTypeSpeeds.put(tracktype, new SpeedValue(speed));
  722.     }

  723.     protected void setTrackTypeSpeed(String tracktype, SpeedValue speed) {
  724.         trackTypeSpeeds.put(tracktype, speed);
  725.     }

  726.     protected void setSurfaceSpeed(String surface, int speed) {
  727.         surfaceSpeeds.put(surface, new SpeedValue(speed));
  728.     }

  729.     protected void setSurfaceSpeed(String surface, SpeedValue speed) {
  730.         surfaceSpeeds.put(surface, speed);
  731.     }

  732.     SpeedValue getSurfaceSpeed(String key) {
  733.         return surfaceSpeeds.get(key);
  734.     }

  735.     void addPushingSection(String highway) {
  736.         pushingSectionsHighways.add(highway);
  737.     }

  738.     @Override
  739.     public boolean supports(Class<?> feature) {
  740.         if (super.supports(feature)) {
  741.             return true;
  742.         }
  743.         return PriorityWeighting.class.isAssignableFrom(feature);
  744.     }

  745.     public void setAvoidSpeedLimit(int limit) {
  746.         avoidSpeedLimit = limit;
  747.     }

  748.     protected void setSpecificClassBicycle(String subkey) {
  749.         classBicycleKey = "class:bicycle:" + subkey;
  750.     }

  751.     private enum WayType {
  752.         ROAD(0),
  753.         PUSHING_SECTION(1),
  754.         CYCLEWAY(2),
  755.         OTHER_SMALL_WAY(3);

  756.         private final int value;

  757.         WayType(int value) {
  758.             this.value = value;
  759.         }

  760.         public int getValue() {
  761.             return value;
  762.         }
  763.     }

  764.     protected enum UpdateType {
  765.         UPGRADE_ONLY,
  766.         DOWNGRADE_ONLY,
  767.         BOTH
  768.     }

  769.     protected static class SpeedValue {
  770.         private final Integer speed;
  771.         private UpdateType type = UpdateType.BOTH;

  772.         private SpeedValue(Integer speed) {
  773.             this.speed = speed;
  774.         }

  775.         protected SpeedValue(Integer speed, UpdateType type) {
  776.             this.speed = speed;
  777.             this.type = type;
  778.         }

  779.         public String toString() {
  780.             return switch (type) {
  781.                 case BOTH -> speed + " [BOTH]";
  782.                 case UPGRADE_ONLY -> speed + " [UP]";
  783.                 case DOWNGRADE_ONLY -> speed + " [DOWN]";
  784.             };
  785.         }
  786.     }

  787.     // MARQ24 MOD START
  788.     protected abstract double getDownhillMaxSpeed();
  789.     // MARQ24 MOD END

  790.     @Override
  791.     public int hashCode() {
  792.         return ("CommonBikeFlagEnc" + this).hashCode();
  793.     }

  794.     @Override
  795.     public boolean equals(Object obj) {
  796.         if (obj == null)
  797.             return false;
  798.         if (getClass() != obj.getClass())
  799.             return false;
  800.         CommonBikeFlagEncoder cast = (CommonBikeFlagEncoder) obj;
  801.         return toString().equals(cast.toString());
  802.     }

  803.     public abstract double getMeanSpeed();
  804. }