perf(geoportal): 4-level UAT simplification + intravilan layer + preserve view on basemap switch
UAT zoom-dependent views (read-only, original geom NEVER modified): - gis_uats_z0 (z0-5): 2000m simplification — country outlines - gis_uats_z5 (z5-8): 500m — regional overview - gis_uats_z8 (z8-12): 50m — county/city level with labels - gis_uats_z12 (z12+): 10m — near-original precision New layers: - gis_administrativ (intravilan, arii speciale) — orange dashed, no simplification - Toggle in layer panel (off by default) Basemap switching: - Now preserves current center + zoom when switching between basemaps Parcels + buildings: NO simplification (exact geometry needed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+56
-8
@@ -1,36 +1,82 @@
|
||||
# Martin v0.15 configuration — optimized tile sources for ArchiTools Geoportal
|
||||
# All geometries are EPSG:3844 (Stereo70). Bounds are approximate Romania extent.
|
||||
# Original table data is NEVER modified — views compute simplification on-the-fly.
|
||||
|
||||
# Disable auto-discovery — only serve explicitly configured sources
|
||||
postgres:
|
||||
connection_string: ${DATABASE_URL}
|
||||
default_srid: 3844
|
||||
auto_publish: false
|
||||
tables:
|
||||
gis_uats:
|
||||
# ── UAT boundaries: 4 zoom-dependent simplification levels ──
|
||||
|
||||
gis_uats_z0:
|
||||
schema: public
|
||||
table: gis_uats
|
||||
table: gis_uats_z0
|
||||
geometry_column: geom
|
||||
srid: 3844
|
||||
bounds: [20.2, 43.5, 30.0, 48.3]
|
||||
minzoom: 0
|
||||
maxzoom: 14
|
||||
maxzoom: 5
|
||||
properties:
|
||||
name: text
|
||||
siruta: text
|
||||
|
||||
gis_uats_simple:
|
||||
gis_uats_z5:
|
||||
schema: public
|
||||
table: gis_uats_simple
|
||||
table: gis_uats_z5
|
||||
geometry_column: geom
|
||||
srid: 3844
|
||||
bounds: [20.2, 43.5, 30.0, 48.3]
|
||||
minzoom: 0
|
||||
maxzoom: 9
|
||||
minzoom: 5
|
||||
maxzoom: 8
|
||||
properties:
|
||||
name: text
|
||||
siruta: text
|
||||
|
||||
gis_uats_z8:
|
||||
schema: public
|
||||
table: gis_uats_z8
|
||||
geometry_column: geom
|
||||
srid: 3844
|
||||
bounds: [20.2, 43.5, 30.0, 48.3]
|
||||
minzoom: 8
|
||||
maxzoom: 12
|
||||
properties:
|
||||
name: text
|
||||
siruta: text
|
||||
county: text
|
||||
|
||||
gis_uats_z12:
|
||||
schema: public
|
||||
table: gis_uats_z12
|
||||
geometry_column: geom
|
||||
srid: 3844
|
||||
bounds: [20.2, 43.5, 30.0, 48.3]
|
||||
minzoom: 12
|
||||
maxzoom: 16
|
||||
properties:
|
||||
name: text
|
||||
siruta: text
|
||||
county: text
|
||||
|
||||
# ── Administrativ (intravilan, arii speciale) — NO simplification ──
|
||||
|
||||
gis_administrativ:
|
||||
schema: public
|
||||
table: gis_administrativ
|
||||
geometry_column: geom
|
||||
srid: 3844
|
||||
bounds: [20.2, 43.5, 30.0, 48.3]
|
||||
minzoom: 10
|
||||
maxzoom: 16
|
||||
properties:
|
||||
object_id: text
|
||||
siruta: text
|
||||
layer_id: text
|
||||
cadastral_ref: text
|
||||
|
||||
# ── Terenuri (parcels) — NO simplification ──
|
||||
|
||||
gis_terenuri:
|
||||
schema: public
|
||||
table: gis_terenuri
|
||||
@@ -46,6 +92,8 @@ postgres:
|
||||
area_value: float8
|
||||
layer_id: text
|
||||
|
||||
# ── Cladiri (buildings) — NO simplification ──
|
||||
|
||||
gis_cladiri:
|
||||
schema: public
|
||||
table: gis_cladiri
|
||||
|
||||
@@ -158,29 +158,49 @@ WHERE geometry IS NOT NULL AND geom IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS gis_uat_geom_idx
|
||||
ON "GisUat" USING GIST (geom);
|
||||
|
||||
-- 8. Martin/QGIS-friendly view (moderate simplification — 50m tolerance)
|
||||
CREATE OR REPLACE VIEW gis_uats AS
|
||||
SELECT
|
||||
siruta,
|
||||
name,
|
||||
county,
|
||||
ST_SimplifyPreserveTopology(geom, 50) AS geom
|
||||
FROM "GisUat"
|
||||
WHERE geom IS NOT NULL;
|
||||
-- =============================================================================
|
||||
-- 8. Zoom-dependent views for Martin vector tiles
|
||||
-- 4 levels of geometry simplification for progressive loading.
|
||||
-- SAFE: these are read-only views — original geom column is NEVER modified.
|
||||
-- =============================================================================
|
||||
|
||||
-- 9. Simplified view for low zoom levels (z5-z9) — 500m tolerance
|
||||
CREATE OR REPLACE VIEW gis_uats_simple AS
|
||||
SELECT
|
||||
siruta,
|
||||
name,
|
||||
-- z0-5: Very coarse overview (2000m tolerance) — country-level outlines
|
||||
CREATE OR REPLACE VIEW gis_uats_z0 AS
|
||||
SELECT siruta, name,
|
||||
ST_SimplifyPreserveTopology(geom, 2000) AS geom
|
||||
FROM "GisUat" WHERE geom IS NOT NULL;
|
||||
|
||||
-- z5-8: Coarse (500m tolerance) — regional overview
|
||||
CREATE OR REPLACE VIEW gis_uats_z5 AS
|
||||
SELECT siruta, name,
|
||||
ST_SimplifyPreserveTopology(geom, 500) AS geom
|
||||
FROM "GisUat"
|
||||
WHERE geom IS NOT NULL;
|
||||
FROM "GisUat" WHERE geom IS NOT NULL;
|
||||
|
||||
-- z8-12: Moderate (50m tolerance) — county/city level
|
||||
CREATE OR REPLACE VIEW gis_uats_z8 AS
|
||||
SELECT siruta, name, county,
|
||||
ST_SimplifyPreserveTopology(geom, 50) AS geom
|
||||
FROM "GisUat" WHERE geom IS NOT NULL;
|
||||
|
||||
-- z12+: Fine (10m tolerance) — near-original precision
|
||||
CREATE OR REPLACE VIEW gis_uats_z12 AS
|
||||
SELECT siruta, name, county,
|
||||
ST_SimplifyPreserveTopology(geom, 10) AS geom
|
||||
FROM "GisUat" WHERE geom IS NOT NULL;
|
||||
|
||||
-- Keep the legacy gis_uats view for QGIS compatibility
|
||||
CREATE OR REPLACE VIEW gis_uats AS
|
||||
SELECT siruta, name, county,
|
||||
ST_SimplifyPreserveTopology(geom, 50) AS geom
|
||||
FROM "GisUat" WHERE geom IS NOT NULL;
|
||||
|
||||
-- =============================================================================
|
||||
-- Done! Martin serves these views as vector tiles:
|
||||
-- - gis_uats (moderate detail, z9+)
|
||||
-- - gis_uats_simple (coarse overview, z5-z9)
|
||||
-- QGIS: PostgreSQL -> 10.10.10.166:5432 / architools_db -> gis_uats view
|
||||
-- - gis_uats_z0 (z0-5, 2000m simplification)
|
||||
-- - gis_uats_z5 (z5-8, 500m)
|
||||
-- - gis_uats_z8 (z8-12, 50m)
|
||||
-- - gis_uats_z12 (z12+, 10m near-original)
|
||||
-- - gis_uats (legacy for QGIS, 50m)
|
||||
-- Original geometry in GisUat.geom is NEVER modified.
|
||||
-- SRID: 3844 (Stereo70)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -42,6 +42,13 @@ const LAYER_GROUPS: LayerGroupDef[] = [
|
||||
color: "#3b82f6",
|
||||
defaultVisible: true,
|
||||
},
|
||||
{
|
||||
id: "administrativ",
|
||||
label: "Intravilan",
|
||||
description: "Limite intravilan, arii speciale (zoom >= 10)",
|
||||
color: "#ea580c",
|
||||
defaultVisible: false,
|
||||
},
|
||||
];
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
@@ -34,19 +34,28 @@ const DEFAULT_ZOOM = 7;
|
||||
|
||||
/** Source/layer IDs used on the map */
|
||||
const SOURCES = {
|
||||
uatsSimple: "gis_uats_simple",
|
||||
uats: "gis_uats",
|
||||
uatsZ0: "gis_uats_z0", // z0-5: 2000m simplification
|
||||
uatsZ5: "gis_uats_z5", // z5-8: 500m
|
||||
uatsZ8: "gis_uats_z8", // z8-12: 50m
|
||||
uatsZ12: "gis_uats_z12", // z12+: 10m (near-original)
|
||||
terenuri: "gis_terenuri",
|
||||
cladiri: "gis_cladiri",
|
||||
administrativ: "gis_administrativ",
|
||||
} as const;
|
||||
|
||||
/** Map layer IDs (prefixed to avoid collisions) */
|
||||
const LAYER_IDS = {
|
||||
uatsSimpleFill: "layer-uats-simple-fill",
|
||||
uatsSimpleLine: "layer-uats-simple-line",
|
||||
uatsFill: "layer-uats-fill",
|
||||
uatsLine: "layer-uats-line",
|
||||
uatsLabel: "layer-uats-label",
|
||||
uatsZ0Line: "layer-uats-z0-line",
|
||||
uatsZ5Fill: "layer-uats-z5-fill",
|
||||
uatsZ5Line: "layer-uats-z5-line",
|
||||
uatsZ8Fill: "layer-uats-z8-fill",
|
||||
uatsZ8Line: "layer-uats-z8-line",
|
||||
uatsZ8Label: "layer-uats-z8-label",
|
||||
uatsZ12Fill: "layer-uats-z12-fill",
|
||||
uatsZ12Line: "layer-uats-z12-line",
|
||||
uatsZ12Label: "layer-uats-z12-label",
|
||||
adminFill: "layer-admin-fill",
|
||||
adminLine: "layer-admin-line",
|
||||
terenuriFill: "layer-terenuri-fill",
|
||||
terenuriLine: "layer-terenuri-line",
|
||||
cladiriFill: "layer-cladiri-fill",
|
||||
@@ -235,7 +244,13 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
||||
if (!map || !map.isStyleLoaded()) return;
|
||||
|
||||
const mapping: Record<string, string[]> = {
|
||||
uats: [LAYER_IDS.uatsSimpleFill, LAYER_IDS.uatsSimpleLine, LAYER_IDS.uatsFill, LAYER_IDS.uatsLine, LAYER_IDS.uatsLabel],
|
||||
uats: [
|
||||
LAYER_IDS.uatsZ0Line,
|
||||
LAYER_IDS.uatsZ5Fill, LAYER_IDS.uatsZ5Line,
|
||||
LAYER_IDS.uatsZ8Fill, LAYER_IDS.uatsZ8Line, LAYER_IDS.uatsZ8Label,
|
||||
LAYER_IDS.uatsZ12Fill, LAYER_IDS.uatsZ12Line, LAYER_IDS.uatsZ12Label,
|
||||
],
|
||||
administrativ: [LAYER_IDS.adminFill, LAYER_IDS.adminLine],
|
||||
terenuri: [LAYER_IDS.terenuriFill, LAYER_IDS.terenuriLine],
|
||||
cladiri: [LAYER_IDS.cladiriFill, LAYER_IDS.cladiriLine],
|
||||
};
|
||||
@@ -263,13 +278,18 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
// Preserve current view when switching basemaps
|
||||
const prevMap = mapRef.current;
|
||||
const currentCenter = prevMap ? prevMap.getCenter().toArray() as [number, number] : (center ?? DEFAULT_CENTER);
|
||||
const currentZoom = prevMap ? prevMap.getZoom() : (zoom ?? DEFAULT_ZOOM);
|
||||
|
||||
const basemapDef = BASEMAPS[basemap];
|
||||
|
||||
const map = new maplibregl.Map({
|
||||
container: containerRef.current,
|
||||
style: buildStyle(basemapDef),
|
||||
center: center ?? DEFAULT_CENTER,
|
||||
zoom: zoom ?? DEFAULT_ZOOM,
|
||||
center: currentCenter,
|
||||
zoom: currentZoom,
|
||||
maxZoom: basemapDef.maxzoom ?? 20,
|
||||
});
|
||||
|
||||
@@ -281,111 +301,118 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
||||
|
||||
/* ---- Add Martin sources + layers on load ---- */
|
||||
map.on("load", () => {
|
||||
// --- UAT boundaries (simplified, low zoom z0-z9) ---
|
||||
map.addSource(SOURCES.uatsSimple, {
|
||||
const m = resolvedMartinUrl;
|
||||
|
||||
// === UAT z0-5: very coarse (2000m) — lines only ===
|
||||
map.addSource(SOURCES.uatsZ0, {
|
||||
type: "vector",
|
||||
tiles: [`${resolvedMartinUrl}/${SOURCES.uatsSimple}/{z}/{x}/{y}`],
|
||||
minzoom: 0,
|
||||
maxzoom: 9,
|
||||
tiles: [`${m}/${SOURCES.uatsZ0}/{z}/{x}/{y}`],
|
||||
minzoom: 0, maxzoom: 5,
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.uatsSimpleFill,
|
||||
type: "fill",
|
||||
source: SOURCES.uatsSimple,
|
||||
"source-layer": SOURCES.uatsSimple,
|
||||
maxzoom: 9,
|
||||
paint: {
|
||||
"fill-color": "#8b5cf6",
|
||||
"fill-opacity": [
|
||||
"interpolate", ["linear"], ["zoom"],
|
||||
5, 0.03,
|
||||
8, 0.05,
|
||||
],
|
||||
},
|
||||
id: LAYER_IDS.uatsZ0Line, type: "line",
|
||||
source: SOURCES.uatsZ0, "source-layer": SOURCES.uatsZ0,
|
||||
maxzoom: 5,
|
||||
paint: { "line-color": "#7c3aed", "line-width": 0.3 },
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.uatsSimpleLine,
|
||||
type: "line",
|
||||
source: SOURCES.uatsSimple,
|
||||
"source-layer": SOURCES.uatsSimple,
|
||||
maxzoom: 9,
|
||||
paint: {
|
||||
"line-color": "#7c3aed",
|
||||
"line-width": [
|
||||
"interpolate", ["linear"], ["zoom"],
|
||||
5, 0.5,
|
||||
8, 1,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// --- UAT boundaries (detailed, high zoom z9+) ---
|
||||
map.addSource(SOURCES.uats, {
|
||||
// === UAT z5-8: coarse (500m) — lines + faint fill ===
|
||||
map.addSource(SOURCES.uatsZ5, {
|
||||
type: "vector",
|
||||
tiles: [`${resolvedMartinUrl}/${SOURCES.uats}/{z}/{x}/{y}`],
|
||||
minzoom: 9,
|
||||
maxzoom: 16,
|
||||
tiles: [`${m}/${SOURCES.uatsZ5}/{z}/{x}/{y}`],
|
||||
minzoom: 5, maxzoom: 8,
|
||||
});
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.uatsZ5Fill, type: "fill",
|
||||
source: SOURCES.uatsZ5, "source-layer": SOURCES.uatsZ5,
|
||||
minzoom: 5, maxzoom: 8,
|
||||
paint: { "fill-color": "#8b5cf6", "fill-opacity": 0.03 },
|
||||
});
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.uatsZ5Line, type: "line",
|
||||
source: SOURCES.uatsZ5, "source-layer": SOURCES.uatsZ5,
|
||||
minzoom: 5, maxzoom: 8,
|
||||
paint: { "line-color": "#7c3aed", "line-width": 0.6 },
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.uatsFill,
|
||||
type: "fill",
|
||||
source: SOURCES.uats,
|
||||
"source-layer": SOURCES.uats,
|
||||
minzoom: 8,
|
||||
paint: {
|
||||
"fill-color": "#8b5cf6",
|
||||
"fill-opacity": [
|
||||
"interpolate", ["linear"], ["zoom"],
|
||||
8, 0.03,
|
||||
12, 0.08,
|
||||
],
|
||||
},
|
||||
// === UAT z8-12: moderate (50m) — lines + fill + labels ===
|
||||
map.addSource(SOURCES.uatsZ8, {
|
||||
type: "vector",
|
||||
tiles: [`${m}/${SOURCES.uatsZ8}/{z}/{x}/{y}`],
|
||||
minzoom: 8, maxzoom: 12,
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.uatsLine,
|
||||
type: "line",
|
||||
source: SOURCES.uats,
|
||||
"source-layer": SOURCES.uats,
|
||||
minzoom: 9,
|
||||
paint: {
|
||||
"line-color": "#7c3aed",
|
||||
"line-width": [
|
||||
"interpolate", ["linear"], ["zoom"],
|
||||
5, 0.5,
|
||||
8, 1,
|
||||
12, 2,
|
||||
],
|
||||
},
|
||||
id: LAYER_IDS.uatsZ8Fill, type: "fill",
|
||||
source: SOURCES.uatsZ8, "source-layer": SOURCES.uatsZ8,
|
||||
minzoom: 8, maxzoom: 12,
|
||||
paint: { "fill-color": "#8b5cf6", "fill-opacity": 0.05 },
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.uatsLabel,
|
||||
type: "symbol",
|
||||
source: SOURCES.uats,
|
||||
"source-layer": SOURCES.uats,
|
||||
minzoom: 9,
|
||||
id: LAYER_IDS.uatsZ8Line, type: "line",
|
||||
source: SOURCES.uatsZ8, "source-layer": SOURCES.uatsZ8,
|
||||
minzoom: 8, maxzoom: 12,
|
||||
paint: { "line-color": "#7c3aed", "line-width": 1 },
|
||||
});
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.uatsZ8Label, type: "symbol",
|
||||
source: SOURCES.uatsZ8, "source-layer": SOURCES.uatsZ8,
|
||||
minzoom: 9, maxzoom: 12,
|
||||
layout: {
|
||||
"text-field": ["coalesce", ["get", "name"], ["get", "uat_name"], ""],
|
||||
"text-size": [
|
||||
"interpolate", ["linear"], ["zoom"],
|
||||
9, 10,
|
||||
14, 14,
|
||||
],
|
||||
"text-anchor": "center",
|
||||
"text-allow-overlap": false,
|
||||
},
|
||||
paint: {
|
||||
"text-color": "#5b21b6",
|
||||
"text-halo-color": "#ffffff",
|
||||
"text-halo-width": 1.5,
|
||||
"text-field": ["coalesce", ["get", "name"], ""],
|
||||
"text-size": 10, "text-anchor": "center", "text-allow-overlap": false,
|
||||
},
|
||||
paint: { "text-color": "#5b21b6", "text-halo-color": "#fff", "text-halo-width": 1.5 },
|
||||
});
|
||||
|
||||
// --- Terenuri (parcels) ---
|
||||
// === UAT z12+: fine (10m) — full detail ===
|
||||
map.addSource(SOURCES.uatsZ12, {
|
||||
type: "vector",
|
||||
tiles: [`${m}/${SOURCES.uatsZ12}/{z}/{x}/{y}`],
|
||||
minzoom: 12, maxzoom: 16,
|
||||
});
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.uatsZ12Fill, type: "fill",
|
||||
source: SOURCES.uatsZ12, "source-layer": SOURCES.uatsZ12,
|
||||
minzoom: 12,
|
||||
paint: { "fill-color": "#8b5cf6", "fill-opacity": 0.08 },
|
||||
});
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.uatsZ12Line, type: "line",
|
||||
source: SOURCES.uatsZ12, "source-layer": SOURCES.uatsZ12,
|
||||
minzoom: 12,
|
||||
paint: { "line-color": "#7c3aed", "line-width": 2 },
|
||||
});
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.uatsZ12Label, type: "symbol",
|
||||
source: SOURCES.uatsZ12, "source-layer": SOURCES.uatsZ12,
|
||||
minzoom: 12,
|
||||
layout: {
|
||||
"text-field": ["coalesce", ["get", "name"], ""],
|
||||
"text-size": 13, "text-anchor": "center", "text-allow-overlap": false,
|
||||
},
|
||||
paint: { "text-color": "#5b21b6", "text-halo-color": "#fff", "text-halo-width": 1.5 },
|
||||
});
|
||||
|
||||
// === Administrativ (intravilan, arii speciale) ===
|
||||
map.addSource(SOURCES.administrativ, {
|
||||
type: "vector",
|
||||
tiles: [`${m}/${SOURCES.administrativ}/{z}/{x}/{y}`],
|
||||
minzoom: 10, maxzoom: 16,
|
||||
});
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.adminFill, type: "fill",
|
||||
source: SOURCES.administrativ, "source-layer": SOURCES.administrativ,
|
||||
minzoom: 11,
|
||||
paint: { "fill-color": "#f97316", "fill-opacity": 0.06 },
|
||||
});
|
||||
map.addLayer({
|
||||
id: LAYER_IDS.adminLine, type: "line",
|
||||
source: SOURCES.administrativ, "source-layer": SOURCES.administrativ,
|
||||
minzoom: 10,
|
||||
paint: { "line-color": "#ea580c", "line-width": 1.2, "line-dasharray": [4, 2] },
|
||||
});
|
||||
|
||||
// --- Terenuri (parcels) — NO simplification ---
|
||||
map.addSource(SOURCES.terenuri, {
|
||||
type: "vector",
|
||||
tiles: [`${resolvedMartinUrl}/${SOURCES.terenuri}/{z}/{x}/{y}`],
|
||||
@@ -489,8 +516,9 @@ export const MapViewer = forwardRef<MapViewerHandle, MapViewerProps>(
|
||||
const clickableLayers = [
|
||||
LAYER_IDS.terenuriFill,
|
||||
LAYER_IDS.cladiriFill,
|
||||
LAYER_IDS.uatsFill,
|
||||
LAYER_IDS.uatsSimpleFill,
|
||||
LAYER_IDS.uatsZ5Fill,
|
||||
LAYER_IDS.uatsZ8Fill,
|
||||
LAYER_IDS.uatsZ12Fill,
|
||||
];
|
||||
|
||||
map.on("click", (e) => {
|
||||
|
||||
Reference in New Issue
Block a user