seed tiles
This commit is contained in:
parent
897aa72cea
commit
9fa252a6af
2 changed files with 136 additions and 0 deletions
|
|
@ -31,6 +31,20 @@ services:
|
||||||
- osrm-walking
|
- osrm-walking
|
||||||
- osrm-cycling
|
- osrm-cycling
|
||||||
|
|
||||||
|
tile-seeder:
|
||||||
|
image: python:3-slim
|
||||||
|
networks:
|
||||||
|
- maps-net
|
||||||
|
volumes:
|
||||||
|
- ./scripts:/scripts:ro
|
||||||
|
environment:
|
||||||
|
BACKEND_URL: "http://backend:8080"
|
||||||
|
SEED_WORKERS: "4"
|
||||||
|
command: python3 /scripts/seed_tiles.py
|
||||||
|
restart: "no"
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
|
|
|
||||||
122
backend/scripts/seed_tiles.py
Normal file
122
backend/scripts/seed_tiles.py
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Pre-warm the Redis tile cache by fetching all tiles for a bounding box.
|
||||||
|
|
||||||
|
Tiles are fetched through the backend, which caches them in Redis automatically.
|
||||||
|
Run this in the background after starting the stack:
|
||||||
|
|
||||||
|
nohup python3 scripts/seed_tiles.py &
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 seed_tiles.py [min_lon] [min_lat] [max_lon] [max_lat] [min_zoom] [max_zoom]
|
||||||
|
|
||||||
|
Defaults to the Netherlands at zoom levels 0-12.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
BACKEND_URL = os.environ.get("BACKEND_URL", "http://localhost:8080")
|
||||||
|
LAYERS = ["planet_osm_polygon", "planet_osm_line", "planet_osm_point", "planet_osm_roads"]
|
||||||
|
WORKERS = int(os.environ.get("SEED_WORKERS", "4"))
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_backend():
|
||||||
|
"""Wait until the backend health endpoint responds."""
|
||||||
|
url = f"{BACKEND_URL}/health"
|
||||||
|
print(f"Waiting for backend at {url}...")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(url, timeout=5) as resp:
|
||||||
|
if resp.status == 200:
|
||||||
|
print("Backend is up.")
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
|
||||||
|
def lon_to_x(lon, zoom):
|
||||||
|
return int((lon + 180.0) / 360.0 * (1 << zoom))
|
||||||
|
|
||||||
|
|
||||||
|
def lat_to_y(lat, zoom):
|
||||||
|
lat_r = math.radians(lat)
|
||||||
|
return int((1.0 - math.log(math.tan(lat_r) + 1.0 / math.cos(lat_r)) / math.pi) / 2.0 * (1 << zoom))
|
||||||
|
|
||||||
|
|
||||||
|
def tiles_for_bbox(min_lon, min_lat, max_lon, max_lat, zoom):
|
||||||
|
max_coord = (1 << zoom) - 1
|
||||||
|
x_min = max(0, min(lon_to_x(min_lon, zoom), max_coord))
|
||||||
|
x_max = max(0, min(lon_to_x(max_lon, zoom), max_coord))
|
||||||
|
y_min = max(0, min(lat_to_y(max_lat, zoom), max_coord)) # max_lat → smaller y
|
||||||
|
y_max = max(0, min(lat_to_y(min_lat, zoom), max_coord)) # min_lat → larger y
|
||||||
|
for x in range(x_min, x_max + 1):
|
||||||
|
for y in range(y_min, y_max + 1):
|
||||||
|
yield zoom, x, y
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_tile(layer, z, x, y):
|
||||||
|
url = f"{BACKEND_URL}/tiles/{layer}/{z}/{x}/{y}.pbf"
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(url, timeout=120) as resp:
|
||||||
|
return resp.status == 200
|
||||||
|
except urllib.error.HTTPError:
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
wait_for_backend()
|
||||||
|
|
||||||
|
min_lon = float(sys.argv[1]) if len(sys.argv) > 1 else 3.3
|
||||||
|
min_lat = float(sys.argv[2]) if len(sys.argv) > 2 else 50.7
|
||||||
|
max_lon = float(sys.argv[3]) if len(sys.argv) > 3 else 7.2
|
||||||
|
max_lat = float(sys.argv[4]) if len(sys.argv) > 4 else 53.6
|
||||||
|
min_zoom = int(sys.argv[5]) if len(sys.argv) > 5 else 0
|
||||||
|
max_zoom = int(sys.argv[6]) if len(sys.argv) > 6 else 12
|
||||||
|
|
||||||
|
# Build full task list
|
||||||
|
tasks = []
|
||||||
|
for zoom in range(min_zoom, max_zoom + 1):
|
||||||
|
coords = list(tiles_for_bbox(min_lon, min_lat, max_lon, max_lat, zoom))
|
||||||
|
tile_count = len(coords)
|
||||||
|
print(f" z{zoom}: {tile_count} tiles × {len(LAYERS)} layers = {tile_count * len(LAYERS)} requests")
|
||||||
|
for z, x, y in coords:
|
||||||
|
for layer in LAYERS:
|
||||||
|
tasks.append((layer, z, x, y))
|
||||||
|
|
||||||
|
total = len(tasks)
|
||||||
|
print(f"\nTotal: {total} requests — starting with {WORKERS} workers...\n")
|
||||||
|
|
||||||
|
done = 0
|
||||||
|
errors = 0
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
with ThreadPoolExecutor(max_workers=WORKERS) as executor:
|
||||||
|
futures = {executor.submit(fetch_tile, *t): t for t in tasks}
|
||||||
|
for future in as_completed(futures):
|
||||||
|
ok = future.result()
|
||||||
|
done += 1
|
||||||
|
if not ok:
|
||||||
|
errors += 1
|
||||||
|
if done % 50 == 0 or done == total:
|
||||||
|
elapsed = time.time() - start
|
||||||
|
rate = done / elapsed if elapsed > 0 else 0
|
||||||
|
eta = (total - done) / rate if rate > 0 else 0
|
||||||
|
pct = 100 * done // total
|
||||||
|
print(f" {done}/{total} ({pct}%) | {errors} errors | {rate:.1f} req/s | ETA {eta:.0f}s")
|
||||||
|
|
||||||
|
elapsed = time.time() - start
|
||||||
|
print(f"\nDone: {total - errors}/{total} tiles seeded in {elapsed:.0f}s")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Add table
Reference in a new issue