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.
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.
# 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:
# 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.
# 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:
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.
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.
-- 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:
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:
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:
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.