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 is turncost * 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!