perf(geoportal): extend PMTiles to z16 — near-zero PostGIS load for tile serving

- PMTiles now covers z0-z16 (was z0-z14) for terenuri + cladiri
- Martin only serves z17-z18 (very close zoom, few features per tile)
- map-viewer: PMTiles layers serve z13-z16 for terenuri, z14-z16 for cladiri
- Labels at z16 now from PMTiles (cadastral_ref included in tiles)
- Remove failed compound index from postgis-setup.sql

This eliminates PostgreSQL as bottleneck for 99% of tile requests.
PMTiles file will be ~300-500MB (vs 104MB at z14).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-28 15:40:03 +02:00
parent b33fe35c4b
commit f5c8cf5fdc
4 changed files with 27 additions and 24 deletions
+2 -2
View File
@@ -84,7 +84,7 @@ postgres:
geometry_column: geom geometry_column: geom
srid: 3844 srid: 3844
bounds: [20.2, 43.5, 30.0, 48.3] bounds: [20.2, 43.5, 30.0, 48.3]
minzoom: 14 minzoom: 17
maxzoom: 18 maxzoom: 18
properties: properties:
object_id: text object_id: text
@@ -139,7 +139,7 @@ postgres:
geometry_column: geom geometry_column: geom
srid: 3844 srid: 3844
bounds: [20.2, 43.5, 30.0, 48.3] bounds: [20.2, 43.5, 30.0, 48.3]
minzoom: 15 minzoom: 17
maxzoom: 18 maxzoom: 18
properties: properties:
object_id: text object_id: text
-4
View File
@@ -59,10 +59,6 @@ WHERE geometry IS NOT NULL AND geom IS NULL;
CREATE INDEX IF NOT EXISTS gis_feature_geom_idx CREATE INDEX IF NOT EXISTS gis_feature_geom_idx
ON "GisFeature" USING GIST (geom); ON "GisFeature" USING GIST (geom);
-- Compound index: layerId + spatial — Martin queries filter by layer via views
CREATE INDEX IF NOT EXISTS gis_feature_layer_geom_idx
ON "GisFeature" ("layerId", geom) WHERE geom IS NOT NULL;
-- B-tree index on layerId for view filtering (LIKE 'TERENURI%', 'CLADIRI%') -- B-tree index on layerId for view filtering (LIKE 'TERENURI%', 'CLADIRI%')
CREATE INDEX IF NOT EXISTS gis_feature_layer_id_idx CREATE INDEX IF NOT EXISTS gis_feature_layer_id_idx
ON "GisFeature" ("layerId"); ON "GisFeature" ("layerId");
+2 -2
View File
@@ -83,8 +83,8 @@ tippecanoe \
--named-layer=gis_terenuri:terenuri_overview.fgb \ --named-layer=gis_terenuri:terenuri_overview.fgb \
--named-layer=gis_cladiri:cladiri_overview.fgb \ --named-layer=gis_cladiri:cladiri_overview.fgb \
--minimum-zoom=0 \ --minimum-zoom=0 \
--maximum-zoom=14 \ --maximum-zoom=16 \
--base-zoom=14 \ --base-zoom=16 \
--drop-densest-as-needed \ --drop-densest-as-needed \
--detect-shared-borders \ --detect-shared-borders \
--simplification=10 \ --simplification=10 \
+23 -16
View File
@@ -328,7 +328,7 @@ 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, "l-terenuri-pm-fill", "l-terenuri-pm-line"], terenuri: [LAYER_IDS.terenuriFill, LAYER_IDS.terenuriLine, LAYER_IDS.terenuriLabel, "l-terenuri-pm-fill", "l-terenuri-pm-line", "l-terenuri-pm-label"],
cladiri: [LAYER_IDS.cladiriFill, LAYER_IDS.cladiriLine, LAYER_IDS.cladiriLabel, "l-cladiri-pm-fill", "l-cladiri-pm-line"], 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)) {
@@ -467,22 +467,29 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
// === Terenuri (parcels) === // === Terenuri (parcels) ===
if (usePmtiles) { if (usePmtiles) {
// PMTiles overview at z13 (fast, pre-generated), Martin for z14+ (live detail) // PMTiles serves z13-z16 (pre-generated, instant), Martin only z17-z18 (close detail)
map.addLayer({ id: "l-terenuri-pm-fill", type: "fill", source: "overview-pmtiles", "source-layer": SOURCES.terenuri, minzoom: 13, maxzoom: 14, map.addLayer({ id: "l-terenuri-pm-fill", type: "fill", source: "overview-pmtiles", "source-layer": SOURCES.terenuri, minzoom: 13, maxzoom: 17,
paint: { "fill-color": "#22c55e", "fill-opacity": 0.15 } }); 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, map.addLayer({ id: "l-terenuri-pm-line", type: "line", source: "overview-pmtiles", "source-layer": SOURCES.terenuri, minzoom: 13, maxzoom: 17,
paint: { "line-color": "#15803d", "line-width": 0.8 } }); paint: { "line-color": "#15803d", "line-width": 0.8 } });
// Martin source starts at z14 (saves generating heavy z10-z13 tiles) // PMTiles labels z16 (cadastral_ref available in PMTiles)
map.addSource(SOURCES.terenuri, { type: "vector", tiles: [`${m}/${SOURCES.terenuri}/{z}/{x}/{y}`], minzoom: 14, maxzoom: 18 }); map.addLayer({ id: "l-terenuri-pm-label", type: "symbol", source: "overview-pmtiles", "source-layer": SOURCES.terenuri, minzoom: 16, maxzoom: 17,
layout: {
"text-field": ["coalesce", ["get", "cadastral_ref"], ""],
"text-font": ["Noto Sans Regular"],
"text-size": 10, "text-anchor": "center", "text-allow-overlap": false,
"text-max-width": 8,
},
paint: { "text-color": "#166534", "text-halo-color": "#fff", "text-halo-width": 1 } });
map.addSource(SOURCES.terenuri, { type: "vector", tiles: [`${m}/${SOURCES.terenuri}/{z}/{x}/{y}`], minzoom: 17, maxzoom: 18 });
} else { } else {
map.addSource(SOURCES.terenuri, { type: "vector", tiles: [`${m}/${SOURCES.terenuri}/{z}/{x}/{y}`], minzoom: 10, maxzoom: 18 }); 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, map.addLayer({ id: LAYER_IDS.terenuriFill, type: "fill", source: SOURCES.terenuri, "source-layer": SOURCES.terenuri, minzoom: usePmtiles ? 17 : 13,
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: 14, map.addLayer({ id: LAYER_IDS.terenuriLine, type: "line", source: SOURCES.terenuri, "source-layer": SOURCES.terenuri, minzoom: usePmtiles ? 17 : 13,
paint: { "line-color": "#15803d", "line-width": 0.8 } }); paint: { "line-color": "#15803d", "line-width": 0.8 } });
// 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: usePmtiles ? 17 : 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"], ""],
"text-font": ["Noto Sans Regular"], "text-font": ["Noto Sans Regular"],
@@ -493,18 +500,18 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
// === Cladiri (buildings) === // === Cladiri (buildings) ===
if (usePmtiles) { if (usePmtiles) {
// PMTiles overview at z14 (pre-generated), Martin for z15+ (live detail) // PMTiles serves z14-z16, Martin only z17-z18
map.addLayer({ id: "l-cladiri-pm-fill", type: "fill", source: "overview-pmtiles", "source-layer": SOURCES.cladiri, minzoom: 14, maxzoom: 15, map.addLayer({ id: "l-cladiri-pm-fill", type: "fill", source: "overview-pmtiles", "source-layer": SOURCES.cladiri, minzoom: 14, maxzoom: 17,
paint: { "fill-color": "#3b82f6", "fill-opacity": 0.5 } }); 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, map.addLayer({ id: "l-cladiri-pm-line", type: "line", source: "overview-pmtiles", "source-layer": SOURCES.cladiri, minzoom: 14, maxzoom: 17,
paint: { "line-color": "#1e3a5f", "line-width": 0.6 } }); 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 }); map.addSource(SOURCES.cladiri, { type: "vector", tiles: [`${m}/${SOURCES.cladiri}/{z}/{x}/{y}`], minzoom: 17, maxzoom: 18 });
} else { } else {
map.addSource(SOURCES.cladiri, { type: "vector", tiles: [`${m}/${SOURCES.cladiri}/{z}/{x}/{y}`], minzoom: 12, maxzoom: 18 }); 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, map.addLayer({ id: LAYER_IDS.cladiriFill, type: "fill", source: SOURCES.cladiri, "source-layer": SOURCES.cladiri, minzoom: usePmtiles ? 17 : 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: usePmtiles ? 15 : 14, map.addLayer({ id: LAYER_IDS.cladiriLine, type: "line", source: SOURCES.cladiri, "source-layer": SOURCES.cladiri, minzoom: usePmtiles ? 17 : 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,