From 5eb60ec2d31f0eb6a9f27d77d6ed86e90f241acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= Date: Mon, 7 Sep 2026 22:37:41 +0200 Subject: [PATCH] Map tiles: switch to Esri World Light Gray (OSM 403s file://) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tiles="OpenStreetMap" returned HTTP 403 "referer is required by tile usage policy" in the target browser — OSM graylists refererless requests and a karte.html opened via file:// sends no Referer. Esri's Canvas/World_Light_Gray base + reference layers need neither a key nor a Referer and keep the muted look. New MAP_TILES / MAP_TILES_LABELS / MAP_TILES_ATTR constants; generate_map adds them as explicit TileLayers. Co-Authored-By: Claude Sonnet 5 --- app.py | 23 ++++++++++++++++++----- docs/architecture.md | 2 +- docs/changelog.md | 10 ++++++++++ docs/dev-notes.md | 29 ++++++++++++++++++----------- docs/improvements.md | 10 +++++----- docs/overview.md | 18 ++++++++++-------- 6 files changed, 62 insertions(+), 30 deletions(-) diff --git a/app.py b/app.py index f915082..b8b3b47 100755 --- a/app.py +++ b/app.py @@ -61,9 +61,16 @@ GEOCODE_REGIONS = ["Baden-Württemberg", "Hessen"] MAP_DEFAULT_CENTER = [49.0, 9.0] MAP_DEFAULT_ZOOM = 8 -#: Kartenkacheln. "OpenStreetMap" braucht keinen API-Schlüssel. -#: (Die früher genutzten "CartoDB positron"-Kacheln verlangen seit 2024 einen Schlüssel.) -MAP_TILES = "OpenStreetMap" +#: Kartenkacheln – Esri "World Light Gray" (heller, ruhiger Hintergrund). +#: Braucht keinen API-Schlüssel und keinen Referer (anders als tile.openstreetmap.org, +#: das lokale Dateien mit 403 blockt, und "CartoDB positron", das einen Schlüssel will). +#: Alternativen ohne Schlüssel als Ein-Zeilen-Tausch: +#: Esri Straßenkarte: …/World_Street_Map/MapServer/tile/{z}/{y}/{x} +#: Esri Topo: …/World_Topo_Map/MapServer/tile/{z}/{y}/{x} +_ESRI = "https://server.arcgisonline.com/ArcGIS/rest/services" +MAP_TILES = f"{_ESRI}/Canvas/World_Light_Gray_Base/MapServer/tile/{{z}}/{{y}}/{{x}}" +MAP_TILES_LABELS = f"{_ESRI}/Canvas/World_Light_Gray_Reference/MapServer/tile/{{z}}/{{y}}/{{x}}" +MAP_TILES_ATTR = "Kacheln © Esri" log = logging.getLogger("arbeitsorte") @@ -421,9 +428,15 @@ def generate_map(store: DataStore) -> Path: raise ValueError("Keine Koordinaten vorhanden. Bitte zuerst Einträge speichern.") m = folium.Map( - location=MAP_DEFAULT_CENTER, zoom_start=MAP_DEFAULT_ZOOM, - tiles=MAP_TILES, + location=MAP_DEFAULT_CENTER, zoom_start=MAP_DEFAULT_ZOOM, tiles=None, ) + folium.TileLayer( + MAP_TILES, attr=MAP_TILES_ATTR, name="Karte", control=False, + ).add_to(m) + folium.TileLayer( + MAP_TILES_LABELS, attr=MAP_TILES_ATTR, name="Beschriftung", + control=False, overlay=True, + ).add_to(m) max_count = int(freq["count"].max()) for _, row in freq.iterrows(): diff --git a/docs/architecture.md b/docs/architecture.md index ed411e6..f83694a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -112,6 +112,6 @@ This is the file to ask for when diagnosing a problem on the user's laptop. |------------|-----------|-------------------| | Nominatim (OSM) | geocoding new/edited entries | entries save with blank coords, shown as `⚠ nicht gefunden` | | CDN (jsdelivr, jquery, cloudflare) | rendering `karte.html` | map page loads blank / unstyled | -| OpenStreetMap tiles (`tile.openstreetmap.org`) | map background | blank/grey tiles | +| Esri tiles (`server.arcgisonline.com`) | map background | blank/grey tiles | Existing entries that already have coordinates do **not** need the network. diff --git a/docs/changelog.md b/docs/changelog.md index dbfaab4..4b7bc31 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,16 @@ Notable changes to the app. Newest first. +## 2026-09-07 — map tiles → Esri (OSM 403s local files) + +The switch to `tiles="OpenStreetMap"` worked on the dev machine but the target +laptop's browser got **HTTP 403 "referer is required by tile usage policy"** — +OSM's CDN blocks refererless requests, and a `karte.html` opened as `file://` +sends no `Referer`. Switched to **Esri "World Light Gray"** (base + labels +overlay, `server.arcgisonline.com`): no key, no `Referer` check, muted look +similar to the old positron. Constants `MAP_TILES` / `MAP_TILES_LABELS` / +`MAP_TILES_ATTR`. + ## 2026-09-07 — install split for non-admin users The target account has no sudo, so install is two scripts: diff --git a/docs/dev-notes.md b/docs/dev-notes.md index 1e138fb..1e1513c 100644 --- a/docs/dev-notes.md +++ b/docs/dev-notes.md @@ -36,19 +36,26 @@ Numeric parsing is deliberately deferred to `get_map_data` (`pd.to_numeric(..., errors="coerce")`). Don't "fix" columns to float on load — blank coordinates and leading-zero PLZ both depend on the string representation. -## CartoDB "positron" tiles now require an API key (2026-09-07) +## Map tiles: Carto needs a key, OSM 403s file:// — use Esri (2026-09-07) -The original `folium.Map(tiles="CartoDB positron")` started rendering an -"API KEY REQUIRED / carto.com/basemaps/apikey" watermark – Carto moved its -basemaps behind a key. Switched to `tiles="OpenStreetMap"` (constant -`MAP_TILES`), which needs no key. Trade-off: OSM standard tiles are more -colourful than positron's muted grey. Other no-key options if a lighter look is -wanted: OSM Germany (`tile.openstreetmap.de`), OSM France Hot -(`{s}.tile.openstreetmap.fr/hot`) – both still colourful. Truly muted -key-free basemaps are hard to find now. +Two dead ends before landing on Esri: -Don't assume a "free" third-party tile source stays free – pin the provider in -one constant so swapping it is a one-line change. +1. `folium.Map(tiles="CartoDB positron")` → "API KEY REQUIRED" watermark; Carto + moved basemaps behind a key. +2. `tiles="OpenStreetMap"` → worked in `curl` from the dev machine but the + **user's browser got HTTP 403 "referer is required by tile usage policy"**. + OSM's tile CDN graylists refererless requests, and a `karte.html` opened as a + local `file://` sends no `Referer`. A browser can't add one. Dead end for + this app's "open a local HTML file" model. + +Now: **Esri "World Light Gray"** base + reference (labels) overlay +(`server.arcgisonline.com/ArcGIS/rest/services/Canvas/…`). No key, no `Referer` +check, light/muted look close to the old positron. `MAP_TILES` / +`MAP_TILES_LABELS` constants; comment lists Esri Street/Topo as swaps. + +Lesson: a tile source that works from `curl` or an `http://` page can still fail +from `file://`. Test the map by **opening the generated `karte.html` directly**, +not just by checking the URL in the HTML. ## Nominatim query order matters diff --git a/docs/improvements.md b/docs/improvements.md index edfc351..5524d87 100644 --- a/docs/improvements.md +++ b/docs/improvements.md @@ -163,11 +163,11 @@ or at least skip exact duplicates by default. ## 9. Offline map 🟢 `karte.html` loads Leaflet/jQuery/Bootstrap from CDNs and tiles from -`tile.openstreetmap.org` (`MAP_TILES`; switched away from CartoDB positron, which -now needs an API key). The map is blank without internet even though all the -data is local. If offline use matters, vendor the Leaflet JS/CSS into `data/` -and post-process the Folium output to point at local files (tiles would still -need caching or an offline tile pack — larger effort). +`server.arcgisonline.com` (Esri; `MAP_TILES` — Carto needs a key, OSM 403s +`file://`). The map is blank without internet even though all the data is local. +If offline use matters, vendor the Leaflet JS/CSS into `data/` and post-process +the Folium output to point at local files (tiles would still need caching or an +offline tile pack — larger effort). ## 10. Small code-quality items 🟢 — partly done diff --git a/docs/overview.md b/docs/overview.md index afd69a8..a57a20d 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -65,9 +65,10 @@ A table view of everything in `orte.csv`. Generated by `generate_map()`: -- Base layer: **OpenStreetMap** standard tiles (`MAP_TILES`) – no API key. - Initial view centred on `[49.0, 9.0]`, zoom 8 (roughly Baden-Württemberg); - the view auto-fits to the markers when there is more than one. +- Base layer: **Esri "World Light Gray"** (`MAP_TILES` + a labels overlay) – a + light, muted basemap that needs no API key and no `Referer`. Initial view + centred on `[49.0, 9.0]`, zoom 8; auto-fits to the markers when there is more + than one. - One **`CircleMarker`** per unique `(city, lat, lon)` group. Radius scales linearly with the visit count for that location (`5 + count / max_count * 20` px), colour is DRK red (`#CC0000`). @@ -77,11 +78,12 @@ Generated by `generate_map()`: bar shows a warning). > The generated HTML pulls Leaflet, jQuery and Bootstrap from CDNs, and the map -> tiles from `tile.openstreetmap.org`, so the map needs an internet connection to -> render even though the data is local. To change the look, set `MAP_TILES` near -> the top of `app.py` to another no-key provider Folium knows -> (`"OpenStreetMap"`, `"CartoDB positron"` *now needs a key*, or a custom -> `folium.TileLayer` URL such as OSM Germany / OSM France). +> tiles from `server.arcgisonline.com`, so the map needs an internet connection +> to render even though the data is local. To change the look, edit `MAP_TILES` / +> `MAP_TILES_LABELS` near the top of `app.py` — the comment there lists other +> no-key Esri layers (Street, Topo). `tile.openstreetmap.org` was tried first +> but its tile-usage policy **403s** pages opened from `file://` (no `Referer`); +> `CartoDB positron` needs an API key. ## Geocoding behaviour