Applying CC0 to Public Agency Reference Data

Apply CC0-1.0 to reference geographies — administrative boundaries, address points, elevation grids, gazetteers — because the dedication removes every downstream friction on data whose value is that it is used as a common denominator, and record the intent explicitly so nobody later reintroduces conditions the agency never meant to impose.

Reference data is the category where licensing choices do the most damage per clause. A boundary layer is not a product; it is the coordinate system in which other people’s products are expressed, and every condition attached to it propagates into all of them. An attribution requirement on a national boundary set means every map, report and derived dataset that uses it inherits a notice obligation, which for a widely used layer is thousands of downstream obligations to enforce something nobody intends to enforce. This guide sits under Creative Commons licensing for GIS datasets, within Geospatial Data Licensing & Compliance Fundamentals.

Whether an agency dataset is reference dataThree tests identify the datasets for which a public domain dedication is the appropriate licence.Do others express their data interms of it?boundaries, addresses, gridsReference dataCC0-1.0 is appropriateyesnoIs it an authoritative register?the definitive list of somethingReference dataconditions impede its purposeyesnoIs it an analysis or a product?a risk model, a styled mapNot reference dataCC-BY may be rightyesnoProbably reference datadefault open for public agency output
Reference data is defined by how it is used, not by who made it or how much it cost.

Automated Python Implementation

The script below applies a CC0 dedication across a directory of agency datasets, writing the identifier into every carrier the format supports and emitting a record of what was applied to what. It deliberately refuses to overwrite a dataset that already carries a different licence, because relicensing is a decision and not a batch operation.

#!/usr/bin/env python3
"""Apply a CC0-1.0 dedication across a directory of agency reference datasets."""
import json
import pathlib
import sqlite3

from osgeo import gdal, ogr

gdal.UseExceptions()

CC0 = "CC0-1.0"
DEDICATION = (
    "Dedicated to the public domain under CC0 1.0. "
    "No rights reserved. Citation is appreciated but not required."
)

def existing_licence(path):
    """Read whatever licence the dataset already declares, if any."""
    ds = gdal.OpenEx(str(path))
    try:
        meta = ds.GetMetadata() or {}
    finally:
        ds = None
    for key in ("LICENSE", "License", "license", "TIFFTAG_COPYRIGHT"):
        if meta.get(key):
            return meta[key]
    return None

def apply_to_geopackage(path):
    """Write the dedication into gpkg_metadata and attach it to the file."""
    con = sqlite3.connect(str(path))
    try:
        con.execute(
            "CREATE TABLE IF NOT EXISTS gpkg_metadata ("
            "id INTEGER PRIMARY KEY ASC NOT NULL, md_scope TEXT NOT NULL DEFAULT 'dataset',"
            " md_standard_uri TEXT NOT NULL, mime_type TEXT NOT NULL DEFAULT 'text/xml',"
            " metadata TEXT NOT NULL DEFAULT '')"
        )
        con.execute(
            "INSERT INTO gpkg_metadata (md_scope, md_standard_uri, mime_type, metadata)"
            " VALUES (?, ?, ?, ?)",
            ("dataset", "http://www.isotc211.org/2005/gmd", "application/json",
             json.dumps({"license": CC0, "rights": DEDICATION})),
        )
        con.commit()
    finally:
        con.close()

def apply_to_raster(path):
    ds = gdal.Open(str(path), gdal.GA_Update)
    try:
        ds.SetMetadataItem("TIFFTAG_COPYRIGHT", DEDICATION)
        ds.SetMetadataItem("LICENSE", CC0)
    finally:
        ds = None

def dedicate(root):
    applied, skipped = [], []
    for path in sorted(pathlib.Path(root).rglob("*")):
        if path.suffix.lower() not in {".gpkg", ".tif", ".tiff"}:
            continue
        current = existing_licence(path)
        if current and CC0 not in current:
            skipped.append({"path": str(path), "existing": current})
            continue
        if path.suffix.lower() == ".gpkg":
            apply_to_geopackage(path)
        else:
            apply_to_raster(path)
        applied.append(str(path))
    return {"applied": applied, "skipped_existing_licence": skipped}

if __name__ == "__main__":
    import sys
    print(json.dumps(dedicate(sys.argv[1]), indent=2))

The skipped_existing_licence list is the important output. A directory of agency data almost always contains a few layers that came from somewhere else — a purchased basemap, a partner’s contribution, a layer derived from a licensed source — and an agency cannot dedicate to the public domain rights it does not hold. Producing that list as a first-class result turns the run into an inventory of exactly the datasets that need a rights decision.

What CC0 removes that CC-BY does notComparison of CC0-1.0 and CC-BY-4.0 for reference data across four downstream frictions.CC0-1.0CC-BY-4.0Notice in every derived productnot requiredrequiredNotice in a rendered mapnot requiredrequiredCombining with any otherlicencenever conflictsnever conflictsTracking obligationsdownstreamnothing to trackper-source trackingAgency named in practiceusually, by conventionyes, by obligation
Every row is a cost borne by every downstream user, forever, in exchange for a credit the agency usually receives anyway.

Validation and Pipeline Integration

Two checks matter after a dedication run, and the second is the one that protects the agency.

# Every dedicated dataset declares the identifier
python -c "
import pathlib, json, sqlite3
for p in pathlib.Path('reference').rglob('*.gpkg'):
    con = sqlite3.connect(str(p))
    rows = con.execute('SELECT metadata FROM gpkg_metadata').fetchall()
    con.close()
    ids = {json.loads(r[0]).get('license') for r in rows}
    print(p.name, sorted(i for i in ids if i))
"
def test_third_party_layers_are_never_dedicated():
    """A layer carrying someone else's licence must appear in the skipped list."""
    result = dedicate("fixtures/mixed_rights")
    skipped = {item["path"] for item in result["skipped_existing_licence"]}
    assert any("purchased_basemap" in p for p in skipped)
    assert not any("purchased_basemap" in p for p in result["applied"])

That test is the one to write first. The risk in an automated dedication is not that it fails to apply CC0 — that is visible immediately — but that it applies CC0 to something the agency had no right to dedicate, which is invisible until a vendor notices.

Recording the Intent, Not Just the Identifier

An SPDX identifier in a metadata field is a fact about the dataset. It is not a record of the decision, and reference data outlives the people who made the decision by a wide margin.

What a dedication decision record containsScope, authority, reasoning and exclusions, each answering a question the licence field cannot.Exclusionsthe valuable partthird-party layerspurchased datapartner contributionsReasoningreference geographyfrictionless reusestatutory duty or policyAuthoritydecided bydecided onScopethe series, not a releasewhich directories
The exclusions band is the one a future team most needs and is least able to reconstruct.

The gap shows up in a predictable way. Some years after the dedication, a new team inherits the catalogue, finds CC0-1.0 on the boundary layers, and cannot tell whether that was a considered policy or a default someone applied in a batch job. The safe-looking response is to add an attribution requirement “to be on the safe side”, which quietly imposes an obligation on every downstream user of a layer that thousands of products already depend on.

A short decision record next to the data prevents it: what was dedicated, on whose authority, on what date, and the reasoning — that these are reference geographies whose utility depends on frictionless reuse. Three sentences. The same record should name the categories explicitly excluded from the dedication, because the exclusions are the part that a future team most needs and is least able to reconstruct.

Where an agency operates under a statutory openness duty, cite it in the record. Where it does not, say that the dedication was a discretionary policy choice, because a future reader will otherwise assume a legal basis that does not exist and may hesitate to extend the same treatment to new datasets.

Long-Term Compliance Best Practices

  • Dedicate the layer, not the release. A dedication that applies to “the 2026 boundary set” leaves the 2027 set undecided and re-opens the question annually. State that the dedication applies to the series.
  • Keep the courtesy citation request in documentation. CC0 removes the obligation; nothing stops the agency asking to be cited, and most users will. Putting the request in the licence field, however, records a condition that does not exist.
  • Never mix a dedication with a disclaimer of accuracy in the same field. They are different statements and downstream parsers read the licence field as an identifier. Put the disclaimer in the metadata’s limitation element where it belongs.
  • Re-run the dedication after every ingest. New files arrive without metadata, and a dedication applied once describes the directory as it was that day.
  • Publish the exclusion list. Users who know which layers are not CC0 will not assume the whole catalogue is, which prevents exactly the accidental redistribution the exclusions exist to avoid.