GeoPackage → SensorThings API (STA 2.0) transformation

Converts the SoilWise GeoPackage into a SensorThings API 2.0 batch request, ready to POST to a FROST server’s $batch endpoint. FROST Server docs if you need a server to test against or want the full API reference.

The transformation is built as a hale Studio project. Two small Python scripts handle the parts hale’s stock JSON writer cannot do on its own (see Known limitations).

Pipeline

GeoPackage --[hale transform]--> flat JSON array --[assemble_batch.py]--> FROST $batch file --[upload_batch.py]--> FROST server
  1. hale transform: runs the alignment (SoilWise-gpkg-to-STA.halex) against a source GeoPackage, producing one flat, ordered JSON array of STA request envelopes (one per entity to create).
  2. assemble_batch.py: post-processes that array. It reprojects each Feature’s geometry from the GeoPackage’s native CRS (EPSG:3035) to WGS84 (EPSG:4326) as required by GeoJSON, and wraps the array in the {"requests": [...]} envelope FROST’s $batch endpoint expects.
  3. upload_batch.py (optional): uploads the assembled batch to a live FROST server via its JSON Batch Request extension, in dependency order and in manageable chunks, rewriting the batch-internal "$<id>" placeholder references into the real server-assigned @iot.ids as it goes.

Prerequisites

Usage

# 1. Transform the GeoPackage to a flat JSON array
hale transform -project SoilWise-gpkg-to-STA.halex \
  -source path/to/your/SoilWise.gpkg \
  -target out.json -providerId eu.esdihumboldt.hale.io.json.writer -trustGroovy

# 2. Assemble the FROST batch file
python assemble_batch.py out.json batch.json

# 3. (optional) Upload to a FROST server
#    Set FROST_USER / FROST_PASS as environment variables first if the server requires auth.
python upload_batch.py batch.json https://your-frost-server/v2.0

upload_batch.py also accepts an optional max-per-type argument to cap how many requests per entity type are uploaded, useful for a smaller test/demo load instead of the full dataset.

What’s mapped

All INSPIRE Soil feature types present in the GeoPackage are mapped to STA Feature + shared FeatureType entities (SoilSite, SoilProfile, ProfileElement, SoilDerivedObject, SoilBody), each carrying its full geometry (including SoilBody’s multi-part geometries, combined into one MultiPolygon per body). The observational side (ObservedProperty, Sensor, ObservingProcedure, Thing, Datastream, Observation) is mapped in full, including SWE Common’s per-type resultType handling (Quantity/Category/etc.) and unit-of-measure resolution.

Non-observational/qualitative content nested under SoilProfile and ProfileElement (WRB qualifiers, FAO horizon notation, and the isderivedfrom provenance relation) is carried through as structured nested arrays/objects in properties, in the {source_table, related_source_table, records:[...]} shape.

Provisional modelling decisions

The following are working decisions made to unblock the transformation, not final, agreed answers. They’re built and functioning, but each has open alternatives that were considered and not resolved.

Known limitations