Routing for unpaved roads

Hello everyone,

I would like to do the following with Graphhopper:
Find a way from A to B, with as MANY unpaved roads as possible, which are allowed to be used by cars, or more precisely by motorcycles (It is OK to do detours for that). Why the hell would i like to do that? Well, I have an adventure bike / dual sport motorcycle and I (and also many others) really enjoy riding it on dirt roads (like the one in the picture).

This is what I found out:
Using Custom Models, I can prefer roads with a certain surface or track type (which indirectly suggests the surface). See code below:


profiles:
      - name: adv_bike
      vehicle: car
      weighting: customv
      "custom_model": {
      "priority": [
      # lookup track_type because some dirt-road tracks/roads don't have surface tag but DO have track_typed defined. track_type 2-5 suggest that it is a dirt/gravel-road
    {
      "if": "!(surface == GRAVEL || surface == UNPAVED || surface == FINE_GRAVEL || surface == COMPACTED || surface == GROUND || surface == DIRT || surface == GRASS || surface == SAND || track_type == GRADE2 || track_type == GRADE3 || track_type == GRADE4 || track_type == GRADE5)",
      "multiply_by": "0.01"
    }
  ],
  distance_influence: 1

    }

BUT:
I use the Car Model as a base (e.g. because I only want to drive on roads that I am allowed to drive on with car/motorbike - and because the speeds are realistic). but the Car Model cannot use Tracktype 4&5. My current temporary solution is to change the CarTagParser so that I can use roads with tracktype 4&5 (see code snippet from CarTagParser below - last 2 lines of code added).


        trackTypeSpeedMap.put("grade1", 20); // paved
        trackTypeSpeedMap.put("grade2", 15); // now unpaved - gravel mixed with ...
        trackTypeSpeedMap.put("grade3", 10); // ... hard and soft materials
        trackTypeSpeedMap.put("grade4", 10); // ... some hard or compressed materials
        trackTypeSpeedMap.put("grade5", 10); // ... no hard materials. soil/sand/grass
        trackTypeSpeedMap.put(null, defaultSpeedMap.get("track"));

Now to the actual questions:

Question 1 are roads with certain surface or tracktype disfavored by default in the Car model? And if so, where is this done?

Question 2: is it possible to change the CarTagParser in a way so that you can allow track types 4&5 in the custom model? If no → Question 3: does it make sense to add an additional offroad-carflagencoder to Graphhopper?

Or am I completely on the wrong track and there is a much better approach to implement my request? :smiley:

Something like is also possible without a code change. You can use the roads vehicle and set the car_average_speed to valid values even for track road classes where it is not defined. See this model (use with profile=roads):

{
  "speed": [{
    "if": "road_class == TRACK",
    "limit_to": "30"
  },{
    "else": "",
    "limit_to": "car_average_speed"
  }],
  "priority": [{
    "if": "road_class == TRACK",
    "multiply_by": "1"
  },{
    "else_if": "car_access",
    "multiply_by": "0.5"
  },{
    "else": "",
    "multiply_by": "0"
  }]
}

Note that the priority does not influence the speed. And I choose a completely arbitrary value here (0.5) for all other roads and you can increase this, but then the track road classes are unlikely to be chosen as they are relative slow (in the model they are 30km/h).

See this link.

See also this car4wd model for a slightly different customization.

Thank you very much and sorry that a reply that late! The proposed code lead me to the right direction and helped me understand graphhopper a bit better.
This is the code which works best for me:

{
  "speed": [
    {
      "if": "road_class == TRACK",
      "limit_to": "20"
    },
    {
      "else": "",
      "limit_to": "car_average_speed"
    }
  ],
  "priority": [
    {
      "if": "(surface == GRAVEL || surface == UNPAVED || surface == FINE_GRAVEL || surface == COMPACTED || surface == GROUND || surface == DIRT || surface == GRASS || surface == SAND || track_type == GRADE2 || track_type == GRADE3 || track_type == GRADE4 || track_type == GRADE5)",
      "multiply_by": "1"
    },
    {
      "else_if": "(road_class != TRACK)",
      "multiply_by": "0.1"
    },
    {
      "else_if": "track_type == GRADE1",
      "multiply_by": "0.5"
    },
    {
      "else": "",
      "multiply_by": "0.3"
    },
    {
      "if": "car_access == false && road_class != TRACK",
      "multiply_by": "0.0"
    },
    {
      "if": "road_class == MOTORWAY",
      "multiply_by": "0.1"
    }
  ]
}

And here is an exempla where i used it to find unsealed roads:
example

It is a bit complicated, because:

  • I want to favor roads which have an unsealed/unpaved surface in general - no matter if they are tracks or not. Because for my use-case unsealed/unpaved is what i am looking for.
  • Only approx. 30% of the tracks have a surface key. But because they are tracks they are probably unsealed/unpaved. So i want to favor them too (but not as much as roads with unsealed/unpaved surface tag)
  • But there is an exception: tracks with grade=1 - because grade 1 implies sealed surface (probably asphalt or concrete). But still they are probably more fun to ride than regular roads. So i still want to favor them a bit.

There are still two questions:

1st: Is there a way to use nested if statement in custom models? For this case it worked out without them, but i can imagine cases, where it would be very useful.

2nd: I’m not sure if i understood the car_access correctly. I assume that for example an access=agricultural leads to car_access=false. But where in the graphhopper code can i look up what makes car_access true or false?

Nice that you found a solution. And yes, tweaking such a model is a challenge :slight_smile:

btw: make sure you look also into urban_density which might help to avoid cities or villages

btw2: can you try if

track_type.ordinal() >= GRADE2.ordinal()

also works for you instead of

track_type == GRADE2 || track_type == GRADE3 || track_type == GRADE4 || track_type == GRADE5

?

Ah, it seems it cannot resolve GRADE2 then and it would be required to use the constant:

track_type.ordinal() >= 2

Which is a bit ugly as this might change (although it probably shouldn’t).

Not yet, but I know what you mean and this might come.

But where in the graphhopper code can i look up what makes car_access true or false?

In master it is now separated and you can find it in CarAccessParser.handleWayTags

Thanks again for the fast reply!

This is definetely on my todo-List! :slightly_smiling_face:

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