More flexibility in GraphHopper

For the next release we try to make the flexibility mode production ready and more flexible (like fixing this issue). E.g. one will be able to import certain OSM attributes so that one can customize per-request if and how it should influence the speed or weighting.

Continuing the discussion from Weighting for bicycle routing:

2 Likes

GraphHopper was always flexible and fast but did not publish its flexibility to non-Java developers.

The aim of this topic is to discuss the direction of a more flexible weighting combined with better separated concerns from FlagEncoders.

Current limitations / disadvantages:

  • currently just guessed average speed, sometimes a ‘priority’ and the specific vehicle access properties are stored in the flags. Here we need more properties like highway tags, surface values, maxspeed etc. Also just one encoder would avoid duplicative information like it is currently the case with the roundabout information which is set in all encoders - i.e. if you have 5, then 4 bits are wasted.
  • the flags itself are limited to 64bits. Here we need a different approach e.g. like retrieving other integers via edge.getData(index) see #505 and #472
  • there should be one GenericFlagEncoder which is used from various weighting functions e.g. a weighting for car, one for bike etc. all extending e.g. GenericWeighting
  • In a next step a more generic weighting should be created which can include running (maths/javascript/etc) functions provided by a URL parameter, see #193
  • JSON should be used to define such weightings. First step is to import this from disc, next step is to POST this per request and make configuration changes of the vehicle (like speed and access properties) per request possible. Here we should use just a Map object in core and in the import and web module we use e.g. moshi - JSONObject does not have a good license and we should get rid of it anyway. This requires #450
  • Furthermore this allows us to include more information in the route output like the highway type like #439 or #311
  • Access: store per vehicle but how to solve time dependent access?
  • consider barrier stuff like #308
  • Other related issues which are then either easy to fix or already fixed by configuring a special weighting: #728, #643, #597, #614, #560, #523, #407, #390, #344, #343, #257, #253, ford #236, truck #223, #205,
  • country specific stuff code be workaround easier, e.g. #504, #469, #175

I have a rough prototype of the first points working already and posted as PR here. This task will cause not one big PR but will be merged rather fast and improved by several follow up PRs.

1 Like

Options for this are:

1. A less flexible but easy to implement JSON config approach:

{ "4_wheel_car": { 
     "default": "car",
     "allow": ["highway=track", "ford=yes", "access=private"],
     "avoid": ["toll=yes", "route=ferry", "highway=motorway"],
     "avoid_factor": [2, 10], 
     "prefer": ["highway=trunk"],
     "urban_speeds": { "trunk":50, "primary":70 },
     "width": 1.9,
     "height": 1.4,
     "consider_one_ways": "yes",
     "crossing_country": "avoid",
     "avoid_area": [41,42,3,4],
     "consider_conditional_restrictions": "yes"
 },
  "my_bike": {
      "default": "bike",
      "prefer": ["route=bicycle"]
 },
 "ch": { "disable": "true" },
}   

Here the problem is how to express that e.g. the speed should be 20 inner city and 50 elsewhere or use a certain speed set for country:de and another set for country:ch? Or should we allow just speed factors? Or specify that some tags are on ways (or on nodes like barriers) and others like route=bicycle are on relations? We could find a solution but it might be more wise to avoid any new language and use the other options instead if this is necessary or allow some combination? Also our aim should be to use low level OSM tags but being able to apply this for other data sources and avoid the complexity e.g. merge some tags into a ‘super tag’ and always use the same units like for weight, height and speed.)

2. A more flexible JSON format similar to what ElasticSearch provides

where we could build boolean expression and combine with other queries.

{ "actions": [{
  "and_conditions": [{ "highway": "primary" }, {"some_more": "condition"}],
  "or_conditions": [{ "some_condition": "but or_conditions cannot exist at the same time as and_conditions" }],
  "action": "use_values",
  "speed": 60,
  "priority": 1.55,
  "time_offset": 5,
  "speed_factor": 2.5
}, {
  "action": "use_speed",
  "and_conditions": [],
  "highway": { "primary": 60, "secondary": 50 },
  "comment": "'shortcut action' -> this is a kind of a shortcut for multiple use_values actions with different speed values"
}]}

The first action sets e.g. the speed to 60 for all highway=primary tags

The second action sets the speed to 60 (for highway=primary) and to 50 (for highway=secondary). We can add here additional conditions like { "and_condition": "filter", "bbox": [1,2,3,4] } to say something like “set the edges only within a certain boundary to the specified values”.

The and_conditions or or_conditions are not allowed to have actions where values are modified, just ‘conditions’.

With this we can model lots of things already like avoiding streets via “priority” changes (without affecting the times) but also changing speed due to traffic jams or completely blocking with (priority=0 or speed=0). Or another and_condition forcing inner or outer city only. time_offset will be useful for customizable turn costs. With some more ‘area selection’ conditions we could even integrate custom data, e.g. for routes depending on the direction of the wind, to make this more compact we could introduce further ‘shortcut actions’.

In the calcWeight method we have the following steps:

2.1. default speed from some alias (“car alias”) from config json, if nothing set it is 0
2.2. use speed from request overwriting config json, via speed. Here we can also apply the speed_factor to just reduce (not set) the default speed defined elsewhere.
2.3. calculate time: t=s/v, with important assert before v>0
2.4. add / remove time via time_offset, with important assert afterwards that t>0
2.5. multiply priority t*=priority, with assert that priority>0
2.6. return t; // in seconds?

Make speed=0 and priority=0 blocking the edge.

3. Offer a very flexible approach via allowing a script language

(#193, painless or janino) or even the scripting lang that brouter implements (I find it a bit too complex for the average dev like me ;)) and just allow this language in the JSON request. Here the Java API has to made very simple to be used and fast from the client side.

All these 3 options have advantages&disadvantages. At the moment I feel we should start with 1. and investigate 2&3 while we collect more requirements and also get a better understanding of what is necessary to keep this API simple, fast and powerful. The advantage of 1. is that we can transform this easier into the best performing expression e.g. "avoid" is easily doable with landmarks but "prefer" would need a 'negative avoid’ to make it workìng for landmarks. And e.g. too big weight differences can lead to heavy performance degradations and with 1. we could limit these differences easier (e.g. allow max. factor 10)

While I thought about this it might be better to either rename our prefix pt. into something else or rename the vehicle pt into public_transit or similar to avoid confusion.