Selfhost OpenRouteService API

After last weeks overpass api selfhost, this week it is selfhosting openrouteservice. They have a ready to modify docker-compose.yml and good documentation for Docker hosting. Maybe a bit too many options and too many choices for someone hosting this the first time.

As last week we will use a Hetzner VPS, I used the same CX32 with 4 cores, 8 GB memory and 80GB disk to start with (and later a CX42 for more memory). Again I used Debian 12 as operating system.

The 8GB of memory are not enough so we create a swapfile with additonal 8GB of memory:

fallocate -l 8G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
swapon --show

Then I installed Docker Engine.

My modified docker-compose.yml is stripped of everything I don't need: removing car-routing and adding walking. I downloaded baden-wuerttemberg-latest.osm.pdf from Geofabrik and put it into a freshly created ors-docker/files/ folder.

---
services:
  ors-app:
    build:
      context: ./
    container_name: ors-app
    ports:
      - "80:8082"  # Expose the ORS API on port 80
      - "9001:9001"
    image: openrouteservice/openrouteservice:v8.0.0
    volumes:
      - ./ors-docker:/home/ors
    environment:
      REBUILD_GRAPHS: False
      CONTAINER_LOG_LEVEL: INFO
      XMS: 4g  # start RAM assigned to java
      XMX: 10g  # max RAM assigned to java. Rule of Thumb: <PBF-size> * <profiles> * 2
      # Example: 1.5 GB pbf size, two profiles (car and foot-walking)
      # -> 1.5 * 2 * 2 = 6. Set xmx to be AT LEAST `-Xmx6g`
      ADDITIONAL_JAVA_OPTS: ""  # further options you want to pass to the java command
      ors.engine.source_file: /home/ors/files/baden-wuerttemberg-latest.osm.pbf
      ors.engine.profiles.car.enabled: false
      ors.engine.profiles.walking.enabled: true

Then run everything with docker compose up. This took a while. Especially the elevation cache and graph building.

I later tried the pbf for the United States (10GB) which needed more memory (I used a bigger instance with 16Gb of memory and 16GB of swap, and it took more than 7 hours to preprocess.

When the preprocessing is finished, the status page (http://<IP-ADDRESS>/ors/v2/status) for me looked like this:

{
  "languages": [
    "cs", "cs-cz", "de", "de-de", "en", "en-us", "eo", "eo-eo", "es", "es-es", "fr", "fr-fr", "gr", "gr-gr",
    "he", "he-il", "hu", "hu-hu", "id", "id-id", "it", "it-it", "ja", "ja-jp", "nb", "nb-no", "ne", "ne-np",
    "nl", "nl-nl", "pl", "pl-pl", "pt", "pt-pt", "ro", "ro-ro", "ru", "ru-ru", "tr", "tr-tr", "zh", "zh-cn"
  ],
  "engine": {"build_date": "2024-03-21T13:55:54Z", "version": "8.0.0"},
  "profiles": {
    "profile 1": {
      "storages": {
        "HillIndex": {"gh_profile": "pedestrian_ors_fastest"},
        "WayCategory": {"gh_profile": "pedestrian_ors_fastest"},
        "WaySurfaceType": {"gh_profile": "pedestrian_ors_fastest"},
        "TrailDifficulty": {"gh_profile": "pedestrian_ors_fastest"}
      },
      "profiles": "foot-walking",
      "creation_date": "",
      "limits": {
        "maximum_distance": 100000,
        "maximum_waypoints": 50,
        "maximum_distance_dynamic_weights": 100000,
        "maximum_distance_avoid_areas": 100000
      }}},
  "services": ["routing", "isochrones", "matrix", "snap"]
}

Same as last week an example for how to use the API. I only needed the routing for a walking distance and duration estimation. As example we will route from the Empire State building in New York to the center of Central Park. The coordinates are: 40.748448,-73.985630 to 40.782773,-73.965363 (both lat,lon).

import httpx

r = httpx.get(
    "http://<IP-ADDRESS>/ors/v2/directions/foot-walking",
    params={
        # here it is lon,lat
        "start": "-73.985630,40.748448",
        "end": "-73.965363,40.782773",
    },
)
print(r.json()["features"][0]["properties"]["summary"])

This returns {'distance': 4421.3, 'duration': 3183.2}, so 4.4 km and 53 minutes. I compared this to Google Maps Routing and it is in the same ballpark.