feat(parcel-sync): native PostGIS geometry support for QGIS
- Remove postgresqlExtensions/postgis from Prisma schema (PostGIS not yet installed) - Add prisma/postgis-setup.sql: trigger auto-converts GeoJSON→native geometry, GiST spatial index, QGIS-friendly views (gis_terenuri, gis_cladiri, etc.) - Add POST /api/eterra/setup-postgis endpoint (idempotent, runs all SQL setup) - Add safety-net raw SQL in sync-service: backfills geom after upsert phase - Add QGIS/PostGIS setup card in layer catalog UI with connection info - Schema comment documents the trigger-managed 'geom' column approach
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
-- =============================================================================
|
||||
-- PostGIS native geometry setup for GisFeature
|
||||
-- Run once via POST /api/eterra/setup-postgis (idempotent — safe to re-run)
|
||||
--
|
||||
-- What this does:
|
||||
-- 1. Ensures PostGIS extension
|
||||
-- 2. Adds native geometry column (geom) if missing
|
||||
-- 3. Creates trigger to auto-convert GeoJSON → native on INSERT/UPDATE
|
||||
-- 4. Backfills existing features that have JSON geometry but no native geom
|
||||
-- 5. Creates GiST spatial index for fast spatial queries
|
||||
-- 6. Creates QGIS-friendly views with clean column names
|
||||
-- =============================================================================
|
||||
|
||||
-- 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 = 'GisFeature' AND column_name = 'geom'
|
||||
) THEN
|
||||
ALTER TABLE "GisFeature" ADD COLUMN geom geometry(Geometry, 3844);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 3. Trigger function: auto-convert GeoJSON (geometry JSON column) → native PostGIS (geom)
|
||||
CREATE OR REPLACE FUNCTION gis_feature_sync_geom()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.geometry IS NOT NULL THEN
|
||||
BEGIN
|
||||
NEW.geom := ST_SetSRID(ST_GeomFromGeoJSON(NEW.geometry::text), 3844);
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
-- Invalid GeoJSON → leave geom NULL rather than fail the write
|
||||
NEW.geom := NULL;
|
||||
END;
|
||||
ELSE
|
||||
NEW.geom := NULL;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 4. Attach trigger (drop + recreate for idempotency)
|
||||
DROP TRIGGER IF EXISTS trg_gis_feature_sync_geom ON "GisFeature";
|
||||
CREATE TRIGGER trg_gis_feature_sync_geom
|
||||
BEFORE INSERT OR UPDATE OF geometry ON "GisFeature"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION gis_feature_sync_geom();
|
||||
|
||||
-- 5. Backfill: convert existing JSON geometries to native
|
||||
UPDATE "GisFeature"
|
||||
SET geom = ST_SetSRID(ST_GeomFromGeoJSON(geometry::text), 3844)
|
||||
WHERE geometry IS NOT NULL AND geom IS NULL;
|
||||
|
||||
-- 6. GiST spatial index for fast bounding-box / intersection queries
|
||||
CREATE INDEX IF NOT EXISTS gis_feature_geom_idx
|
||||
ON "GisFeature" USING GIST (geom);
|
||||
|
||||
-- =============================================================================
|
||||
-- 7. QGIS-friendly views
|
||||
-- - Clean snake_case column names
|
||||
-- - Only rows with valid geometry
|
||||
-- - One master view + per-category views
|
||||
-- =============================================================================
|
||||
|
||||
-- Master view: all features
|
||||
CREATE OR REPLACE VIEW gis_features AS
|
||||
SELECT
|
||||
id,
|
||||
"layerId" AS layer_id,
|
||||
siruta,
|
||||
"objectId" AS object_id,
|
||||
"inspireId" AS inspire_id,
|
||||
"cadastralRef" AS cadastral_ref,
|
||||
"areaValue" AS area_value,
|
||||
"isActive" AS is_active,
|
||||
attributes,
|
||||
"projectId" AS project_id,
|
||||
"createdAt" AS created_at,
|
||||
"updatedAt" AS updated_at,
|
||||
geom
|
||||
FROM "GisFeature"
|
||||
WHERE geom IS NOT NULL;
|
||||
|
||||
-- Terenuri (parcels)
|
||||
CREATE OR REPLACE VIEW gis_terenuri AS
|
||||
SELECT * FROM gis_features
|
||||
WHERE layer_id LIKE 'TERENURI%' OR layer_id LIKE 'CADGEN_LAND%';
|
||||
|
||||
-- Clădiri (buildings)
|
||||
CREATE OR REPLACE VIEW gis_cladiri AS
|
||||
SELECT * FROM gis_features
|
||||
WHERE layer_id LIKE 'CLADIRI%' OR layer_id LIKE 'CADGEN_BUILDING%';
|
||||
|
||||
-- Documentații (expertize, zone interes, recepții)
|
||||
CREATE OR REPLACE VIEW gis_documentatii AS
|
||||
SELECT * FROM gis_features
|
||||
WHERE layer_id LIKE 'EXPERTIZA%'
|
||||
OR layer_id LIKE 'ZONE_INTERES%'
|
||||
OR layer_id LIKE 'RECEPTII%';
|
||||
|
||||
-- Administrativ (limite UAT, intravilan, arii speciale)
|
||||
CREATE OR REPLACE VIEW gis_administrativ AS
|
||||
SELECT * FROM gis_features
|
||||
WHERE layer_id LIKE 'LIMITE%'
|
||||
OR layer_id LIKE 'SPECIAL_AREAS%';
|
||||
|
||||
-- =============================================================================
|
||||
-- Done! QGIS connection: PostgreSQL → 10.10.10.166:5432 / architools_db
|
||||
-- Add layers from views: gis_terenuri, gis_cladiri, gis_documentatii, etc.
|
||||
-- SRID: 3844 (Stereo70)
|
||||
-- =============================================================================
|
||||
Reference in New Issue
Block a user