Writing a BRouter profile
Posted on December 13, 2018 in Dev • 4 min read
I recently started to have a deeper look into routing for bikes, on top of OpenStreetMap data and came across BRouter. This article is a follow-up to the first and second ones and it will quickly cover how to write your own profile or extend a profile.
The main (official) documentation about profile development is the
profile developers
guide. It can be
quite lengthy but covers well all the basis. The main parts you have to worry
about to write your profile is the
lookups.dat
and the profiles files.
The lookups.dat
has already been covered in the previous
article of this series. The only
additional point to keep in mind is that when you use aliases in this file,
only the first value will be emitted: zone:maxspeed;0000001616 20 DE:20
FR:20
will only emit a single usable value (20
) in profile for the three
possible values (20
, DE:20
and FR:20
).
Quick overview of the key points for writing a BRouter profile
The profile developers guide covers all the valid expressions in the profile file (variables, operations, etc). Note that all operators use a prefix notation and not the (rather usual) infix notation.
Let us have a look at the
trekking.brf
profile. There are three different contexts: global, way and node.
The global
context
defines global variables which are used across all the profile and for routing
operations. The full list of available global variables can be found in the
brouter-core/.../RoutingContext.java#readGlobalConfig
file, which is the main routing entry point.
The way
context
defines the cost (penalties) for OSM ways (road segments). Let us put aside
the “classifier” variables for the moment.
Ways and nodes are processed by the
brouter-core/StdPath.java
(processWaySection
and processTargetNode
) and
brouter-core/.../KinematicPath.java
(same methods). StdPath.java
is used by default, KinematicPath.java
is
used for profiles relying on the Kinematic model, such as car-fast.brf
profile.
The main variables to assign in the way
context are:
turncost
which is the cost for a 90° turn (cost of a turn isturncost * cos(angle)
).initialcost
which is a constant initial cost for a way.costfactor
which is a cost proportional to the length of the way and is actually the main cost source.
In the node
context, only initialcost
is to be filled.
Personal tips for writing BRouter profiles
Here is a quick list of personal tips after writing and contributing to some BRouter profiles (and reviewing a bunch of them!):
- There are a lot, really a lot, quite too many, really too many profiles out there. This is a personal view but I rather believe in a “one size fits all” approach for BRouter profiles than a super optimized profile for each possible use case, including the current weather. Plus having many variables means you are likely to over-optimize for your specific route and the profile will not be easily reusable.
- When testing your profile on routes, try to use diverse routes and check on the full set. Optimizing for a particular use case might actually be over-optimization and is likely to give bad results on other routes.
brouter-web
has a “Data” tab which gives the complete details of the current route and the associated costs. Coupled with putting waypoints to force a particular route, it is really a super valuable tool to write profiles and find about costs!
Unit-testing BRouter profiles for easier development
Wetneb introduced the idea of having a kind of unit-testing to help writing profiles in this issue. This is a super useful idea indeed, to be able to compare profiles and prevent regressions when tweaking variables.
I am currently using this iPython notebook to check my profiles and ensure they behave correctly.
In this notebook, I tried to have a mix of routes and edge cases to cover typical use cases and ensure my profile is not overfitting for a particular use case:
- Complete routes I am familiar with, either in large city (Paris), medium sized city (Clermont-Ferrand), following bike routes (cycle travel) and in the countryside (out of marked bike routes).
- Tests on specific features. For these tests, you should take care to have a “stable” route meaning that if you move slightly the start or end point for your edge case, the BRouter route is not moving, so that the test can actually be significant (and avoid hysteretic behavior).
These tests should be run using a predefined set of segments4
files to ensure the results
are stable (as OSM data may evolve in time). All instructions on using it
should be available in the
notebook.
Feel free to reuse these tests for your own profiles. I’ll gladly take any PR to improve coverage!