Flexible / Configurable Weighting Update

Then I do not fully understand your usecase.

Unfortunately there are lots of roads without a surface tag

Yes, the most roads have no surface tag.

include all Primary roads regardless of surface, but only include more minor roads if they meet the surface test, or something along those lines.

Let’s say we introduce a without option for surface, then this would mean that other would not include roads without a surface tag. So when you exclude all unpaved sufaces and also other then this might be what you are after because you are no longer excluding roads without surface tag.

But I would need to also exclude “without” as well as “other” otherwise it would result in routes on paths and unclassified roads that don’t have a surface tag, which could very well could be unpaved.

I’m trying to find a way to avoid any path or road which is definitely unpaved but also allow any where the surface isn’t known but there is a very high likelihood of it being paved (e.g. primary roads), without blocking paths and minor roads that have a paved surface tag.

Maybe there is a way to combine surface and road_class priorities to achieve this?

Yes, you can combine them:

priority: {
  surface: { unpaved: 0, other: 0 },
  road_class: { unclassified: 0 }
}

It will use the priority 0 if a surface tag is unpaved or other, or if the road_class is unclassified. In fact it multiplies the priorities.

Wouldn’t that block unclassified roads that are paved though? As well as blocking any other roads that are missing a surface tag?

My aim is to allow roads and paths that are very likely to be paved but block roads and paths that are definitely not paved. I don’t think that’s possible as it currently stands, as I think this would need conditional priority rules.

At the moment yes, but that was the reason I asked if it would help if we feed untagged roads into a separate without value (or call it missing).

Wouldn’t that block unclassified roads that are paved though?

Either you want to block all unclassified roads (then you do it like I proposed with the two entries) or you want to block all unpaved roads (then there is no need for the second entry).

So wouldn’t this be what you are looking for:

priority: {
  surface: { unpaved: 0, other: 0, missing: 1, paved: 1 },
}

?

The value missing: 1 and paved: 1 is optional

Or do you have another use case? If yes, can you describe the tagging of certain roads that would be handled incorrectly by this proposal?

Sorry for the confusion!

Hopefully the following table will clarify what I’m trying to do. Basically it is a road bike use case. I want to only allow routing on more minor roads and paths if they are known to be paved (by paved I would include asphalt, concrete, etc but I’ve just shown it as paved below for simplicity).

I would like to “allow” the following (obviously subject to normal bike access rules):

road class surface
primary any
secondary any
tertiary any
unclassified only if paved
residential any
living_street only if paved
service only if paved
pedestrian any
all other types ... only if paved

motorway and trunk roads would also be disallowed.

Thanks, I think I understand the problem now. I’m sometimes a bit slow :slight_smile:

This is currently indeed not possible. An ugly workaround would be to not fully avoid unpaved roads (e.g. unpaved: 0.5) and also avoid unclassified, living_street etc via unclassified: 0.5 etc. Then if it is unpaved and unclassified both values are multiplied resulting in 0.25.

To fix this we could try to extend the “condition” feature we already have for area:

priority:
   area_somecity: 0

areas:
  somecity: # define area via geosjon

and do something like this instead:

priority:
   in_somecity: 0

conditions:
  in_somecity: # define area via geosjon
  other_condition: 
      road_class: unclassified
      road_class: living_street

Which would be then for your use case:

priority:
   "small_roads and paved_surface": 0

conditions:
   small_roads: 
      road_class: unclassified
      road_class: living_street
      ...
   paved_surface:
      surface: paved
      surface: concrete
      ...

This cries for a more flexible and generic scripting language (Java/JavaScript/python/…). The downside of a generic scripting language is that we cannot check per-request if it is compatible to an existing landmark vehicle profile (to use the faster landmark A*) and so we would either have to accept that the script triggers suboptimal routes (if it increases the weight for certain edges) or the response time is much slower (due to fallback to beeline A*) or requires a slow graph scan to compare every edge.

Hmmh, or we limit the power of the scripting engine when executed per-request and say it can only decrease the weight and either throw an error if it increases it or ignore it silently.

No worries, I can understand that there are a number of challenges to adding support for conditional rules. It would still be useful to add “missing” though, so that different priorities can be used if the surface information hasn’t been added.

Can you also clarify what “other” means please? I’m assuming it means any value not on the list that you get when you hover over the surface rules on your demo server (including missing tags)…? Or is there another definitive list of what is not “other” somewhere?

Yes, this is the meaning and also how you know the values (see the /info endpoint).

Have integrated the missing value for surface in master: https://github.com/graphhopper/graphhopper/commit/5620ea0a307ea96bb8554281770fad7a41405e49

Thank you. Does that mean it has also now been added to the version 1.0 web service jar or will this update be applied to a future version of this?

I think a simple workaround for such ‘conditional’ use-cases should be doing some kind of pre-processing: Process all roads and add some custom tag depending on other tags (like surface+road_class) and then use this new custom tag in the custom weighting. Maybe we should make this process more accessible/easier and keep the ‘scripting/custom weighting’ simple.

No, commiting to master does not add the change to an already existing version/jar (like 1.0), but the change will be included in the next (pre)release, the last one of which was on july 17th: Releases · graphhopper/graphhopper · GitHub

1 Like

@plotaroute there is now a new branch in a good shape that allows you to define more complex conditions like:

priority:
  road_class == UNCLASSIFIED && surface != PAVED: 0.5
  road_class == LIVING_STREET && (surface != PAVED || surface == OTHER): 0.3

Here every statement will be evaluated and the values will be multiplied for priority.

Or something like an if-then-else-if flow where only one value is picked:

priority:
  first_match:
    road_class == PRIMARY || road_class == SECONDARY: 1.0
    surface == UNPAVED: 0

It is slightly more verbose but much more powerful and readable IMO. Let me know what you think. For a migration guide there is currently only the examples and the changed profiles.md documentation in the branch.

2 Likes

Wow, that sounds great - thanks. Looks really flexible. We’re deeply tied up with another project at the moment but will have a look at it when we get chance.

@karussell We’ve got a bit of time now to try out the new conditional priorities. However, I’m a bit unclear what we need to download in order to access it. We use the Map Matching build, as we need a .jar file that has both the route and match endpoints - is the new conditional priority feature available in a Map Matching build?

You clone the repository, checkout the janino_scripting branch and build the jar file e.g. via mvn clean install.

Please note that the syntax of this new feature has further evolved and is described in the initial comment of this pull request. E.g. speed_factor and max_speed was merged and instead of first_match there are now statements with if, else and else if.

btw: the map matching service is now already included in the same repository so no more hassle like you had in Recent Core Build Fails

btw2: currently map matching does not support custom profiles per-request - only if defined on the server-side, but I guess this is sufficient for your needs as this was always the case.

Thanks. I’ve downloaded the janino_scripting branch and have successfully built a new jar file. This runs OK with a simple config file and I can see it also has the match endpoint now, which is great.

I’ll need to convert my custom profile files to the new format before testing. Is the plan for the Janino format to become a permanent new format for custom profiles in future releases or is this just experimental?

As noted in our blog and in the documentation the format always was considered alpha state. Now the plan is to make it production grade after the next 3.0 release (in March or April) but it could take another release (in September or October) to find & fix all problems with that format.

And before that 3.0 release the format might also change. But also here I guess the changes will be small if at all.

Thanks, its a good idea to get used to the new format now then!

I’ve been trying it out but have come across a problem. Is there a limit on the number of conditions you can have in a single if statement? It seems to fail when there are more than 4.

The following example fails with an error saying it cannot compile the expression:

priority:
- if: surface == PAVING_STONES || surface == COBBLESTONE || surface == UNPAVED || surface == GRAVEL || surface == FINE_GRAVEL
  multiply by: 0

But the following example works:

priority:
- if: surface == PAVING_STONES || surface == COBBLESTONE || surface == UNPAVED || surface == GRAVEL
  multiply by: 0