The single place the PDSSP ontology suite is authored -- one independent, stable named graph per data model, plus a separate, independently-evolving graph for the mapping between any two of them (see "Design" below for why). Adding a new consuming service means adding its model/mapping here; that service then queries Fuseki via SPARQL for whatever it needs -- no service is ever imported from, or fetched from, by this repo.
Published two ways:
- hosted in Apache Jena Fuseki
(
docker-compose.yml) for SPARQL querying, - published as a Widoco-generated documentation site per named graph (real WebVOWL graph, real multi-format downloads).
Only one named graph exists today: the PDSSP Thesaurus
(vocab/thesaurus/*.ttl -- product type, method, processing level).
A PDSSP/STAC OWL profile, an EPN-TAP vocabulary, and the STAC<->EPN-TAP mapping between them are not included in this pass. An earlier attempt at building all of these (a since-deleted prototype of this same repo) had known inconsistencies that were never resolved. Real, reusable material exists for a future pass:
- a standalone STAC Core 1.1.0 + SSYS 1.1.1 OWL ontology (with SHACL
shapes) at
/home/malapert/Downloads/stac_owl_ontologies/-- reifiedstac:Link, GeoSPARQL-based geometry,stac:CommonMetadata, etc. - the previously-published (still live, not yet re-verified) EPN-TAP vocabulary at https://pdssp.github.io/pdssp-ontology/epn-tap/ and the STAC<->EPN-TAP mapping at https://pdssp.github.io/pdssp-ontology/mappings/pdssp-stac-epn-tap/.
Each would become its own named graph here (pdssp-stac, epn-tap,
mappings/pdssp-stac-epn-tap), added one at a time once actually
correct -- see "Design" below for why they're kept structurally separate
rather than merged into one graph.
See docs/model-management.md. In short:
vocab/stac/andvocab/epn-tap/: one RDF property per STAC field / EPN-TAP column, each tied to an OMS/SOSA concept;mappings/stac-epn-tap/*.sssom.tsv: field and value mappings (SSSOM), with the transform named per row;- EPN-TAP value lists are reused from PADC (Observatoire de Paris), snapshots in
vocab/external/; python -m pdssp_ontology.convert item.json: STAC Item -> EPN-TAP row, driven by the mapping files;python -m pdssp_ontology.export --out site: browsable pages where every IRI resolves, desise/JSON-LD/Turtle downloads, the crosswalk with its loss report,enums.json, and one JSON Schema per scheme (schemas/<scheme>.json) that the stacplanet profile$refs.
Eleven independent SKOS concept schemes under vocab/thesaurus/:
| Dimension | Question | File | Example values |
|---|---|---|---|
| Product type | What am I downloading? | product-type.ttl |
Digital elevation model, Orthoimage, Anaglyph, Point cloud |
| Method | How was it produced? | method.ttl |
Panchromatic imaging, Multispectral imaging, Photogrammetry, Stereoscopic imaging |
| Processing level | How far has it been processed? | processing-level.ttl |
Raw, Calibrated, Derived, Ancillary |
| Target | What was observed? (ssys:targets) |
target.ttl |
Mars, Phobos, Moon, Mars Global Surveyor, C/2013 A1 (Siding Spring) |
| Target class | What kind of body? (ssys:target_class) |
target-class.ttl |
planet, satellite, comet, spacecraft (the PADC EPN terms) |
| Platform | Which spacecraft? (platform) |
platform.ttl |
Mars Reconnaissance Orbiter |
| Instrument | Which instrument? (instruments) |
instrument.ttl |
HiRISE, Context Camera |
| Mission | For which mission? (mission) |
mission.ttl |
Mars Reconnaissance Orbiter |
| Spectral band | Which channel? (bands[].name) |
band.ttl |
HiRISE RED, HiRISE NEAR-INFRARED, CTX broadband |
| CRS | In which reference system? (proj:code) |
crs.ttl |
IAU_2015:49901, IAU_2015:49902 |
| Wavelength type | Which spectral domain? (pdssp:wavelength_type) |
wavelength-type.ttl |
Optical, Infrared, UV (the IVOA messenger terms) |
Targets, platforms and instruments carry their NAIF ID (pdssp:naifId) and
skos:exactMatch to Wikidata and to the PDS4 context product (LID); a target points at its class
(pdssp:targetClass) and a satellite at the body it orbits
(pdssp:primaryBody, not skos:broader, so searching Mars does not return
Phobos). Target classes are the PADC EPN target-class terms one to one; their
skos:notation is the value written in the data. Source variants
("MARS RECONNAISSANCE ORBITER (MRO)", "HIRISE", the CTX misspelling "Jupitr")
are alt/hidden labels, resolved by the generated search/facets.json
normaliser. Every value's origin is listed in
docs/sources.md (and as dct:source on each scheme).
Bands are instrument channels (notation = bands[].name,
skos:broadMatch to the IVOA messenger vocabulary); CRS concepts are the IAU
2015 CRS of the OGC registry (notation = proj:code). The seven new schemes were seeded from the ODE STAC collections
(MRO HiRISE and CTX, 2026-10-18).
Reuses the USGS Thesaurus where it
genuinely fits (skos:exactMatch/skos:closeMatch back to the source
concept, confirmed by downloading and inspecting the real RDF/XML --
https://apps.usgs.gov/thesaurus/download/USGSThesaurus.rdf -- not
assumed from its web UI), and mints new PDSSP concepts for what a
planetary imaging archive needs that USGS doesn't have at all: Anaglyph,
Point cloud, Photogrammetry (USGS only has it as a synonym of "remote
sensing" itself, too coarse), Stereoscopic imaging, and Orthoimage (USGS
only has "orthoimagery" as a synonym of two different concepts, never
its own).
processing-level.ttl is built from a cross-system correspondence table
(EPN-TAP2 / CODMAC / ESA's Planetary Science Archive / NASA PDS / PDS3 /
PDS4 / ObsTAP) supplied by the project owner (2026-09-29). PDSSP only
models the four PDS4-aligned levels (Raw, Calibrated, Derived, Ancillary);
each carries its code in the other systems (pdssp:epntapLevel, ...). The
levels form an ordered scale (skos:OrderedCollection
pdssp:ProcessingLevelSequence), not a broader/narrower hierarchy.
Each data model's ontology rarely changes -- it's a fixed vocabulary. The mapping between two models changes far more often (a converter gets added, a column gets re-pointed at a different STAC path). Bundling both into one graph means every mapping tweak touches the "stable" vocabulary's own graph too, and makes it impossible to publish/query one vocabulary in isolation. So each lives in its own named graph, and a mapping graph (once one exists again) references the two vocabularies' real term/column IRIs rather than inventing stand-ins for them -- a genuine cross-reference, not a lookalike duplicate.
{base}/ itself is a plain catalogue page linking to each named graph's
own site -- never a merged/flattened ontology of its own. Each data
model's ontology stays independently dereferenceable; the catalogue is
pure navigation, not a second ontology.
https://pdssp.github.io/pdssp-ontology/vocab# -- already the pdssp:
prefix bound in ode-stac-proxy's own PROV vocabulary
(ode_stac_proxy.vocabulary) and already cited as
PDSSP_DATA_MODEL_SPEC["doc_url"] (ode_stac_proxy.pdssp_data_model).
A w3id.org/pdssp/... permanent redirect to this GitHub Pages site is
planned but not yet registered -- tracked as a follow-up.
uv run python -m pdssp_ontology.merge_ontology --base https://pdssp.github.io/pdssp-ontologyWrites development/ontology.trig (named-graph-aware; what Fuseki
loads), development/ontology.ttl (flattened single-file download of
everything -- not published as its own site), and one
development/<name>.ttl per named graph (thesaurus.ttl today). Pure
function of this repo's own source -- no network access needed.
Adding a second named graph later means: add its own vocab/<name>/*.ttl
(or a Python seed module, for a generated-from-a-spec vocabulary like
EPN-TAP), add one build_<name>_graph() call in merge_ontology.py's
build_dataset(), and add one Widoco step + one catalogue-page entry to
.github/workflows/publish-docs.yml -- each graph stays independently
dereferenceable, never merged into another's own file.
uv run pytestParses every vocab/thesaurus/*.ttl, checks every skos:Concept
declares skos:inScheme/skos:prefLabel, checks every
skos:broader/skos:narrower pair is mutually consistent, and pins the
per-scheme concept count.
docker compose up -d
uv run python -m pdssp_ontology.merge_ontology
uv run python -m pdssp_ontology.load_fuseki --username admin --password pdssp(ADMIN_PASSWORD in docker-compose.yml sets that password; the
stain/jena-fuseki image's default security config allows anonymous
SELECT queries but requires auth to write/read the Graph Store Protocol
/data endpoint load_fuseki uses.)
Exposes http://localhost:3030/pdssp/sparql (SPARQL 1.1 Protocol, no auth
needed for querying). GRAPH <.../thesaurus> { ... } scopes a query to
that graph; omitting GRAPH queries the union (there is only one graph
today, so this doesn't matter yet). Verified live during development:
curl -s -G "http://localhost:3030/pdssp/sparql" \
--data-urlencode 'query=PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
SELECT ?label WHERE {
GRAPH <https://pdssp.github.io/pdssp-ontology/thesaurus> {
?c a skos:Concept ; skos:prefLabel ?label
}
}' -H "Accept: application/sparql-results+json"returns all 26 concept labels.
Re-run merge_ontology then load_fuseki whenever any graph changes --
load_fuseki PUTs each named graph, replacing its prior content, so
this is safe to repeat.
.github/workflows/publish-docs.yml
rebuilds the ontology (a pure function of this repo's own source, no
network access needed), runs Widoco once per named graph, and publishes
the result to GitHub Pages. Runs on every push touching vocab/,
src/pdssp_ontology/, or the workflow itself, and on demand. Not yet run
for real (needs a push to main plus GitHub's own Widoco/Java toolchain
-- not exercised locally in this pass).
One-time setup: in this repo's Settings -> Pages, set "Source" to "GitHub Actions" -- the workflow can't do that part for you.
Not wired into either consumer yet -- this pass only builds, validates and SPARQL-serves the thesaurus itself. The intended integration (not yet implemented):
pds-geopackage-pipeline: each mission'smapping.pywould gain aTHEMESconstant (thethemesSTAC extension --properties.themeson an Item, top-levelthemeson a Collection;{"scheme": "<ConceptScheme IRI>", "concepts": [{"id": "<Concept IRI>", "title": "..."}]}, one Theme Object per scheme actually used) built from these concept URIs, plus a plain-stringproduct:typevalue (STAC's existing "product" extension) extracted from the same concept'sskos:prefLabel, kept for compatibility with anything only reading that property today.ode-stac-proxy: no plugin changes anticipated --themesis a passthrough STAC property already inside each Item/Collection's ownstac_item/collection_json, not something either store computes.
Not yet decided -- the USGS Thesaurus itself is public domain, and this repo's own additions are trivial factual/classificatory statements, but this should be picked explicitly (the deleted prototype's own catalogue page assumed CC-BY-4.0) rather than left unstated.