Skip to content

Give an option to append valhalla tiles to style.json file#312

Merged
mustaphaturhan merged 3 commits intomasterfrom
feat/append-valhalla-tiles
Jan 19, 2026
Merged

Give an option to append valhalla tiles to style.json file#312
mustaphaturhan merged 3 commits intomasterfrom
feat/append-valhalla-tiles

Conversation

@mustaphaturhan
Copy link
Collaborator

🛠️ Fixes Issue

Related with #307.

👨‍💻 Changes proposed

  • Adds new "Append Valhalla layers" switch to the top of tiles panel
  • When it is selected, it also lists the prepared layers in the layer list, so user can select/de-select visibility of the layer.

📄 Note to reviewers

  • In localhost, we are getting a lot of CORS errors. Probably it will happen in our demo too.
  • I believe we set the max zoom level as 14 in the back-end. When we zoom a lot, the back-end throws error 400 with a response: {"error_code":175,"error":"Exceeded max zoom level of: 14","status_code":400,"status":"Bad Request"}
  • When we add new layers to tile server, we need to update src/components/tiles/valhalla-layers.ts file manually.

📷 Screenshots

image image

@ghost
Copy link

ghost commented Jan 18, 2026

Preview is ready! 🚀 You can view it here: https://valhalla-app-tests.gis-ops.com/312

@nilsnolde
Copy link
Member

nilsnolde commented Jan 18, 2026

In localhost, we are getting a lot of CORS errors. Probably it will happen in our demo too.

that's strange.. our nginx seems to be set up ok:

  location ~ ^/(route|optimized_route|status|isochrone|trace_route|trace_attributes|sources_to_targets|expansion|height|locate|tile) {
    # Preflighted requests
    if ($request_method = OPTIONS ) {
      add_header "Access-Control-Allow-Origin"  *;
      # Let the client cache for 1 day max (e.g. Firefox)
      add_header "Access-Control-Max-Age" 86400;
      add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD";
      add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type";
      return 204;
    }

    limit_req zone=valhalla_per_ip_rate burst=10;
    limit_req zone=valhalla_global_limit;
    limit_req_status 429;
    proxy_pass http://valhalla2.openstreetmap.de;
    proxy_set_header Host valhalla2.openstreetmap.de;
   }

which also reminds me I should set up another limit_req_zone for /tile I guess (though the debug tiles are so huge, maybe it's better to leave it..).

I believe we set the max zoom level as 14 in the back-end. When we zoom a lot, the back-end throws error 400 with a response: {"error_code":175,"error":"Exceeded max zoom level of: 14","status_code":400,"status":"Bad Request"}

right.. I'm not sure how to properly do this. what we really want is that maplibre requests level 14 tiles for anything beyond level 14. atm we don't let a client (or service) specify overzooming capabilities, i.e. increase the extent/resolution of the last serveable zoom level. I thought any 4xx on higher zoom levels would automatically make maplibre walk up the tree until if finds a 200 response. but maybe it needs to be a 404 response? did you come across any docs about that? I also wonder why that takes such a long time to resolve 4xx responses, maybe too many requests for the server to handle concurrently (though that seems weird, we run on 16 threads..)?

@mustaphaturhan
Copy link
Collaborator Author

@nilsnolde my browser says No 'Access-Control-Allow-Origin' header is present on the requested resource. and my humble opinion is, this happens because you are doing add_header "Access-Control-Allow-Origin" *; only if $request_method = OPTIONS. maybe:

location ~ ^/(route|optimized_route|status|isochrone|trace_route|trace_attributes|sources_to_targets|expansion|height|locate|tile) {
    # Add CORS headers to ALL responses
    add_header "Access-Control-Allow-Origin" * always;
    add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD" always;
    add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type" always;

    # Preflighted requests
    if ($request_method = OPTIONS) {
        add_header "Access-Control-Allow-Origin" * always;
        add_header "Access-Control-Max-Age" 86400;
        add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD";
        add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type";
        return 204;
    }

    limit_req zone=valhalla_per_ip_rate burst=10;
    limit_req zone=valhalla_global_limit;
    limit_req_status 429;
    proxy_pass http://valhalla2.openstreetmap.de;
    proxy_set_header Host valhalla2.openstreetmap.de;
}

would fix the problem, but I am not sure since my devops capabilities are low :( this is a straight answer from opus 4.5.

what we really want is that maplibre requests level 14 tiles for anything beyond level 14

I see now... so the thing is, I was porting our default, recommended style.json file directly and we are defining our styles like this:

"sources": {
    "valhalla": {
      "type": "vector",
      "tiles": [
        "http://localhost:8002/tile?json=%7B%22tile%22%3A%7B%22z%22%3A{z}%2C%22x%22%3A{x}%2C%22y%22%3A{y}%7D%7D"
      ],
      "minzoom": 7,
      "maxzoom": 22, // this is problematic because we are only supporting maxzoom level 14.
      "scheme": "xyz"
    }
},

I updated maxzoom with 14, now we are not sending requests for zoom levels > 14 at all and it saves the day.

@nilsnolde
Copy link
Member

Ah cool, that was easy then! Seems like it does now what I was hoping for.

I also didn't set up the nginx config, but I figured all our requests made a preflight bcs they're not "simple". CORS should then also fail for routing & isochrones (but maybe we use some headers there that provoke a preflight and for tiles/maplibre that's not the case). It's strange that it only fails for some few tiles requests and I can't see that any tile is actually missing visually. Still, AI is right, CORS should be enabled for all methods. Hope that'll fix it!

@mustaphaturhan
Copy link
Collaborator Author

@nilsnolde what about the user experience? do you think "append valhalla layers" switch is working for what we want? we can merge the pr if so.

Copy link
Member

@nilsnolde nilsnolde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's good! I'm not sure anyone would need to enable/disable background map features, but it's a nice gimmick:)

@mustaphaturhan mustaphaturhan merged commit 34776c3 into master Jan 19, 2026
3 checks passed
@mustaphaturhan mustaphaturhan deleted the feat/append-valhalla-tiles branch January 19, 2026 08:16
@nilsnolde
Copy link
Member

hm, smth is really weird here.. I just naively tried your snippet for nginx, now I get 2 error messages for CORS:

  • No 'Access-Control-Allow-Origin' header is present on the requested resource.
  • The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed. Have the server send the header with a valid value.

haha wtf.. I reverted back. no idea why there's 2 conflicting messages for the same endpoint.. I'm inclined to leave it the way it is for now, I have little motivation to dive into our wretched server setup (it's distributed over 2 servers and involved both nginx & apache..)

@mustaphaturhan
Copy link
Collaborator Author

@nilsnolde i am wondering if the reason is not related with our CORS configuration, but it is rate limits because i don't understand why some of the requests doesn't fire CORS errors while the other ones do. i mean maplibre-gl request many tiles at once, and we have the config limit_req zone=valhalla_per_ip_rate burst=10;.

before giving up, maybe we can try this:

location ~ ^/(route|optimized_route|status|isochrone|trace_route|trace_attributes|sources_to_targets|expansion|height|locate|tile) {
    # Hide header from upstream to avoid duplicates (e.g., "*, *")
    proxy_hide_header 'Access-Control-Allow-Origin';

    # Add CORS header authoritatively for ALL responses (including 429/50x errors)
    add_header 'Access-Control-Allow-Origin' '*' always;

    # Preflighted requests
    if ($request_method = OPTIONS) {
        add_header "Access-Control-Allow-Origin" * always;
        add_header "Access-Control-Max-Age" 86400;
        add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD";
        add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type";
        return 204;
    }

    limit_req zone=valhalla_per_ip_rate burst=10;
    limit_req zone=valhalla_global_limit;
    limit_req_status 429;
    proxy_pass http://valhalla2.openstreetmap.de;
    proxy_set_header Host valhalla2.openstreetmap.de;
}

or since the other endpoints already working, we can split the configuration into two blocks and improve the burst of tile endpoint:

# --------------------------------------------------------
# 1. TILE ENDPOINT (Higher Rate Limit)
# --------------------------------------------------------
location ~ ^/tile {
    proxy_hide_header 'Access-Control-Allow-Origin';

    add_header 'Access-Control-Allow-Origin' '*' always;

    if ($request_method = OPTIONS) {
        add_header "Access-Control-Allow-Origin" * always;
        add_header "Access-Control-Max-Age" 86400;
        add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD";
        add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type";
        return 204;
    }

    limit_req zone=valhalla_per_ip_rate burst=25; 
    limit_req zone=valhalla_global_limit;
    limit_req_status 429;

    proxy_pass http://valhalla2.openstreetmap.de;
    proxy_set_header Host valhalla2.openstreetmap.de;
}

# --------------------------------------------------------
# 2. API ENDPOINTS (Strict Rate Limit)
# --------------------------------------------------------
location ~ ^/(route|optimized_route|status|isochrone|trace_route|trace_attributes|sources_to_targets|expansion|height|locate) {
    # Preflighted requests
    if ($request_method = OPTIONS ) {
      add_header "Access-Control-Allow-Origin"  *;
      # Let the client cache for 1 day max (e.g. Firefox)
      add_header "Access-Control-Max-Age" 86400;
      add_header "Access-Control-Allow-Methods" "GET, POST, OPTIONS, HEAD";
      add_header "Access-Control-Allow-Headers" "Authorization, Origin, X-Requested-With, Content-Type";
      return 204;
    }

    limit_req zone=valhalla_per_ip_rate burst=10;
    limit_req zone=valhalla_global_limit;
    limit_req_status 429;
    proxy_pass http://valhalla2.openstreetmap.de;
    proxy_set_header Host valhalla2.openstreetmap.de;
}

self hosting is hard, but fun :D

@nilsnolde
Copy link
Member

I can trigger 429 regardless of CORS. Since those tiles are exceptionally huge, we might not even have problems with rate limiting in practice (or even want a different rate limit to prevent connection hogging).

What disturbs me is that I got 2 different CORS errors, which suggest there's more than one handler right.. I can try a bit more later, at least it's super quick:)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants