Skip to main content

Upload and view the data

With the OGC Definitions Service running and the uplifted Turtle file in hand, we can now load the data into the triplestore and browse it as linked data.

About named graphs

Fuseki stores RDF data in named graphs — separate, named partitions of the triplestore, each identified by a URI. Keeping each dataset in its own named graph makes it easy to update or remove it later without affecting other data, and allows SPARQL queries to target a specific subset of the data.

We will use the graph URI https://example.com/rainbow/graphs/indicators for this tutorial. You can choose any URI; the convention is to use one that reflects the content.

Uploading the data

We cover three direct ways to upload the file, plus a mention of higher-level OGC tooling. Option A (the Fuseki UI) is the easiest way to get started; Options B and C are more suitable for scripting or automation.

Option A: Fuseki admin interface

  1. Open http://localhost:3030 and log in.

  2. Select your dataset (e.g. fuseki) from the list.

  3. Click add data.

    Fuseki "add data" button

  4. In the Destination graph name field, enter: https://example.com/rainbow/graphs/indicators

  5. Upload cdi-indicator.ttl.

  6. Click upload now.

    Fuseki upload dialog

Option B: curl

curl -X PUT \
-u admin:${FUSEKI_PASSWORD} \
-H "Content-Type: text/turtle" \
--data-binary @cdi-indicator.ttl \
"http://localhost:3030/fuseki/data?graph=https://example.com/rainbow/graphs/indicators"

Set FUSEKI_PASSWORD to the value you configured in docker-compose.yml, or replace ${FUSEKI_PASSWORD} with the password directly.

This uses the SPARQL Graph Store Protocol PUT method, which replaces the entire named graph with the uploaded content. If you want to add triples to an existing graph instead of replacing it, use POST.

Option C: Python (requests + SPARQL Graph Store Protocol)

import os
import requests

FUSEKI_URL = "http://localhost:3030"
DATASET = "fuseki"
GRAPH_URI = "https://example.com/rainbow/graphs/indicators"
FUSEKI_PASSWORD = os.environ.get("FUSEKI_PASSWORD", "changeme")

with open("cdi-indicator.ttl") as f:
data = f.read()

response = requests.put(
f"{FUSEKI_URL}/{DATASET}/data",
params={"graph": GRAPH_URI},
headers={"Content-Type": "text/turtle"},
data=data,
auth=("admin", FUSEKI_PASSWORD),
)
response.raise_for_status()
print(f"Uploaded successfully (HTTP {response.status_code})")

Option D: OGC Naming Authority tools

The OGC Naming Authority provides higher-level tooling that automates publishing and registration of definitions. This approach will be covered in a dedicated tutorial.

Browsing the resource

Once the data is loaded, you can browse it in the Prez UI. To understand how the URL is constructed, let's trace it from the source:

  1. The indicator's id in the JSON document is indicators/cdi.
  2. During semantic uplift, base_uri (https://example.com/rainbow/) is prepended, giving the full resource URI:
    https://example.com/rainbow/indicators/cdi
  3. The nginx-ld REDIRECTS mapping (/rainbow/=https://example.com/rainbow/) means any request to http://localhost:8080/rainbow/... is treated as a request for the corresponding https://example.com/rainbow/... URI. So the resource is locally accessible at:
    http://localhost:8080/rainbow/indicators/cdi
Local testing configuration

If you are testing locally and did not replace the https://my.own.domain/ references in docker-compose.yml with http://localhost:8080/, navigating to the resource URL or following a linked data redirect will send your browser to https://my.own.domain/[...] instead of your local instance. Make sure the SYSTEM_URI, PREZ_UI_URL, and EXTERNAL_PREZ_BACKEND_URL environment variables are all set to http://localhost:8080/ before proceeding.

Navigate to that URL to see the Prez UI rendering of the resource.

Retrieving the linked data representation

You can also retrieve the RDF representation directly using curl. The -L flag is necessary because nginx-ld implements the linked data pattern: it inspects the Accept header and responds with an HTTP 303 redirect to the appropriate Prez endpoint. Without -L, curl would stop at the redirect and return nothing useful.

curl -L \
-H "Accept: text/turtle" \
"http://localhost:8080/rainbow/indicators/cdi"

Prez will return the resource serialized as Turtle RDF.

Registering in the Prez catalog

Browsing a resource by its URI works as soon as the data is in the triplestore. However, Prez also provides a data catalog view — a browsable hierarchy of all published resources. For a resource to appear there, it must be declared as an entry in a dcat:Catalogskos:ConceptSchemeskos:Concept hierarchy that Prez recognizes.

We will use the Prez default hierarchy OGC Block (ogc.prez.hierarchy.default) to define this scaffolding. Like the provenance block, it ships with a schema and a JSON-LD context, so you can use it straight away or inspect its definition to understand exactly what RDF it produces.

Creating the catalog document

Create a file called cdi-catalog.json. Each resource from the provenance chain becomes a skos:Concept entry, a skos:ConceptScheme groups them, and a dcat:Catalog wraps everything.

{
"id": "catalogs/rainbow",
"name": "Rainbow Catalog",
"items": [
{
"id": "schemes/drought-indicators",
"name": "Drought Indicators",
"concepts": [
{
"id": "indicators/cdi",
"name": "Composite Drought Indicator"
},
{
"id": "act/cdi-computation",
"name": "CDI Computation"
},
{
"id": "obs/soil-moisture-anomaly",
"name": "Soil Moisture Anomaly"
},
{
"id": "obs/rainfall-anomaly",
"name": "Rainfall Anomaly"
},
{
"id": "obs/vegetation-condition-anomaly",
"name": "Vegetation Condition Anomaly"
}
]
}
]
}

The id values for the concepts are the same ones used in cdi-indicator.json. After uplift, both documents produce triples for the same URIs, so the concept entries point at the resources already in the triplestore rather than creating new, duplicate ones.

Uplifting and uploading

The process follows the same validate → uplift → upload pattern, with two differences: the Prez Blocks register is used instead of the provenance one, and the data goes into a dedicated catalog graph. Here we also skip writing an intermediate Turtle file and pass the serialized RDF directly to the upload request. Keeping the catalog in a separate graph is important because PUT replaces the entire named graph on each upload; if the catalog and the provenance data shared a graph, updating one would overwrite the other.

from ogc.bblocks.register import load_register
from ogc.bblocks.validate import validate_json
from ogc.bblocks.semantic_uplift import uplift_json
import json, requests, os

# Load the Prez Hierarchy block
register = load_register(
"https://ogcincubator.github.io/bblocks-prez/build/register.json"
)
bblock = register.get_item_full("ogc.prez.hierarchy.default")

with open("cdi-catalog.json") as f:
catalog = json.load(f)

result = validate_json(bblock, catalog)
result.raise_for_invalid()
print("Validation passed!")

rdf_graph = uplift_json(bblock, catalog, base_uri='https://example.com/rainbow/')
data = rdf_graph.serialize()
print("Uplift complete")

FUSEKI_URL = "http://localhost:3030"
DATASET = "fuseki"
GRAPH_URI = "https://example.com/rainbow/graphs/catalog"
FUSEKI_PASSWORD = os.environ.get("FUSEKI_PASSWORD", "changeme")

response = requests.put(
f"{FUSEKI_URL}/{DATASET}/data",
params={"graph": GRAPH_URI},
headers={"Content-Type": "text/turtle"},
data=data,
auth=("admin", FUSEKI_PASSWORD),
)
response.raise_for_status()
print(f"Catalog uploaded (HTTP {response.status_code})")

Once uploaded, the Composite Drought Indicator and its associated resources will appear under Rainbow Catalog in the Prez catalog view at http://localhost:8080.

Summary

You have successfully:

  1. Deployed the OGC Definitions Service locally
  2. Described the Composite Drought Indicator as a provenance chain
  3. Validated and uplifted the document with bblocks-client-python
  4. Uploaded the data to Fuseki and browsed it as linked data
  5. Defined the Prez catalog scaffolding and made the resources discoverable in the catalog view

The indicator is now published and accessible as a dereferenceable linked data resource, and discoverable through the Prez data catalog.

Moving to real-world URIs

Throughout this tutorial we have used https://example.com/rainbow/ as a placeholder URI namespace. For a real deployment, you would replace this with a namespace you actually control, so that the URIs you publish are stable and dereferenceable on the public web.

There are two common approaches:

  • Use a domain you control. If your organization owns a domain, you can dedicate a path on it to your definitions (e.g. https://yourdomain.example.org/rainbow/). You configure your OGC Definitions Service at that address and set the nginx-ld REDIRECTS accordingly. The URIs you mint will then resolve directly to your service.

  • Use a persistent URI redirection service. Services such as w3id.org provide stable, community-governed URI namespaces that redirect to a URL of your choice. This is useful when you do not control a suitable domain, or when you want the URIs to remain stable even if the underlying service moves. You register a namespace (e.g. https://w3id.org/your-namespace/rainbow/), configure it to redirect to your deployment, and use that namespace as the base_uri in the uplift step.

In both cases, the only things that change are the base_uri passed to uplift_json and the corresponding entries in docker-compose.yml. The structure of the data and the rest of this tutorial remain exactly the same.