Skip to content

Commit 617881a

Browse files
authored
Merge pull request #9 from opengeospatial/geosciml-llm-examples
Merge geosciml llm examples branch into main
2 parents 6081fb7 + e77ccd5 commit 617881a

68 files changed

Lines changed: 4466 additions & 13 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/validate.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Validate examples
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches: ["**"]
7+
8+
jobs:
9+
validate:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
14+
steps:
15+
- uses: actions/checkout@v4
16+
with:
17+
ref: ${{ github.head_ref || github.ref_name }}
18+
19+
- uses: actions/setup-python@v5
20+
with:
21+
python-version: "3.12"
22+
23+
- run: pip install -r requirements.txt
24+
25+
- name: Run validator and refresh report
26+
run: |
27+
python validate.py > examples/json/4.1/validation_report.txt
28+
echo "VALIDATION_EXIT=$?" >> "$GITHUB_ENV"
29+
shell: bash {0}
30+
31+
- name: Commit updated report
32+
run: |
33+
git config user.name "github-actions[bot]"
34+
git config user.email "github-actions[bot]@users.noreply.github.com"
35+
git add examples/json/4.1/validation_report.txt
36+
git diff --cached --quiet || git commit -m "chore: refresh validation report [skip ci]"
37+
git push
38+
39+
- name: Fail if validation failed
40+
if: env.VALIDATION_EXIT != '0'
41+
run: |
42+
echo "::error::Validation failed — see examples/json/4.1/validation_report.txt for details"
43+
exit 1

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
.DS_Store
2-
/shapechange/scresults
2+
.schema_cache/
3+
.idea/
4+
/shapechange/scresults

CLAUDE.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# GeoSciML JSON Code Sprint — project notes for Claude
2+
3+
## What this repo is
4+
5+
Development of a JSON encoding for GeoSciML 4.1, initially in support of the
6+
May 2026 OGC Builder Days Code Sprint. The main artefacts are JSON Schemas
7+
(under `schemas/`) and conformant example instances (under `examples/`).
8+
9+
Reference GML 4.1 instances live in the sibling directory
10+
`../schemas.opengis.net/gsml/4.1/instances/`. Always read the relevant GML
11+
file before writing a new example for a given type.
12+
13+
## Repository layout
14+
15+
```
16+
schemas/json/4.1/ JSON Schema documents (one per feature family)
17+
examples/json/4.1/
18+
<schema-name>/ Individual feature examples for that schema
19+
featurecollections/
20+
<schema-name>/ FeatureCollection examples for that schema
21+
validation_report.txt Last recorded validate.py run
22+
validate.py Validation script (see below)
23+
venv/ Python venv with jsonschema installed
24+
.schema_cache/ Remote schema cache — auto-populated, git-ignored
25+
```
26+
27+
Each schema file `schemas/json/4.1/<name>.json` has a companion example
28+
directory `examples/json/4.1/<name>/` (individual features) and optionally
29+
`examples/json/4.1/featurecollections/<name>/` (FeatureCollection documents).
30+
31+
## Validation
32+
33+
```bash
34+
venv/bin/python validate.py
35+
```
36+
37+
The script maps each example directory to its schema, fetches remote dependency
38+
schemas (json-fg, SWE Common) on first run, caches them under `.schema_cache/`,
39+
and exits with code 1 if any file fails.
40+
41+
Always run validation before committing and update the recorded report:
42+
43+
```bash
44+
venv/bin/python validate.py > examples/json/4.1/validation_report.txt
45+
```
46+
47+
## Adding a new example directory
48+
49+
1. Create `examples/json/4.1/<schema-name>/` for individual features, or
50+
`examples/json/4.1/featurecollections/<schema-name>/` for collections.
51+
2. Register the directory in `validate.py` by adding an entry to `DIR_SCHEMA`:
52+
```python
53+
EXAMPLES_DIR / "<schema-name>": f"{_BASE}/<schema-name>.json",
54+
EXAMPLES_DIR / "featurecollections/<schema-name>": f"{_BASE}/<schema-collection-name>.json",
55+
```
56+
The validator automatically treats any path containing `featurecollections`
57+
as a collection (validated against the schema's top-level `$ref`); individual
58+
features are narrowed to the `$anchor` matching their `featureType`.
59+
3. Run the validator to confirm the new directory is picked up.
60+
61+
## Writing a new example instance
62+
63+
### General structure (JSON-FG feature)
64+
65+
Every feature follows the JSON-FG feature structure:
66+
67+
```json
68+
{
69+
"type": "Feature",
70+
"featureType": "<TypeName>",
71+
"id": "<unique-string>",
72+
"geometry": { <GeoJSON geometry in WGS84, or null> },
73+
"place": { <geometry in local CRS — omit entirely if WGS84-only> },
74+
"coordRefSys": "<CRS URI — only when place is present, at Feature level>",
75+
"time": null,
76+
"properties": { ... }
77+
}
78+
```
79+
80+
- `featureType` must match a `$anchor` in the target schema.
81+
- `geometry` uses WGS84 lon/lat. `place` is only for non-WGS84 geometries;
82+
declare the CRS with `coordRefSys` **at the Feature level**, never inside the
83+
geometry object itself (json-fg prohibits it there).
84+
- Purely descriptive features (e.g. GeologicUnit, GeologicEvent) typically have
85+
`geometry: null` and no `place`.
86+
87+
### SWE Common encoding
88+
89+
SWE Category, Quantity, and QuantityRange objects all require `type`,
90+
`definition` (a URI), and `label` (a string). Quantity and QuantityRange also
91+
require `uom`. Missing any of these causes a validation failure.
92+
93+
```json
94+
{ "type": "Category", "definition": "http://...", "label": "...", "value": "http://..." }
95+
{ "type": "Quantity", "definition": "http://...", "label": "...", "uom": {"code": "m"}, "value": 100.0 }
96+
{ "type": "QuantityRange", "definition": "http://...", "label": "...", "uom": {"code": "%"}, "value": [5.0, 50.0] }
97+
```
98+
99+
### Links vs inline objects
100+
101+
Properties that accept either a reference or an embedded object use
102+
`oneOf [SCLinkObject, <Type>]`. An SCLinkObject requires `href`:
103+
104+
```json
105+
{ "href": "http://...", "title": "Human readable label" }
106+
```
107+
108+
Use a link when the target is defined elsewhere; use an inline object when the
109+
value is self-contained in the example.
110+
111+
### Lite schema specifics
112+
113+
All Lite properties are flat strings, numbers, or URIs — no nested SWE objects.
114+
Properties suffixed `_uri` take a concept URI; the un-suffixed twin is a
115+
human-readable string.
116+
117+
The Lite schema's `place` references `geometry-object.json` directly (no null
118+
branch), so omit `place` entirely when the geometry is WGS84-only. Add
119+
`place` + `coordRefSys` (at Feature level) to show a non-WGS84 geometry.
120+
121+
### Feature collections
122+
123+
Homogeneous (single type): put `featureType` at the collection level.
124+
Mixed: omit `featureType` from the collection; each feature carries its own.
125+
126+
## Vocabulary URIs
127+
128+
Use CGI / INSPIRE / ICS URIs consistently. Common bases:
129+
130+
| Vocabulary | Base URI |
131+
|---|---|
132+
| CGI simple lithology | `http://resource.geosciml.org/classifier/cgi/simplelithology/` |
133+
| CGI geologic unit type | `http://resource.geosciml.org/classifier/cgi/geologicunittype/` |
134+
| CGI stratigraphic rank | `http://resource.geosciml.org/classifier/cgi/stratigraphicrank/` |
135+
| CGI event process | `http://resource.geosciml.org/classifier/cgi/eventprocess/` |
136+
| CGI fault type | `http://resource.geosciml.org/classifier/cgi/faulttype/` |
137+
| CGI contact type | `http://resource.geosciml.org/classifier/cgi/contacttype/` |
138+
| ICS chart (ages) | `http://resource.geosciml.org/classifier/ics/ischart/` |
139+
| INSPIRE description purpose | `http://inspire.ec.europa.eu/codelist/DescriptionPurpose/` |
140+
| INSPIRE composition part role | `http://inspire.ec.europa.eu/codelist/CompositionPartRoleValue/` |
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"$comment": "Adapted from Loop3D-GSO/Examples/GSO-ExampleComplexContacts.ttl — the contact `con:contact_Js_on_Xm`, a Nonconformable Contact where Jurassic Js Formation (sedimentary) unconformably overlies the Early Proterozoic Xm Rock Body (metamorphic basement). Validates against schemas/json/4.1/geoscimlBasic.json#Contact. The TTL also defines part-whole decomposition into Rock_Body_Boundary instances (KdBoundaryDownSide-3_1_1, baseJs-6_3, etc.); those are outside GeoSciML Basic's Contact model and are not represented here.",
3+
"type": "Feature",
4+
"id": "contact.JsOnXm",
5+
"featureType": "Contact",
6+
"conformsTo": [
7+
"https://ext.iide.dev/schemas/geosciml/json/4.1/geoscimlBasic.json"
8+
],
9+
"geometry": null,
10+
"properties": {
11+
"name": "Js on Xm nonconformity",
12+
"description": "Nonconformable contact: Jurassic Js Formation (sedimentary) deposited on the eroded surface of the Early Proterozoic Xm Rock Body (medium-grade gneiss). Hosted on rock-body boundaries `baseJs-6_3` (younger, base of Js) and `XmBoundary-6_3` (older, erosional top of Xm). The older surface records pre-Jurassic erosion of crystalline basement.",
13+
"purpose": "instance",
14+
"observationMethod": [
15+
{
16+
"type": "Category",
17+
"definition": "http://resource.geosciml.org/classifier/cgi/featureobservationmethod",
18+
"label": "synthesis from multiple sources",
19+
"codeSpace": "http://resource.geosciml.org/classifier/cgi/featureobservationmethod",
20+
"value": "http://resource.geosciml.org/classifier/cgi/featureobservationmethod/synthesis_from_multiple_sources"
21+
}
22+
],
23+
"geologicHistory": [
24+
{
25+
"type": "Feature",
26+
"id": "JsDeposition",
27+
"featureType": "GeologicEvent",
28+
"geometry": null,
29+
"properties": {
30+
"name": "Deposition of Unit Js",
31+
"description": "Jurassic-age deposition of the Js Formation onto eroded Paleozoic and Proterozoic substrate.",
32+
"eventProcess": [
33+
"http://resource.geosciml.org/classifier/cgi/eventprocess/deposition"
34+
],
35+
"olderNamedAge": "http://resource.geosciml.org/classifier/ics/ischart/EarlyJurassic",
36+
"youngerNamedAge": "http://resource.geosciml.org/classifier/ics/ischart/LateJurassic"
37+
}
38+
},
39+
{
40+
"type": "Feature",
41+
"id": "preJurassicErosion",
42+
"featureType": "GeologicEvent",
43+
"geometry": null,
44+
"properties": {
45+
"name": "Pre-Jurassic erosion of Xm basement",
46+
"description": "Long erosional interval that exposed the Xm metamorphic basement before Js deposition; produces the nonconformity surface.",
47+
"eventProcess": [
48+
"http://resource.geosciml.org/classifier/cgi/eventprocess/erosion"
49+
]
50+
}
51+
}
52+
],
53+
"relatedFeature": [
54+
{
55+
"href": "https://example.org/loop3d/JsFormation",
56+
"title": "Js Formation (Jurassic) — younger host",
57+
"rel": "youngerHost"
58+
},
59+
{
60+
"href": "https://example.org/loop3d/XmRockBody",
61+
"title": "Xm Rock Body (Early Proterozoic gneiss) — older host",
62+
"rel": "olderHost"
63+
}
64+
],
65+
"contactType": "http://resource.geosciml.org/classifier/cgi/contacttype/nonconformity"
66+
}
67+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"type": "Feature",
3+
"featureType": "Contact",
4+
"id": "contact-disconformity-xy",
5+
"geometry": null,
6+
"place": null,
7+
"time": null,
8+
"properties": {
9+
"purpose": "http://inspire.ec.europa.eu/codelist/DescriptionPurpose/instance",
10+
"classifier": [
11+
{
12+
"type": "Category",
13+
"definition": "http://some.org/classifier/contactType",
14+
"label": "erosional contact",
15+
"value": "http://some.org/contactType/erosional-contact"
16+
}
17+
],
18+
"geologicHistory": [
19+
{
20+
"type": "Feature",
21+
"featureType": "GeologicEvent",
22+
"id": "non-deposition-xy",
23+
"geometry": null,
24+
"place": null,
25+
"time": null,
26+
"properties": {
27+
"eventProcess": [
28+
"http://resource.geosciml.org/classifier/cgi/eventprocess/non-deposition"
29+
],
30+
"olderNamedAge": "http://resource.geosciml.org/classifier/ics/ischart/Cambrian",
31+
"youngerNamedAge": "http://resource.geosciml.org/classifier/ics/ischart/Ordovician"
32+
}
33+
}
34+
],
35+
"contactType": "http://resource.geosciml.org/classifier/cgi/contacttype/disconformity"
36+
}
37+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"type": "Feature",
3+
"featureType": "Contact",
4+
"id": "contact-null-contacttype-invalid",
5+
"geometry": null,
6+
"place": null,
7+
"time": null,
8+
"properties": {
9+
"purpose": "http://inspire.ec.europa.eu/codelist/DescriptionPurpose/instance",
10+
"contactType": null
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"type": "Feature",
3+
"featureType": "Contact",
4+
"id": "contact-unconformity-1",
5+
"geometry": null,
6+
"place": null,
7+
"time": null,
8+
"properties": {
9+
"purpose": "http://inspire.ec.europa.eu/codelist/DescriptionPurpose/instance",
10+
"contactType": "http://resource.geosciml.org/classifier/cgi/contacttype/unconformity"
11+
}
12+
}

examples/json/4.1/basic/fold.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$comment": "Adapted from Loop3D-GSO/Examples/GSO-ExampleFold.ttl — an antiform with a hinge plunging 33° toward 330° and an axial surface dipping 78° toward 310°, amplitude 10 m. Only Antiform classification + name/description fit GeoSciML Basic's Fold class; the structured hinge/axial-surface/amplitude measurements require GeoSciML Extension's FoldDescription (carried via the stFoldDescription stub, abstract in Basic). They are summarised here as prose in `description`. Validates against schemas/json/4.1/geoscimlBasic.json#Fold.",
3+
"type": "Feature",
4+
"id": "fold.gso.1",
5+
"featureType": "Fold",
6+
"conformsTo": [
7+
"https://ext.iide.dev/schemas/geosciml/json/4.1/geoscimlBasic.json"
8+
],
9+
"geometry": null,
10+
"properties": {
11+
"name": "is test fold",
12+
"description": "Test fold properties — an antiform. Hinge line plunges 33° toward azimuth 330°; axial surface dips 78° toward 310° (Plane_Dip_Dip_Direction convention); fold amplitude 10 m. (Structured orientation and amplitude values are carried in GeoSciML Extension's FoldDescription, not in this Basic encoding.)",
13+
"purpose": "instance",
14+
"profileType": "http://resource.geosciml.org/classifier/cgi/foldprofiletype/antiform"
15+
}
16+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"type": "Feature",
3+
"featureType": "Fold",
4+
"id": "anticline-complex",
5+
"geometry": null,
6+
"place": null,
7+
"time": null,
8+
"properties": {
9+
"observationMethod": [
10+
{
11+
"type": "Category",
12+
"definition": "http://resource.geosciml.org/classifierScheme/cgi/featureobservationmethod",
13+
"label": "field observation",
14+
"value": "http://resource.geosciml.org/classifier/cgi/featureobservationmethod/field_observation"
15+
}
16+
],
17+
"purpose": "http://inspire.ec.europa.eu/codelist/DescriptionPurpose/instance",
18+
"occurrence": [
19+
{
20+
"href": "http://data.geoscience.gov.au/mappedfeature/fold/anticline-mf-1",
21+
"title": "Anticline trace – 1:50000 sheet"
22+
}
23+
],
24+
"geologicHistory": [
25+
{
26+
"type": "Feature",
27+
"featureType": "GeologicEvent",
28+
"id": "anticline-deformation",
29+
"geometry": null,
30+
"place": null,
31+
"time": null,
32+
"properties": {
33+
"eventProcess": [
34+
"http://resource.geosciml.org/classifier/cgi/eventprocess/folding"
35+
],
36+
"olderNamedAge": "http://resource.geosciml.org/classifier/ics/ischart/Silurian",
37+
"youngerNamedAge": "http://resource.geosciml.org/classifier/ics/ischart/Devonian"
38+
}
39+
}
40+
],
41+
"profileType": "http://resource.geosciml.org/classifier/cgi/foldprofiletype/anticline"
42+
}
43+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"type": "Feature",
3+
"featureType": "Fold",
4+
"id": "fold-null-profiletype-invalid",
5+
"geometry": null,
6+
"place": null,
7+
"time": null,
8+
"properties": {
9+
"purpose": "http://inspire.ec.europa.eu/codelist/DescriptionPurpose/instance",
10+
"profileType": null
11+
}
12+
}

0 commit comments

Comments
 (0)