4f694d4458
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>
207 lines
6.8 KiB
PL/PgSQL
207 lines
6.8 KiB
PL/PgSQL
-- =============================================================================
|
|
-- PostGIS native geometry setup for GisUat (UAT boundaries)
|
|
-- Run once manually: PGPASSWORD='...' psql -h 10.10.10.166 -p 5432 \
|
|
-- -U architools_user -d architools_db -f prisma/gisuat-postgis-setup.sql
|
|
--
|
|
-- Idempotent — safe to re-run.
|
|
--
|
|
-- What this does:
|
|
-- 1. Ensures PostGIS extension
|
|
-- 2. Adds native geometry column (geom) if missing
|
|
-- 3. Creates function to convert Esri ring JSON -> PostGIS geometry
|
|
-- 4. Creates trigger to auto-convert on INSERT/UPDATE
|
|
-- 5. Backfills existing rows
|
|
-- 6. Creates GiST spatial index
|
|
-- 7. Creates Martin/QGIS-friendly view 'gis_uats'
|
|
--
|
|
-- After running both SQL scripts (postgis-setup.sql + this file), Martin
|
|
-- will auto-discover these views (any table/view with a 'geom' geometry column):
|
|
-- - gis_features (master: all GisFeature rows with geometry)
|
|
-- - gis_terenuri (parcels from GisFeature)
|
|
-- - gis_cladiri (buildings from GisFeature)
|
|
-- - gis_documentatii (expertize/zone/receptii from GisFeature)
|
|
-- - gis_administrativ (limite UAT/intravilan/arii speciale from GisFeature)
|
|
-- - gis_uats (UAT boundaries from GisUat) <-- this script
|
|
--
|
|
-- All geometries are in EPSG:3844 (Stereo70).
|
|
-- =============================================================================
|
|
|
|
-- 1. Ensure PostGIS extension
|
|
CREATE EXTENSION IF NOT EXISTS postgis;
|
|
|
|
-- 2. Add native geometry column (idempotent)
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM information_schema.columns
|
|
WHERE table_name = 'GisUat' AND column_name = 'geom'
|
|
) THEN
|
|
ALTER TABLE "GisUat" ADD COLUMN geom geometry(Geometry, 3844);
|
|
END IF;
|
|
END $$;
|
|
|
|
-- 3. Function: convert Esri ring JSON { rings: number[][][] } -> PostGIS geometry
|
|
-- Esri rings format: each ring is an array of [x, y] coordinate pairs.
|
|
-- First ring = exterior, subsequent rings = holes.
|
|
-- Multiple outer rings (non-holes) would need MultiPolygon, but UAT boundaries
|
|
-- from eTerra typically have a single polygon with possible holes.
|
|
--
|
|
-- Strategy: build WKT POLYGON/MULTIPOLYGON from the rings array, then
|
|
-- use ST_GeomFromText with SRID 3844.
|
|
CREATE OR REPLACE FUNCTION gis_uat_esri_to_geom(geom_json jsonb)
|
|
RETURNS geometry AS $$
|
|
DECLARE
|
|
rings jsonb;
|
|
ring jsonb;
|
|
coord jsonb;
|
|
ring_count int;
|
|
coord_count int;
|
|
i int;
|
|
j int;
|
|
wkt_ring text;
|
|
wkt text;
|
|
first_x double precision;
|
|
first_y double precision;
|
|
last_x double precision;
|
|
last_y double precision;
|
|
BEGIN
|
|
-- Extract the rings array from the JSON
|
|
rings := geom_json -> 'rings';
|
|
|
|
IF rings IS NULL OR jsonb_array_length(rings) = 0 THEN
|
|
RETURN NULL;
|
|
END IF;
|
|
|
|
ring_count := jsonb_array_length(rings);
|
|
|
|
-- Build WKT POLYGON with all rings (first = exterior, rest = holes)
|
|
wkt := 'POLYGON(';
|
|
|
|
FOR i IN 0 .. ring_count - 1 LOOP
|
|
ring := rings -> i;
|
|
coord_count := jsonb_array_length(ring);
|
|
|
|
IF coord_count < 3 THEN
|
|
CONTINUE; -- skip degenerate rings
|
|
END IF;
|
|
|
|
IF i > 0 THEN
|
|
wkt := wkt || ', ';
|
|
END IF;
|
|
|
|
wkt_ring := '(';
|
|
|
|
FOR j IN 0 .. coord_count - 1 LOOP
|
|
coord := ring -> j;
|
|
IF j > 0 THEN
|
|
wkt_ring := wkt_ring || ', ';
|
|
END IF;
|
|
wkt_ring := wkt_ring || (coord ->> 0) || ' ' || (coord ->> 1);
|
|
|
|
-- Track first and last coordinates to check ring closure
|
|
IF j = 0 THEN
|
|
first_x := (coord ->> 0)::double precision;
|
|
first_y := (coord ->> 1)::double precision;
|
|
END IF;
|
|
IF j = coord_count - 1 THEN
|
|
last_x := (coord ->> 0)::double precision;
|
|
last_y := (coord ->> 1)::double precision;
|
|
END IF;
|
|
END LOOP;
|
|
|
|
-- Close the ring if not already closed
|
|
IF first_x != last_x OR first_y != last_y THEN
|
|
wkt_ring := wkt_ring || ', ' || first_x::text || ' ' || first_y::text;
|
|
END IF;
|
|
|
|
wkt_ring := wkt_ring || ')';
|
|
wkt := wkt || wkt_ring;
|
|
END LOOP;
|
|
|
|
wkt := wkt || ')';
|
|
|
|
RETURN ST_GeomFromText(wkt, 3844);
|
|
END;
|
|
$$ LANGUAGE plpgsql IMMUTABLE;
|
|
|
|
-- 4. Trigger function: auto-convert Esri JSON -> native PostGIS on INSERT/UPDATE
|
|
CREATE OR REPLACE FUNCTION gis_uat_sync_geom()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.geometry IS NOT NULL THEN
|
|
BEGIN
|
|
NEW.geom := gis_uat_esri_to_geom(NEW.geometry::jsonb);
|
|
EXCEPTION WHEN OTHERS THEN
|
|
-- Invalid geometry JSON -> leave geom NULL rather than fail the write
|
|
NEW.geom := NULL;
|
|
END;
|
|
ELSE
|
|
NEW.geom := NULL;
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- 5. Attach trigger (drop + recreate for idempotency)
|
|
DROP TRIGGER IF EXISTS trg_gis_uat_sync_geom ON "GisUat";
|
|
CREATE TRIGGER trg_gis_uat_sync_geom
|
|
BEFORE INSERT OR UPDATE OF geometry ON "GisUat"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION gis_uat_sync_geom();
|
|
|
|
-- 6. Backfill: convert existing Esri JSON geometries to native PostGIS
|
|
UPDATE "GisUat"
|
|
SET geom = gis_uat_esri_to_geom(geometry::jsonb)
|
|
WHERE geometry IS NOT NULL AND geom IS NULL;
|
|
|
|
-- 7. GiST spatial index for fast spatial queries
|
|
CREATE INDEX IF NOT EXISTS gis_uat_geom_idx
|
|
ON "GisUat" USING GIST (geom);
|
|
|
|
-- =============================================================================
|
|
-- 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.
|
|
-- =============================================================================
|
|
|
|
-- 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;
|
|
|
|
-- 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_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)
|
|
-- =============================================================================
|