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.
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.
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.
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.
Related
- Creative Commons Licensing for GIS Datasets — the CC family and which identifier imposes which obligation
- Detecting CC Licence Versions in Legacy Metadata — finding out what an inherited catalogue actually says
- License Compatibility & Derivative Works — why a dedication never creates a conflict downstream
- Open Data License Selection & Comparison — choosing between CC0, CC-BY and the copyleft options