Map tiles: switch to Esri World Light Gray (OSM 403s file://)
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 <noreply@anthropic.com>
This commit is contained in:
@@ -61,9 +61,16 @@ GEOCODE_REGIONS = ["Baden-Württemberg", "Hessen"]
|
|||||||
MAP_DEFAULT_CENTER = [49.0, 9.0]
|
MAP_DEFAULT_CENTER = [49.0, 9.0]
|
||||||
MAP_DEFAULT_ZOOM = 8
|
MAP_DEFAULT_ZOOM = 8
|
||||||
|
|
||||||
#: Kartenkacheln. "OpenStreetMap" braucht keinen API-Schlüssel.
|
#: Kartenkacheln – Esri "World Light Gray" (heller, ruhiger Hintergrund).
|
||||||
#: (Die früher genutzten "CartoDB positron"-Kacheln verlangen seit 2024 einen Schlüssel.)
|
#: Braucht keinen API-Schlüssel und keinen Referer (anders als tile.openstreetmap.org,
|
||||||
MAP_TILES = "OpenStreetMap"
|
#: 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")
|
log = logging.getLogger("arbeitsorte")
|
||||||
|
|
||||||
@@ -421,9 +428,15 @@ def generate_map(store: DataStore) -> Path:
|
|||||||
raise ValueError("Keine Koordinaten vorhanden. Bitte zuerst Einträge speichern.")
|
raise ValueError("Keine Koordinaten vorhanden. Bitte zuerst Einträge speichern.")
|
||||||
|
|
||||||
m = folium.Map(
|
m = folium.Map(
|
||||||
location=MAP_DEFAULT_CENTER, zoom_start=MAP_DEFAULT_ZOOM,
|
location=MAP_DEFAULT_CENTER, zoom_start=MAP_DEFAULT_ZOOM, tiles=None,
|
||||||
tiles=MAP_TILES,
|
|
||||||
)
|
)
|
||||||
|
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())
|
max_count = int(freq["count"].max())
|
||||||
|
|
||||||
for _, row in freq.iterrows():
|
for _, row in freq.iterrows():
|
||||||
|
|||||||
@@ -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` |
|
| 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 |
|
| 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.
|
Existing entries that already have coordinates do **not** need the network.
|
||||||
|
|||||||
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
Notable changes to the app. Newest first.
|
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
|
## 2026-09-07 — install split for non-admin users
|
||||||
|
|
||||||
The target account has no sudo, so install is two scripts:
|
The target account has no sudo, so install is two scripts:
|
||||||
|
|||||||
+18
-11
@@ -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
|
errors="coerce")`). Don't "fix" columns to float on load — blank coordinates and
|
||||||
leading-zero PLZ both depend on the string representation.
|
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
|
Two dead ends before landing on Esri:
|
||||||
"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.
|
|
||||||
|
|
||||||
Don't assume a "free" third-party tile source stays free – pin the provider in
|
1. `folium.Map(tiles="CartoDB positron")` → "API KEY REQUIRED" watermark; Carto
|
||||||
one constant so swapping it is a one-line change.
|
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
|
## Nominatim query order matters
|
||||||
|
|
||||||
|
|||||||
@@ -163,11 +163,11 @@ or at least skip exact duplicates by default.
|
|||||||
## 9. Offline map 🟢
|
## 9. Offline map 🟢
|
||||||
|
|
||||||
`karte.html` loads Leaflet/jQuery/Bootstrap from CDNs and tiles from
|
`karte.html` loads Leaflet/jQuery/Bootstrap from CDNs and tiles from
|
||||||
`tile.openstreetmap.org` (`MAP_TILES`; switched away from CartoDB positron, which
|
`server.arcgisonline.com` (Esri; `MAP_TILES` — Carto needs a key, OSM 403s
|
||||||
now needs an API key). The map is blank without internet even though all the
|
`file://`). The map is blank without internet even though all the data is local.
|
||||||
data is local. If offline use matters, vendor the Leaflet JS/CSS into `data/`
|
If offline use matters, vendor the Leaflet JS/CSS into `data/` and post-process
|
||||||
and post-process the Folium output to point at local files (tiles would still
|
the Folium output to point at local files (tiles would still need caching or an
|
||||||
need caching or an offline tile pack — larger effort).
|
offline tile pack — larger effort).
|
||||||
|
|
||||||
## 10. Small code-quality items 🟢 — partly done
|
## 10. Small code-quality items 🟢 — partly done
|
||||||
|
|
||||||
|
|||||||
+10
-8
@@ -65,9 +65,10 @@ A table view of everything in `orte.csv`.
|
|||||||
|
|
||||||
Generated by `generate_map()`:
|
Generated by `generate_map()`:
|
||||||
|
|
||||||
- Base layer: **OpenStreetMap** standard tiles (`MAP_TILES`) – no API key.
|
- Base layer: **Esri "World Light Gray"** (`MAP_TILES` + a labels overlay) – a
|
||||||
Initial view centred on `[49.0, 9.0]`, zoom 8 (roughly Baden-Württemberg);
|
light, muted basemap that needs no API key and no `Referer`. Initial view
|
||||||
the view auto-fits to the markers when there is more than one.
|
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
|
- One **`CircleMarker`** per unique `(city, lat, lon)` group. Radius scales
|
||||||
linearly with the visit count for that location
|
linearly with the visit count for that location
|
||||||
(`5 + count / max_count * 20` px), colour is DRK red (`#CC0000`).
|
(`5 + count / max_count * 20` px), colour is DRK red (`#CC0000`).
|
||||||
@@ -77,11 +78,12 @@ Generated by `generate_map()`:
|
|||||||
bar shows a warning).
|
bar shows a warning).
|
||||||
|
|
||||||
> The generated HTML pulls Leaflet, jQuery and Bootstrap from CDNs, and the map
|
> 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
|
> tiles from `server.arcgisonline.com`, so the map needs an internet connection
|
||||||
> render even though the data is local. To change the look, set `MAP_TILES` near
|
> to render even though the data is local. To change the look, edit `MAP_TILES` /
|
||||||
> the top of `app.py` to another no-key provider Folium knows
|
> `MAP_TILES_LABELS` near the top of `app.py` — the comment there lists other
|
||||||
> (`"OpenStreetMap"`, `"CartoDB positron"` *now needs a key*, or a custom
|
> no-key Esri layers (Street, Topo). `tile.openstreetmap.org` was tried first
|
||||||
> `folium.TileLayer` URL such as OSM Germany / OSM France).
|
> but its tile-usage policy **403s** pages opened from `file://` (no `Referer`);
|
||||||
|
> `CartoDB positron` needs an API key.
|
||||||
|
|
||||||
## Geocoding behaviour
|
## Geocoding behaviour
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user