fix(geoportal): proxy PMTiles through HTTPS + fix click/selection + optimize rebuild
PMTiles was loaded via HTTP from MinIO (10.10.10.166:9002) on an HTTPS page, causing browser mixed-content blocking — parcels invisible on geoportal. Fixes: - tile-cache nginx proxies /pmtiles/ → MinIO with Range header support - PMTILES_URL changed to relative path (resolves to HTTPS automatically) - clickableLayers includes PMTiles fill layers (click on parcels works) - Selection highlight uses PMTiles source at z13+ (was Martin z17+ only) - tippecanoe per-layer zoom ranges (terenuri z13-z18, cladiri z14-z18) skips processing millions of features at z0-z12 — faster rebuild Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -7,7 +7,7 @@ services:
|
|||||||
- NEXT_PUBLIC_APP_NAME=${NEXT_PUBLIC_APP_NAME:-ArchiTools}
|
- NEXT_PUBLIC_APP_NAME=${NEXT_PUBLIC_APP_NAME:-ArchiTools}
|
||||||
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-https://tools.beletage.ro}
|
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-https://tools.beletage.ro}
|
||||||
- NEXT_PUBLIC_MARTIN_URL=https://tools.beletage.ro/tiles
|
- NEXT_PUBLIC_MARTIN_URL=https://tools.beletage.ro/tiles
|
||||||
- NEXT_PUBLIC_PMTILES_URL=http://10.10.10.166:9002/tiles/overview.pmtiles
|
- NEXT_PUBLIC_PMTILES_URL=/tiles/pmtiles/overview.pmtiles
|
||||||
container_name: architools
|
container_name: architools
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
@@ -58,8 +58,8 @@ services:
|
|||||||
- ILOVEPDF_PUBLIC_KEY=${ILOVEPDF_PUBLIC_KEY:-}
|
- ILOVEPDF_PUBLIC_KEY=${ILOVEPDF_PUBLIC_KEY:-}
|
||||||
# Martin vector tile server (geoportal)
|
# Martin vector tile server (geoportal)
|
||||||
- NEXT_PUBLIC_MARTIN_URL=https://tools.beletage.ro/tiles
|
- NEXT_PUBLIC_MARTIN_URL=https://tools.beletage.ro/tiles
|
||||||
# PMTiles overview tiles from MinIO (empty = use Martin for all layers)
|
# PMTiles overview tiles — proxied through tile-cache nginx (HTTPS, no mixed-content)
|
||||||
- NEXT_PUBLIC_PMTILES_URL=http://10.10.10.166:9002/tiles/overview.pmtiles
|
- NEXT_PUBLIC_PMTILES_URL=/tiles/pmtiles/overview.pmtiles
|
||||||
# DWG-to-DXF sidecar
|
# DWG-to-DXF sidecar
|
||||||
- DWG2DXF_URL=http://dwg2dxf:5001
|
- DWG2DXF_URL=http://dwg2dxf:5001
|
||||||
# Email notifications (Brevo SMTP)
|
# Email notifications (Brevo SMTP)
|
||||||
|
|||||||
@@ -37,6 +37,38 @@ server {
|
|||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# PMTiles from MinIO — HTTPS proxy for browser access (avoids mixed-content block)
|
||||||
|
# Browser fetches: /pmtiles/overview.pmtiles → MinIO http://10.10.10.166:9002/tiles/overview.pmtiles
|
||||||
|
location /pmtiles/ {
|
||||||
|
proxy_pass http://10.10.10.166:9002/tiles/;
|
||||||
|
proxy_set_header Host 10.10.10.166:9002;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
|
# Range requests — essential for PMTiles (byte-range tile lookups)
|
||||||
|
proxy_set_header Range $http_range;
|
||||||
|
proxy_set_header If-Range $http_if_range;
|
||||||
|
proxy_pass_request_headers on;
|
||||||
|
|
||||||
|
# Browser cache — file changes only on rebuild (~weekly)
|
||||||
|
add_header Cache-Control "public, max-age=3600, stale-while-revalidate=86400" always;
|
||||||
|
|
||||||
|
# CORS — PMTiles loaded from tools.beletage.ro page
|
||||||
|
add_header Access-Control-Allow-Origin "*" always;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
|
||||||
|
add_header Access-Control-Allow-Headers "Range, If-None-Match, If-Range, Accept-Encoding" always;
|
||||||
|
add_header Access-Control-Expose-Headers "Content-Range, Content-Length, ETag, Accept-Ranges" always;
|
||||||
|
|
||||||
|
# Preflight
|
||||||
|
if ($request_method = OPTIONS) {
|
||||||
|
add_header Access-Control-Allow-Origin "*";
|
||||||
|
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS";
|
||||||
|
add_header Access-Control-Allow-Headers "Range, If-Range";
|
||||||
|
add_header Access-Control-Max-Age 86400;
|
||||||
|
add_header Content-Length 0;
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Tile requests — cache aggressively
|
# Tile requests — cache aggressively
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://martin:3000;
|
proxy_pass http://martin:3000;
|
||||||
|
|||||||
@@ -73,21 +73,23 @@ echo "[$(date -Iseconds)] Export complete."
|
|||||||
# ── Step 2: Generate PMTiles with tippecanoe ──
|
# ── Step 2: Generate PMTiles with tippecanoe ──
|
||||||
echo "[$(date -Iseconds)] Generating PMTiles..."
|
echo "[$(date -Iseconds)] Generating PMTiles..."
|
||||||
|
|
||||||
|
# Per-layer zoom ranges — avoids processing features at zoom levels where they won't appear
|
||||||
|
# UAT boundaries: only at their respective zoom bands (saves processing z13-z18 for simple polygons)
|
||||||
|
# Terenuri/Cladiri: only z13+/z14+ (the expensive layers skip z0-z12 entirely)
|
||||||
tippecanoe \
|
tippecanoe \
|
||||||
-o "$OUTPUT_FILE" \
|
-o "$OUTPUT_FILE" \
|
||||||
--named-layer=gis_uats_z0:uats_z0.fgb \
|
-L'{"layer":"gis_uats_z0","file":"uats_z0.fgb","minzoom":0,"maxzoom":5}' \
|
||||||
--named-layer=gis_uats_z5:uats_z5.fgb \
|
-L'{"layer":"gis_uats_z5","file":"uats_z5.fgb","minzoom":5,"maxzoom":8}' \
|
||||||
--named-layer=gis_uats_z8:uats_z8.fgb \
|
-L'{"layer":"gis_uats_z8","file":"uats_z8.fgb","minzoom":8,"maxzoom":12}' \
|
||||||
--named-layer=gis_uats_z12:uats_z12.fgb \
|
-L'{"layer":"gis_uats_z12","file":"uats_z12.fgb","minzoom":12,"maxzoom":14}' \
|
||||||
--named-layer=gis_administrativ:administrativ.fgb \
|
-L'{"layer":"gis_administrativ","file":"administrativ.fgb","minzoom":10,"maxzoom":16}' \
|
||||||
--named-layer=gis_terenuri:terenuri_overview.fgb \
|
-L'{"layer":"gis_terenuri","file":"terenuri_overview.fgb","minzoom":13,"maxzoom":18}' \
|
||||||
--named-layer=gis_cladiri:cladiri_overview.fgb \
|
-L'{"layer":"gis_cladiri","file":"cladiri_overview.fgb","minzoom":14,"maxzoom":18}' \
|
||||||
--minimum-zoom=0 \
|
|
||||||
--maximum-zoom=18 \
|
|
||||||
--base-zoom=18 \
|
--base-zoom=18 \
|
||||||
--drop-densest-as-needed \
|
--drop-densest-as-needed \
|
||||||
--detect-shared-borders \
|
--detect-shared-borders \
|
||||||
--simplification=10 \
|
--simplification=10 \
|
||||||
|
--no-tile-stats \
|
||||||
--hilbert \
|
--hilbert \
|
||||||
--force
|
--force
|
||||||
|
|
||||||
|
|||||||
@@ -480,7 +480,8 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
|||||||
"text-max-width": 8,
|
"text-max-width": 8,
|
||||||
},
|
},
|
||||||
paint: { "text-color": "#166534", "text-halo-color": "#fff", "text-halo-width": 1 } });
|
paint: { "text-color": "#166534", "text-halo-color": "#fff", "text-halo-width": 1 } });
|
||||||
// Martin source kept for selection highlight (no minzoom layer means no tile requests)
|
// Martin source registered but unused (selection uses PMTiles source now)
|
||||||
|
// Kept as fallback reference — no tile requests since no layers target it
|
||||||
map.addSource(SOURCES.terenuri, { type: "vector", tiles: [`${m}/${SOURCES.terenuri}/{z}/{x}/{y}`], minzoom: 17, maxzoom: 18 });
|
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 });
|
||||||
@@ -539,10 +540,12 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
|||||||
paint: { "text-color": "#1e3a5f", "text-halo-color": "#fff", "text-halo-width": 1 } });
|
paint: { "text-color": "#1e3a5f", "text-halo-color": "#fff", "text-halo-width": 1 } });
|
||||||
|
|
||||||
// === Selection highlight ===
|
// === Selection highlight ===
|
||||||
map.addLayer({ id: LAYER_IDS.selectionFill, type: "fill", source: SOURCES.terenuri, "source-layer": SOURCES.terenuri, minzoom: 13,
|
// Use PMTiles source when available (has data at z13+), Martin only has z17+
|
||||||
|
const selectionSrc = usePmtiles ? "overview-pmtiles" : SOURCES.terenuri;
|
||||||
|
map.addLayer({ id: LAYER_IDS.selectionFill, type: "fill", source: selectionSrc, "source-layer": SOURCES.terenuri, minzoom: 13,
|
||||||
filter: ["==", "object_id", "__NONE__"],
|
filter: ["==", "object_id", "__NONE__"],
|
||||||
paint: { "fill-color": "#f59e0b", "fill-opacity": 0.5 } });
|
paint: { "fill-color": "#f59e0b", "fill-opacity": 0.5 } });
|
||||||
map.addLayer({ id: LAYER_IDS.selectionLine, type: "line", source: SOURCES.terenuri, "source-layer": SOURCES.terenuri, minzoom: 13,
|
map.addLayer({ id: LAYER_IDS.selectionLine, type: "line", source: selectionSrc, "source-layer": SOURCES.terenuri, minzoom: 13,
|
||||||
filter: ["==", "object_id", "__NONE__"],
|
filter: ["==", "object_id", "__NONE__"],
|
||||||
paint: { "line-color": "#d97706", "line-width": 2.5 } });
|
paint: { "line-color": "#d97706", "line-width": 2.5 } });
|
||||||
|
|
||||||
@@ -571,8 +574,10 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* ---- Click handler — NO popup, only callback ---- */
|
/* ---- Click handler — NO popup, only callback ---- */
|
||||||
|
// Include both Martin and PMTiles fill layers — filter() skips non-existent ones
|
||||||
const clickableLayers = [
|
const clickableLayers = [
|
||||||
LAYER_IDS.terenuriFill, LAYER_IDS.cladiriFill,
|
LAYER_IDS.terenuriFill, LAYER_IDS.cladiriFill,
|
||||||
|
"l-terenuri-pm-fill", "l-cladiri-pm-fill",
|
||||||
];
|
];
|
||||||
|
|
||||||
map.on("click", (e) => {
|
map.on("click", (e) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user