chore: backup session 2026-06-01b — JupyterHub fix, Hermes Dashboard, OR mbtiles, Trino node.properties
Summary of changes: - JupyterHub: fix DB path (absolute), Dockerfile cleanup, SimpleLocalProcessSpawner - JupyterHub: user eric created as admin - Hermes Dashboard WebUI + TUI chat service (systemd, localhost:9119, auto-boot) - OR mbtiles: generated Martinique PNG tiles (5690 tiles, 10.9MB) — needs PBF for OR - OR mbtiles: restored original PBF with corrected metadata (world bounds, Martinique center) - OR mapsettings: verified center=[-61,14.5], bounds=Martinique, minZoom=0 - Trino: added node.properties (node.environment=production) — needs restart - TODO.md: updated with current state - session_resume_consolide.md: created (per-session summary)
This commit is contained in:
125
scripts/gen_mbtiles_martinique.py
Normal file
125
scripts/gen_mbtiles_martinique.py
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate mbtiles for Martinique by downloading OSM raster tiles.
|
||||
|
||||
Usage: python3 gen_mbtiles_martinique.py
|
||||
Output: /tmp/mapdata_martinique.mbtiles
|
||||
"""
|
||||
import sqlite3, os, math, time, gzip, io, struct, sys
|
||||
import urllib.request, urllib.error
|
||||
|
||||
# Martinique bounds (with buffer)
|
||||
MIN_LON, MIN_LAT = -61.7, 13.9
|
||||
MAX_LON, MAX_LAT = -60.3, 15.1
|
||||
MIN_Z, MAX_Z = 0, 14
|
||||
|
||||
# Number of tiles per zoom level (Marti◆ique is small)
|
||||
# zoom 0: 1, zoom 1: 1, zoom 2: 1, zoom 3: 1, zoom 4: 1
|
||||
# zoom 5: 1, zoom 6: 2, zoom 7: 4, zoom 8: 6, zoom 9: 12
|
||||
# zoom 10: 24, zoom 11: 48, zoom 12: 96, zoom 13: 192, zoom 14: 384
|
||||
|
||||
TMP_DIR = "/tmp/osm_tiles"
|
||||
OUTPUT = "/tmp/mapdata_martinique.mbtiles"
|
||||
OSM_URL = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
DELAY = 0.15 # OSM usage policy: max 2req/s
|
||||
UA = "SmartCityMartinique/1.0 (eric@digitribe.fr)"
|
||||
|
||||
os.makedirs(TMP_DIR, exist_ok=True)
|
||||
|
||||
def deg2tile(lat, lon, z):
|
||||
n = 1 << z
|
||||
x = int((lon + 180.0) / 360.0 * n)
|
||||
y = int((1.0 - math.asinh(math.tan(math.radians(lat))) / math.pi) / 2.0 * n)
|
||||
return max(0, min(n-1, x)), max(0, min(n-1, y))
|
||||
|
||||
def tile_exists_in_mbtiles(c, z, x, y_tms):
|
||||
c.execute("SELECT 1 FROM map WHERE zoom_level=? AND tile_column=? AND tile_row=?", (z, x, y_tms))
|
||||
return c.fetchone() is not None
|
||||
|
||||
def download_tile(z, x, y):
|
||||
"""Download raster tile from OSM. Returns PNG bytes or None."""
|
||||
url = OSM_URL.format(z=z, x=x, y=y)
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={"User-Agent": UA})
|
||||
resp = urllib.request.urlopen(req, timeout=10)
|
||||
data = resp.read()
|
||||
if len(data) > 100:
|
||||
return data
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 404:
|
||||
return None
|
||||
print(f" HTTP {e.code} for {z}/{x}/{y}")
|
||||
except Exception as e:
|
||||
print(f" Error {z}/{x}/{y}: {e}")
|
||||
return None
|
||||
|
||||
def create_mbtiles():
|
||||
if os.path.exists(OUTPUT):
|
||||
os.remove(OUTPUT)
|
||||
conn = sqlite3.connect(OUTPUT)
|
||||
c = conn.cursor()
|
||||
c.execute("CREATE TABLE map (zoom_level INTEGER, tile_column INTEGER, tile_row INTEGER, tile_id TEXT)")
|
||||
c.execute("CREATE TABLE images (tile_data BLOB, tile_id TEXT)")
|
||||
c.execute("CREATE TABLE metadata (name TEXT PRIMARY KEY, value TEXT)")
|
||||
|
||||
meta = [
|
||||
("name", "Martinique"),
|
||||
("type", "overlay"),
|
||||
("version", "1.0"),
|
||||
("description", "OSM raster tiles for Martinique"),
|
||||
("format", "png"),
|
||||
("tilejson", "2.1.0"),
|
||||
("scheme", "tms"),
|
||||
("minzoom", str(MIN_Z)),
|
||||
("maxzoom", str(MAX_Z)),
|
||||
("bounds", f"{MIN_LON},{MIN_LAT},{MAX_LON},{MAX_LAT}"),
|
||||
("center", "-61.0,14.5,10"),
|
||||
("attribution", "© OpenStreetMap contributors"),
|
||||
]
|
||||
c.executemany("INSERT INTO metadata VALUES (?, ?)", meta)
|
||||
return conn, c
|
||||
|
||||
def main():
|
||||
conn, c = create_mbtiles()
|
||||
total = 0
|
||||
downloaded = 0
|
||||
skipped = 0
|
||||
start = time.time()
|
||||
|
||||
for z in range(MIN_Z, MAX_Z + 1):
|
||||
x1, y1 = deg2tile(MIN_LAT, MIN_LON, z)
|
||||
x2, y2 = deg2tile(MAX_LAT, MAX_LON, z)
|
||||
xm, xM = min(x1, x2)-1, max(x1, x2)+1
|
||||
ym, yM = min(y1, y2)-1, max(y1, y2)+1
|
||||
xm = max(0, xm)
|
||||
xM = min((1 << z) - 1, xM)
|
||||
ym = max(0, ym)
|
||||
yM = min((1 << z) - 1, yM)
|
||||
|
||||
z_downloaded = 0
|
||||
for x in range(xm, xM + 1):
|
||||
for y in range(ym, yM + 1):
|
||||
total += 1
|
||||
tms_y = (1 << z) - 1 - y
|
||||
png = download_tile(z, x, y)
|
||||
if png:
|
||||
tid = f"osm_{z}_{x}_{tms_y}"
|
||||
c.execute("INSERT INTO images VALUES (?, ?)", (sqlite3.Binary(png), tid))
|
||||
c.execute("INSERT INTO map VALUES (?, ?, ?, ?)", (z, x, tms_y, tid))
|
||||
downloaded += 1
|
||||
z_downloaded += 1
|
||||
else:
|
||||
skipped += 1
|
||||
time.sleep(DELAY)
|
||||
|
||||
conn.commit()
|
||||
print(f"Zoom {z}: {z_downloaded} tiles (total: {downloaded})", flush=True)
|
||||
|
||||
c.execute("CREATE INDEX idx_map ON map (zoom_level, tile_column, tile_row)")
|
||||
conn.commit()
|
||||
size_mb = os.path.getsize(OUTPUT) / (1024*1024)
|
||||
elapsed = time.time() - start
|
||||
print(f"\nDone! {downloaded}/{total} tiles downloaded, {skipped} empty. {size_mb:.1f} MB in {elapsed:.0f}s")
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user