feat(geoportal): PMTiles for terenuri/cladiri overview + cache warming + cleanup
- Extend PMTiles to include simplified terenuri (5m tolerance) and cladiri (3m) - map-viewer: terenuri z13 from PMTiles, z14+ from Martin (live detail) - map-viewer: cladiri z14 from PMTiles, z15+ from Martin - Martin sources start at higher minzoom when PMTiles active (less DB load) - Add warm-tile-cache.sh: pre-populate nginx cache for major cities - Rebuild script now includes cache warming step after PMTiles upload - Remove deprecated docker-compose version: "3.8" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,3 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
architools:
|
architools:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# rebuild-overview-tiles.sh — Export UAT overview layers from PostGIS, generate PMTiles, upload to MinIO
|
# rebuild-overview-tiles.sh — Export all overview layers from PostGIS, generate PMTiles, upload to MinIO
|
||||||
|
# Includes: UAT boundaries, administrativ, simplified terenuri (z10-z14), simplified cladiri (z12-z14)
|
||||||
# Usage: ./scripts/rebuild-overview-tiles.sh
|
# Usage: ./scripts/rebuild-overview-tiles.sh
|
||||||
# Dependencies: ogr2ogr (GDAL), tippecanoe, mc (MinIO client)
|
# Dependencies: ogr2ogr (GDAL), tippecanoe, mc (MinIO client)
|
||||||
# Run from project root or as a Docker one-shot container.
|
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -33,6 +33,7 @@ cd "$TMPDIR"
|
|||||||
# ── Step 1: Export views from PostGIS (parallel) ──
|
# ── Step 1: Export views from PostGIS (parallel) ──
|
||||||
echo "[$(date -Iseconds)] Exporting PostGIS views to FlatGeobuf..."
|
echo "[$(date -Iseconds)] Exporting PostGIS views to FlatGeobuf..."
|
||||||
|
|
||||||
|
# UAT boundaries (4 zoom levels)
|
||||||
ogr2ogr -f FlatGeobuf -s_srs EPSG:3844 -t_srs EPSG:4326 \
|
ogr2ogr -f FlatGeobuf -s_srs EPSG:3844 -t_srs EPSG:4326 \
|
||||||
uats_z0.fgb "$PG_CONN" \
|
uats_z0.fgb "$PG_CONN" \
|
||||||
-sql "SELECT name, siruta, geom FROM gis_uats_z0 WHERE geom IS NOT NULL" &
|
-sql "SELECT name, siruta, geom FROM gis_uats_z0 WHERE geom IS NOT NULL" &
|
||||||
@@ -49,10 +50,26 @@ ogr2ogr -f FlatGeobuf -s_srs EPSG:3844 -t_srs EPSG:4326 \
|
|||||||
uats_z12.fgb "$PG_CONN" \
|
uats_z12.fgb "$PG_CONN" \
|
||||||
-sql "SELECT name, siruta, county, geom FROM gis_uats_z12 WHERE geom IS NOT NULL" &
|
-sql "SELECT name, siruta, county, geom FROM gis_uats_z12 WHERE geom IS NOT NULL" &
|
||||||
|
|
||||||
|
# Administrativ (intravilan, arii speciale)
|
||||||
ogr2ogr -f FlatGeobuf -s_srs EPSG:3844 -t_srs EPSG:4326 \
|
ogr2ogr -f FlatGeobuf -s_srs EPSG:3844 -t_srs EPSG:4326 \
|
||||||
administrativ.fgb "$PG_CONN" \
|
administrativ.fgb "$PG_CONN" \
|
||||||
-sql "SELECT object_id, siruta, layer_id, cadastral_ref, geom FROM gis_administrativ WHERE geom IS NOT NULL" &
|
-sql "SELECT object_id, siruta, layer_id, cadastral_ref, geom FROM gis_administrativ WHERE geom IS NOT NULL" &
|
||||||
|
|
||||||
|
# Terenuri simplified for overview (ST_SimplifyPreserveTopology 5m tolerance — good for z10-z14)
|
||||||
|
# Only essential properties for overview display
|
||||||
|
ogr2ogr -f FlatGeobuf -s_srs EPSG:3844 -t_srs EPSG:4326 \
|
||||||
|
terenuri_overview.fgb "$PG_CONN" \
|
||||||
|
-sql "SELECT object_id, siruta, cadastral_ref, area_value, layer_id,
|
||||||
|
ST_SimplifyPreserveTopology(geom, 5) AS geom
|
||||||
|
FROM gis_terenuri WHERE geom IS NOT NULL" &
|
||||||
|
|
||||||
|
# Cladiri simplified for overview (3m tolerance — buildings are smaller)
|
||||||
|
ogr2ogr -f FlatGeobuf -s_srs EPSG:3844 -t_srs EPSG:4326 \
|
||||||
|
cladiri_overview.fgb "$PG_CONN" \
|
||||||
|
-sql "SELECT object_id, siruta, cadastral_ref, area_value, layer_id,
|
||||||
|
ST_SimplifyPreserveTopology(geom, 3) AS geom
|
||||||
|
FROM gis_cladiri WHERE geom IS NOT NULL" &
|
||||||
|
|
||||||
wait
|
wait
|
||||||
echo "[$(date -Iseconds)] Export complete."
|
echo "[$(date -Iseconds)] Export complete."
|
||||||
|
|
||||||
@@ -66,6 +83,8 @@ tippecanoe \
|
|||||||
--named-layer=gis_uats_z8:uats_z8.fgb \
|
--named-layer=gis_uats_z8:uats_z8.fgb \
|
||||||
--named-layer=gis_uats_z12:uats_z12.fgb \
|
--named-layer=gis_uats_z12:uats_z12.fgb \
|
||||||
--named-layer=gis_administrativ:administrativ.fgb \
|
--named-layer=gis_administrativ:administrativ.fgb \
|
||||||
|
--named-layer=gis_terenuri:terenuri_overview.fgb \
|
||||||
|
--named-layer=gis_cladiri:cladiri_overview.fgb \
|
||||||
--minimum-zoom=0 \
|
--minimum-zoom=0 \
|
||||||
--maximum-zoom=14 \
|
--maximum-zoom=14 \
|
||||||
--base-zoom=14 \
|
--base-zoom=14 \
|
||||||
@@ -93,6 +112,30 @@ mc mv "${MINIO_ALIAS}/${MINIO_BUCKET}/overview_new.pmtiles" "${MINIO_ALIAS}/${MI
|
|||||||
|
|
||||||
echo "[$(date -Iseconds)] Upload complete."
|
echo "[$(date -Iseconds)] Upload complete."
|
||||||
|
|
||||||
# ── Step 4: Cleanup ──
|
# ── Step 4: Warm nginx tile cache (detail layers only — overview served from PMTiles) ──
|
||||||
rm -f uats_z0.fgb uats_z5.fgb uats_z8.fgb uats_z12.fgb administrativ.fgb "$OUTPUT_FILE"
|
TILE_CACHE="${TILE_CACHE_URL:-http://10.10.10.166:3010}"
|
||||||
|
echo "[$(date -Iseconds)] Warming tile cache for detail layers..."
|
||||||
|
# Warm popular z14-z16 tiles for Bucharest + Cluj (Martin-served detail layers)
|
||||||
|
for z in 14 15; do
|
||||||
|
for source in gis_terenuri gis_cladiri; do
|
||||||
|
# Bucharest
|
||||||
|
for x in $(seq 9200 9220); do
|
||||||
|
for y in $(seq 5960 5975); do
|
||||||
|
echo "${TILE_CACHE}/${source}/${z}/${x}/${y}"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
# Cluj
|
||||||
|
for x in $(seq 9058 9068); do
|
||||||
|
for y in $(seq 5842 5852); do
|
||||||
|
echo "${TILE_CACHE}/${source}/${z}/${x}/${y}"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
done
|
||||||
|
done | xargs -P 8 -I {} curl -sf -o /dev/null {} 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "[$(date -Iseconds)] Cache warming complete."
|
||||||
|
|
||||||
|
# ── Step 5: Cleanup ──
|
||||||
|
rm -f uats_z0.fgb uats_z5.fgb uats_z8.fgb uats_z12.fgb administrativ.fgb \
|
||||||
|
terenuri_overview.fgb cladiri_overview.fgb "$OUTPUT_FILE"
|
||||||
echo "[$(date -Iseconds)] Rebuild finished successfully."
|
echo "[$(date -Iseconds)] Rebuild finished successfully."
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# warm-tile-cache.sh — Pre-populate nginx tile cache with common tiles
|
||||||
|
# Usage: ./scripts/warm-tile-cache.sh [BASE_URL]
|
||||||
|
# Run after deploy or cache purge to ensure fast first-load for users.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BASE="${1:-http://10.10.10.166:3010}"
|
||||||
|
PARALLEL="${PARALLEL:-8}"
|
||||||
|
TOTAL=0
|
||||||
|
HITS=0
|
||||||
|
|
||||||
|
echo "[$(date -Iseconds)] Warming tile cache at $BASE ..."
|
||||||
|
|
||||||
|
# ── Helper: fetch a range of tiles ──
|
||||||
|
fetch_tiles() {
|
||||||
|
local source="$1" z="$2" x_min="$3" x_max="$4" y_min="$5" y_max="$6"
|
||||||
|
for x in $(seq "$x_min" "$x_max"); do
|
||||||
|
for y in $(seq "$y_min" "$y_max"); do
|
||||||
|
echo "${BASE}/${source}/${z}/${x}/${y}"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Romania bounding box at various zoom levels ──
|
||||||
|
# Lon: 20.2-30.0, Lat: 43.5-48.3
|
||||||
|
# Tile coords computed from slippy map formula
|
||||||
|
|
||||||
|
# z5: UATs coarse (2 tiles)
|
||||||
|
fetch_tiles gis_uats_z5 5 17 18 11 11
|
||||||
|
|
||||||
|
# z7: UATs moderate (12 tiles)
|
||||||
|
fetch_tiles gis_uats_z8 7 69 73 44 46
|
||||||
|
|
||||||
|
# z8: UATs + labels (40 tiles)
|
||||||
|
fetch_tiles gis_uats_z8 8 139 147 88 92
|
||||||
|
|
||||||
|
# z9: UATs labels (100 tiles — major cities area)
|
||||||
|
fetch_tiles gis_uats_z8 9 279 288 177 185
|
||||||
|
|
||||||
|
# z10: Administrativ + terenuri sources start loading
|
||||||
|
# Focus on major metro areas: Bucharest, Cluj, Timisoara, Iasi, Brasov
|
||||||
|
# Bucharest area (z12)
|
||||||
|
fetch_tiles gis_terenuri 12 2300 2310 1490 1498
|
||||||
|
fetch_tiles gis_cladiri 12 2300 2310 1490 1498
|
||||||
|
# Cluj area (z12)
|
||||||
|
fetch_tiles gis_terenuri 12 2264 2270 1460 1465
|
||||||
|
fetch_tiles gis_cladiri 12 2264 2270 1460 1465
|
||||||
|
|
||||||
|
echo "[$(date -Iseconds)] Fetching tiles ($PARALLEL concurrent)..."
|
||||||
|
|
||||||
|
# Pipe all URLs through xargs+curl for parallel fetching
|
||||||
|
fetch_tiles gis_uats_z5 5 17 18 11 11
|
||||||
|
fetch_tiles gis_uats_z8 7 69 73 44 46
|
||||||
|
fetch_tiles gis_uats_z8 8 139 147 88 92
|
||||||
|
fetch_tiles gis_uats_z8 9 279 288 177 185
|
||||||
|
fetch_tiles gis_terenuri 12 2300 2310 1490 1498
|
||||||
|
fetch_tiles gis_cladiri 12 2300 2310 1490 1498
|
||||||
|
fetch_tiles gis_terenuri 12 2264 2270 1460 1465
|
||||||
|
fetch_tiles gis_cladiri 12 2264 2270 1460 1465
|
||||||
|
|
||||||
|
# Actually execute all fetches
|
||||||
|
{
|
||||||
|
fetch_tiles gis_uats_z5 5 17 18 11 11
|
||||||
|
fetch_tiles gis_uats_z8 7 69 73 44 46
|
||||||
|
fetch_tiles gis_uats_z8 8 139 147 88 92
|
||||||
|
fetch_tiles gis_uats_z8 9 279 288 177 185
|
||||||
|
fetch_tiles gis_terenuri 12 2300 2310 1490 1498
|
||||||
|
fetch_tiles gis_cladiri 12 2300 2310 1490 1498
|
||||||
|
fetch_tiles gis_terenuri 12 2264 2270 1460 1465
|
||||||
|
fetch_tiles gis_cladiri 12 2264 2270 1460 1465
|
||||||
|
} | xargs -P "$PARALLEL" -I {} curl -sf -o /dev/null {} 2>/dev/null
|
||||||
|
|
||||||
|
echo "[$(date -Iseconds)] Cache warming complete."
|
||||||
@@ -328,8 +328,8 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
|||||||
LAYER_IDS.uatsZ12Fill, LAYER_IDS.uatsZ12Line, LAYER_IDS.uatsZ12Label,
|
LAYER_IDS.uatsZ12Fill, LAYER_IDS.uatsZ12Line, LAYER_IDS.uatsZ12Label,
|
||||||
],
|
],
|
||||||
administrativ: [LAYER_IDS.adminLineOuter, LAYER_IDS.adminLineInner],
|
administrativ: [LAYER_IDS.adminLineOuter, LAYER_IDS.adminLineInner],
|
||||||
terenuri: [LAYER_IDS.terenuriFill, LAYER_IDS.terenuriLine, LAYER_IDS.terenuriLabel],
|
terenuri: [LAYER_IDS.terenuriFill, LAYER_IDS.terenuriLine, LAYER_IDS.terenuriLabel, "l-terenuri-pm-fill", "l-terenuri-pm-line"],
|
||||||
cladiri: [LAYER_IDS.cladiriFill, LAYER_IDS.cladiriLine, LAYER_IDS.cladiriLabel],
|
cladiri: [LAYER_IDS.cladiriFill, LAYER_IDS.cladiriLine, LAYER_IDS.cladiriLabel, "l-cladiri-pm-fill", "l-cladiri-pm-line"],
|
||||||
};
|
};
|
||||||
for (const [group, layerIds] of Object.entries(mapping)) {
|
for (const [group, layerIds] of Object.entries(mapping)) {
|
||||||
const visible = vis[group] !== false;
|
const visible = vis[group] !== false;
|
||||||
@@ -465,13 +465,23 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
|||||||
paint: { "line-color": "#f97316", "line-width": 1.5 } });
|
paint: { "line-color": "#f97316", "line-width": 1.5 } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Terenuri (parcels) — always from Martin (live data) ===
|
// === Terenuri (parcels) ===
|
||||||
map.addSource(SOURCES.terenuri, { type: "vector", tiles: [`${m}/${SOURCES.terenuri}/{z}/{x}/{y}`], minzoom: 10, maxzoom: 18 });
|
if (usePmtiles) {
|
||||||
map.addLayer({ id: LAYER_IDS.terenuriFill, type: "fill", source: SOURCES.terenuri, "source-layer": SOURCES.terenuri, minzoom: 13,
|
// PMTiles overview at z13 (fast, pre-generated), Martin for z14+ (live detail)
|
||||||
|
map.addLayer({ id: "l-terenuri-pm-fill", type: "fill", source: "overview-pmtiles", "source-layer": SOURCES.terenuri, minzoom: 13, maxzoom: 14,
|
||||||
|
paint: { "fill-color": "#22c55e", "fill-opacity": 0.15 } });
|
||||||
|
map.addLayer({ id: "l-terenuri-pm-line", type: "line", source: "overview-pmtiles", "source-layer": SOURCES.terenuri, minzoom: 13, maxzoom: 14,
|
||||||
|
paint: { "line-color": "#15803d", "line-width": 0.8 } });
|
||||||
|
// Martin source starts at z14 (saves generating heavy z10-z13 tiles)
|
||||||
|
map.addSource(SOURCES.terenuri, { type: "vector", tiles: [`${m}/${SOURCES.terenuri}/{z}/{x}/{y}`], minzoom: 14, maxzoom: 18 });
|
||||||
|
} else {
|
||||||
|
map.addSource(SOURCES.terenuri, { type: "vector", tiles: [`${m}/${SOURCES.terenuri}/{z}/{x}/{y}`], minzoom: 10, maxzoom: 18 });
|
||||||
|
}
|
||||||
|
map.addLayer({ id: LAYER_IDS.terenuriFill, type: "fill", source: SOURCES.terenuri, "source-layer": SOURCES.terenuri, minzoom: 14,
|
||||||
paint: { "fill-color": "#22c55e", "fill-opacity": 0.15 } });
|
paint: { "fill-color": "#22c55e", "fill-opacity": 0.15 } });
|
||||||
map.addLayer({ id: LAYER_IDS.terenuriLine, type: "line", source: SOURCES.terenuri, "source-layer": SOURCES.terenuri, minzoom: 13,
|
map.addLayer({ id: LAYER_IDS.terenuriLine, type: "line", source: SOURCES.terenuri, "source-layer": SOURCES.terenuri, minzoom: 14,
|
||||||
paint: { "line-color": "#15803d", "line-width": 0.8 } });
|
paint: { "line-color": "#15803d", "line-width": 0.8 } });
|
||||||
// Parcel cadastral number label
|
// Parcel cadastral number label (always from Martin — needs live data)
|
||||||
map.addLayer({ id: LAYER_IDS.terenuriLabel, type: "symbol", source: SOURCES.terenuri, "source-layer": SOURCES.terenuri, minzoom: 16,
|
map.addLayer({ id: LAYER_IDS.terenuriLabel, type: "symbol", source: SOURCES.terenuri, "source-layer": SOURCES.terenuri, minzoom: 16,
|
||||||
layout: {
|
layout: {
|
||||||
"text-field": ["coalesce", ["get", "cadastral_ref"], ""],
|
"text-field": ["coalesce", ["get", "cadastral_ref"], ""],
|
||||||
@@ -481,11 +491,20 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
|||||||
},
|
},
|
||||||
paint: { "text-color": "#166534", "text-halo-color": "#fff", "text-halo-width": 1 } });
|
paint: { "text-color": "#166534", "text-halo-color": "#fff", "text-halo-width": 1 } });
|
||||||
|
|
||||||
// === Cladiri (buildings) — no simplification ===
|
// === Cladiri (buildings) ===
|
||||||
map.addSource(SOURCES.cladiri, { type: "vector", tiles: [`${m}/${SOURCES.cladiri}/{z}/{x}/{y}`], minzoom: 12, maxzoom: 18 });
|
if (usePmtiles) {
|
||||||
map.addLayer({ id: LAYER_IDS.cladiriFill, type: "fill", source: SOURCES.cladiri, "source-layer": SOURCES.cladiri, minzoom: 14,
|
// PMTiles overview at z14 (pre-generated), Martin for z15+ (live detail)
|
||||||
|
map.addLayer({ id: "l-cladiri-pm-fill", type: "fill", source: "overview-pmtiles", "source-layer": SOURCES.cladiri, minzoom: 14, maxzoom: 15,
|
||||||
|
paint: { "fill-color": "#3b82f6", "fill-opacity": 0.5 } });
|
||||||
|
map.addLayer({ id: "l-cladiri-pm-line", type: "line", source: "overview-pmtiles", "source-layer": SOURCES.cladiri, minzoom: 14, maxzoom: 15,
|
||||||
|
paint: { "line-color": "#1e3a5f", "line-width": 0.6 } });
|
||||||
|
map.addSource(SOURCES.cladiri, { type: "vector", tiles: [`${m}/${SOURCES.cladiri}/{z}/{x}/{y}`], minzoom: 15, maxzoom: 18 });
|
||||||
|
} else {
|
||||||
|
map.addSource(SOURCES.cladiri, { type: "vector", tiles: [`${m}/${SOURCES.cladiri}/{z}/{x}/{y}`], minzoom: 12, maxzoom: 18 });
|
||||||
|
}
|
||||||
|
map.addLayer({ id: LAYER_IDS.cladiriFill, type: "fill", source: SOURCES.cladiri, "source-layer": SOURCES.cladiri, minzoom: usePmtiles ? 15 : 14,
|
||||||
paint: { "fill-color": "#3b82f6", "fill-opacity": 0.5 } });
|
paint: { "fill-color": "#3b82f6", "fill-opacity": 0.5 } });
|
||||||
map.addLayer({ id: LAYER_IDS.cladiriLine, type: "line", source: SOURCES.cladiri, "source-layer": SOURCES.cladiri, minzoom: 14,
|
map.addLayer({ id: LAYER_IDS.cladiriLine, type: "line", source: SOURCES.cladiri, "source-layer": SOURCES.cladiri, minzoom: usePmtiles ? 15 : 14,
|
||||||
paint: { "line-color": "#1e3a5f", "line-width": 0.6 } });
|
paint: { "line-color": "#1e3a5f", "line-width": 0.6 } });
|
||||||
// Building body labels — extract suffix after last dash (e.g. "291479-C1" → "C1")
|
// Building body labels — extract suffix after last dash (e.g. "291479-C1" → "C1")
|
||||||
map.addLayer({ id: LAYER_IDS.cladiriLabel, type: "symbol", source: SOURCES.cladiri, "source-layer": SOURCES.cladiri, minzoom: 16,
|
map.addLayer({ id: LAYER_IDS.cladiriLabel, type: "symbol", source: SOURCES.cladiri, "source-layer": SOURCES.cladiri, minzoom: 16,
|
||||||
|
|||||||
Reference in New Issue
Block a user