How to define domain boundaries for geospatial data

Boundary leakage in spatial datasets is the primary driver of cross-domain query latency and SLA degradation in enterprise geospatial platforms. When vector parcels, raster elevation models, and real-time sensor feeds share unbounded routing paths, the query planner defaults to full-table spatial scans, bypasses partition pruning, and triggers resource contention across domains that should never touch. This page is a step-by-step operation: declare an explicit boundary for each domain, enforce it at the data mesh ingress layer, and verify that non-conforming geometries are rejected before they reach storage. It is the concrete implementation of Spatial Domain Boundary Design within Geospatial Data Mesh Fundamentals, and it pairs directly with Defining Scoping Rules for Enterprise Spatial Products, which decides what belongs in a domain while this procedure isolates where the perimeter is drawn and enforced.

Ingress admission flow for a geometry payload through three boundary gates An incoming geometry payload carrying an x-domain-id header and WKT extent enters a left-to-right sequence of three admission gates: a CRS check that the payload SRID equals the domain crs, a spatial_extent containment check using ST_Within, and an ownership-tag match on the team label. A payload that passes all three is written to the GiST-indexed domain store as conforming. Any gate failure drops the payload onto a violation rail — labelled crs_mismatch, extent_overflow, and tag_denied — which terminates in a reject response returning ERR_SPATIAL_EXTENT_OVERFLOW before the geometry ever reaches storage. conforming Geometry payload x-domain-id · WKT CRS check SRID == domain crs Extent containment ST_Within(extent) Ownership tag team== match Domain store GiST-indexed write crs_mismatch extent_overflow tag_denied Reject ERR_SPATIAL_EXTENT_OVERFLOW

Prerequisites

Requirement Value / Assumption Notes
Tools kubectl ≥ 1.27, opa ≥ 0.60, psql ≥ 14 (PostGIS 3.3), curl, ogrinfo (GDAL ≥ 3.6) Server-side apply requires the 1.27+ field manager.
CRS convention Declared per domain as EPSG:4326 (geographic) or EPSG:32633 (UTM 33N) All extents and incoming geometries must be expressed in the domain’s declared SRID.
Access roles platform-engineer (manifest apply), gis-data-steward (policy override) Maps to the escalation tiers below; both bound to the geospatial-ingress namespace.
Environment variables CATALOG_API, DATASET_ID, INGRESS_HOST Exported into the deploy shell before running the verification commands.
Namespace geospatial-ingress Hosts the admission webhook and the boundary ConfigMap.

The boundary policy is a declarative routing manifest that maps spatial predicates to domain-level namespaces. It must be applied through the platform GitOps controller before any downstream ingestion pipeline is activated, so that the first asset a domain ever publishes is already validated against its perimeter.

Step-by-Step Implementation

Step 1 — Author the boundary manifest

Define each domain’s coordinate reference system, spatial extent, ownership tag, and permitted operations in a single YAML manifest. Keeping every domain in one source of truth prevents the silent extent drift described in Metadata Cataloging for Raster/Vector.

yaml
# spatial-boundary-policy.yaml
spatial_boundary_policy:
  version: "2.1"
  enforcement_mode: "strict"
  reconciliation_interval: "300s"
  domains:
    - domain_id: "urban_planning_vector"
      crs: "EPSG:4326"
      spatial_extent: "POLYGON((-122.5 37.7, -122.3 37.7, -122.3 37.9, -122.5 37.9, -122.5 37.7))"
      ownership_tag: "team=city-planning"
      allowed_operations: ["read", "spatial_join", "buffer"]
      metadata_schema: "vector_feature_v3"
    - domain_id: "environmental_raster"
      crs: "EPSG:4326"
      spatial_extent: "POLYGON((-123.0 36.5, -121.5 36.5, -121.5 38.5, -123.0 38.5, -123.0 36.5))"
      ownership_tag: "team=env-monitoring"
      allowed_operations: ["read", "raster_calc", "clip"]
      metadata_schema: "raster_tile_v2"

The enforcement_mode: "strict" directive instructs the ingress router to reject any payload that violates the declared extent or CRS. This inverts the legacy monolithic GIS model, where boundary validation runs post-ingestion and forces costly ETL rollbacks.

Verify the manifest parses and the extents are well-formed WKT before applying:

bash
# Confirm each spatial_extent is a closed, valid polygon in the declared CRS
ogrinfo -q -sql \
  "SELECT ST_IsValid(ST_GeomFromText('POLYGON((-122.5 37.7, -122.3 37.7, -122.3 37.9, -122.5 37.9, -122.5 37.7))', 4326))" \
  -dialect SQLite /vsistdin/ < /dev/null 2>/dev/null || \
  echo "Validate WKT closure: first and last vertex must match"

Step 2 — Apply the manifest idempotently

Policy application must be idempotent so that rolling deployments and CI/CD retries never produce configuration drift. Use server-side apply with an explicit field manager.

bash
# Pre-flight validation (non-destructive)
kubectl apply --dry-run=server -f spatial-boundary-policy.yaml \
  -n geospatial-ingress

# Idempotent apply with server-side field management
kubectl apply -f spatial-boundary-policy.yaml \
  --server-side \
  --field-manager=platform-engineer \
  -n geospatial-ingress

Verify the version propagated to the live ConfigMap:

bash
kubectl get configmap spatial-boundary-policy \
  -n geospatial-ingress \
  -o jsonpath='{.data.version}'   # expect: 2.1

Step 3 — Confirm the admission webhook enforces the perimeter

Boundary rules are only real once the ingress admission webhook evaluates them. Send a compliant request whose geometry sits inside the declared urban_planning_vector extent.

bash
curl -sf -X POST "https://${INGRESS_HOST}/validate" \
  -H "x-domain-id: urban_planning_vector" \
  -H "Content-Type: application/json" \
  -d '{"crs":"EPSG:4326","extent":"POLYGON((-122.4 37.75,-122.35 37.75,-122.35 37.8,-122.4 37.8,-122.4 37.75))"}'

Expected output: {"allowed": true, "domain": "urban_planning_vector"}. Then send a deliberately out-of-bounds geometry to confirm the negative path returns {"allowed": false} with reason ERR_SPATIAL_EXTENT_OVERFLOW. Any compliant request that is rejected — or any violating request that is accepted — must be resolved before activating downstream pipelines.

Step 4 — Align the database SRID with the declared CRS

The webhook guards ingress, but the query planner still relies on the underlying PostGIS index matching the declared SRID. Run EXPLAIN ANALYZE on a representative cross-domain query and confirm a GiST index scan, not a Seq Scan or a post-index ST_Transform.

sql
-- Index must be built against the SRID declared in the manifest (4326)
SELECT Find_SRID('public', 'urban_parcels', 'geom');   -- expect: 4326

If the SRID diverges from the manifest crs field, reconcile spatial_ref_sys and rebuild the index in Step 5. Treat boundary modifications as governed changes, the same way Product Thinking for GIS Datasets treats a versioned product feature.

Configuration Reference

Field Type Required Description
version string yes Policy revision; surfaced in the ConfigMap for drift checks.
enforcement_mode enum (strict | audit) yes strict rejects violations; audit logs them without blocking.
reconciliation_interval duration yes How often the controller re-reconciles routing rules (e.g. 300s).
domains[].domain_id string yes Stable identifier sent as the x-domain-id ingress header.
domains[].crs EPSG URI yes Authoritative SRID for the domain; must match the PostGIS index.
domains[].spatial_extent WKT POLYGON yes Closed perimeter; incoming geometries must be fully contained.
domains[].ownership_tag team=<name> yes IAM ownership label used for routing and audit attribution.
domains[].allowed_operations list yes Operations the router will permit against the domain.
domains[].metadata_schema string yes Catalog schema version; mismatch forces a legacy fallback path.
Ingress header Purpose Example
x-domain-id Selects the domain rule to evaluate against urban_planning_vector
x-crs-override Rejected under strict; logged under audit EPSG:3857
x-asset-extent WKT extent of the incoming payload POLYGON((...))

Common Failure Modes & Fixes

CRS mismatch in the query planner. Symptom: EXPLAIN ANALYZE shows a Seq Scan or an ST_Transform applied after the index. Root cause: the manifest crs does not match the table’s SRID, so the GiST index is unusable. Fix: align the spatial_ref_sys SRID with the declared CRS, then rebuild the index concurrently to avoid locking:

sql
CREATE INDEX CONCURRENTLY idx_spatial_table_geom_new
  ON urban_parcels USING GIST (geom);
DROP INDEX CONCURRENTLY idx_spatial_table_geom_old;

Metadata catalog drift. Symptom: requests bypass boundary enforcement and fall back to a legacy path. Root cause: the dataset’s catalog entry no longer matches the metadata_schema tag. Fix: compare the live schema version and re-register if it diverges:

bash
curl -sf "${CATALOG_API}/datasets/${DATASET_ID}/schema-version"   # expect: raster_tile_v2

Add automated schema-drift detection to CI so versioning is enforced before the manifest is applied.

Spatial index fragmentation. Symptom: idx_scan stays near zero despite high query volume. Root cause: the index is fragmented or invalidated by an extent shift. Fix: run VACUUM ANALYZE <table>, then inspect usage and rebuild concurrently if reads still miss the index:

sql
SELECT s.indexrelname AS index_name, s.idx_scan, s.idx_tup_read, s.idx_tup_fetch
FROM pg_stat_user_indexes s
JOIN pg_indexes i ON i.schemaname = s.schemaname AND i.indexname = s.indexrelname
WHERE s.relname = 'urban_parcels' AND i.indexdef ILIKE '%gist%';

Extent overflow on ingest. Symptom: ingress logs emit ERR_SPATIAL_EXTENT_OVERFLOW. Root cause: incoming geometries exceed the declared spatial_extent, so the router takes its fallback path. Fix: clip the asset to the domain extent before publishing, or renegotiate the boundary through the escalation workflow rather than widening the polygon silently.

Non-idempotent re-apply. Symptom: a CI retry mutates lineage or re-triggers reprojection jobs. Root cause: the manifest was applied without server-side field management, so competing managers fight over fields. Fix: always apply with --server-side --field-manager=platform-engineer; identical payloads must then be no-ops.

Escalation Paths & Cross-Team Governance

Boundary enforcement failures need a structured response so a single domain’s drift does not cascade into an SLA breach across dependent services.

Tier Trigger Action Owner SLA
T1 idx_scan=0, catalog drift alert Automated reconciliation, index rebuild, manifest dry-run Platform Engineering 15 min
T2 Repeated ERR_SPATIAL_EXTENT_OVERFLOW or cross-domain latency > 2s Policy override with audit trail, CRS alignment, temporary routing bypass GIS Data Steward 1 hr
T3 Persistent boundary leakage across domains, SLA breach Architecture review, boundary redesign, product lifecycle rollback Data Architecture Board 4 hr

Escalation to T2 or T3 requires a formal change ticket and a rollback manifest. Every boundary modification undergoes peer review, downstream impact analysis, and a versioned policy commit — the same discipline that keeps Spatial Product Lifecycle Management backward-compatible across version bumps. To keep extent declarations interoperable across mesh nodes, align ingress policies with the OGC API - Features Standard.

FAQ

Why enforce the boundary at ingress instead of at query time?

Query-time enforcement still admits non-conforming geometry into the domain store, so the planner has already lost partition pruning by the time a filter runs. Validating at the admission webhook (Step 3) rejects the payload before it is written, which keeps the GiST index dense and the spatial_extent an actual guarantee rather than a hint.

What is the difference between strict and audit enforcement modes?

strict rejects any payload whose CRS or extent violates the domain rule and returns ERR_SPATIAL_EXTENT_OVERFLOW. audit logs the same violations but admits the payload, which is useful when onboarding a new domain whose historical assets have not yet been clipped. Promote a domain to strict only after its audit logs are clean.

How do I add a new domain without disrupting existing ones?

Append a new entry under domains and re-apply with --server-side. Because the field manager owns only the fields it sets, existing domains are untouched and identical entries are no-ops. Verify with the ConfigMap version check in Step 2, then exercise the webhook with one in-bounds and one out-of-bounds geometry for the new domain_id.

A query is slow even though the webhook accepts the geometry — where do I look first?

Run EXPLAIN ANALYZE. An accepted geometry that still scans slowly almost always means the database SRID has drifted from the manifest crs, so the GiST index is bypassed. Confirm with Find_SRID(...) (Step 4) and rebuild the index concurrently if it diverges.

Should I widen a spatial_extent when assets fall outside it?

No. A recurring ERR_SPATIAL_EXTENT_OVERFLOW is a signal that ownership is ambiguous, not that the polygon is too small. Route it through the T2/T3 escalation so the boundary change is reviewed, impact-assessed, and committed — silently widening the extent reintroduces the cross-domain leakage the perimeter exists to prevent.