Skip to content

Extensions ​

A STAC extension adds prefixed keys to properties: eo:cloud_cover, proj:code, sat:orbit_state. Here an extension is a plain struct whose field names are the keys after the prefix, plus two one-line methods.

Six ship, and they are what a read parses eagerly when the caller names none:

StructPrefixCovers
EOeocloud and snow fractions
Projectionprojthe native grid: code, epsg, wkt2, shape, transform
STAC.Rasterrasterhow pixel values map to the quantity they stand for
Satsatwhich satellite, and where on its orbit
Viewviewsensor and sun angles
Scientificscihow to cite the data

STAC.Raster is not exported: Rasters.jl exports a Raster of its own, and Raster(asset) opening a COG is the call that matters in a session holding both.

Three ways to the same struct ​

julia
julia> examples = joinpath(pkgdir(STAC), "test", "fixtures", "static", "self-contained");

julia> item = STAC.read(joinpath(examples, "simple-collection", "extended-item.json"));

julia> item.extensions.eo.cloud_cover           # the eager field: one `Float64` load
1.2

julia> get(item, STAC.EO).cloud_cover           # the same, without knowing the type parameter
1.2

julia> STAC.EO(item).cloud_cover                # the same again, throwing when absent
1.2

julia> get(item, STAC.Sat) === nothing          # this item carries no `sat:` key
true
WayReads fromUse when
item.extensions.eothe eager fieldthe extension was named in extensions =; type stable, and the path a --trim=safe program sees
get(item, STAC.EO)that field when it exists, else properties.otherthe code does not know whether this item carries it
STAC.EO(item)the same as get, raising a STAC.MissingExtension instead of reporting nothingthe extension is required for what follows

An extension outside the parsed set is not lost. Its keys stay on the tail, and get finds them there:

julia
julia> narrow = STAC.read(joinpath(examples, "simple-collection", "extended-item.json");
                          extensions = (STAC.EO,));

julia> narrow.extensions.eo.cloud_cover         # the one eager field
1.2

julia> get(narrow, STAC.View).off_nadir         # read out of `properties.other`
3.8

The one thing that does erase them is metadata = false, which drops the tail during the parse: the keys were skipped, not absent from the document.

Declaring is not carrying ​

stac_extensions says which extensions a producer claims; the keys say which ones are actually there. Producers get both wrong in both directions, so the two questions have two functions.

julia
julia> STAC.declares(item, STAC.EO)             # the schema URI is in `stac_extensions`
true

julia> STAC.declares(item, STAC.Sat)
false

julia> STAC.declares(item, STAC.schema(STAC.EO))    # the same question, asked by URI
true

The version segment of a schema URI is ignored, as pystac does, so an item declaring eo/v1.1.0 declares EO even though this package types the 2.0.0 fields.

Assets and bands carry them too ​

proj: keys on an asset override the item's, which is how a producer says that one band sits on a different grid from the rest.

julia
julia> proj = STAC.read(joinpath(pkgdir(STAC), "test", "fixtures", "stac-spec",
                                 "extensions-collection", "proj-example", "proj-example.json"));

julia> STAC.Projection(proj).shape              # the item's grid
2-element Vector{Int64}:
 8391
 8311

julia> STAC.Projection(proj.assets["B8"]).shape         # the panchromatic band's own
2-element Vector{Int64}:
 16781
 16621

julia> get(proj.assets["B1"], STAC.Projection) === nothing    # this one states none
true

This is what RasterStack reads to refuse a stack of mixed resolutions.

Writing your own ​

An extension is a struct, a prefix, and a schema. Every field is Union{…,Nothing}, because the struct is built from whichever keys a tail carries and an absent key has to be representable.

The catalog the examples on this page read declares the Remote Data extension, which this package ships no struct for. Five lines give it one:

julia
julia> struct RemoteData <: STAC.Extension
           type::Union{String,Nothing}
           sat_id::Union{String,Nothing}
           product_level::Union{String,Nothing}
           anomalous_pixels::Union{Float64,Nothing}
           earth_sun_distance::Union{Float64,Nothing}
       end

julia> STAC.prefix(::Type{RemoteData}) = "rd";

julia> STAC.schema(::Type{RemoteData}) =
           "https://stac-extensions.github.io/remote-data/v1.0.0/schema.json";

That is enough for the two lazy paths, on items you have already read:

julia
julia> RemoteData(item).sat_id
"cool_sat2"

julia> STAC.declares(item, RemoteData)
true

Naming it in extensions = makes it an eager field, on equal footing with the six that ship:

julia
julia> typed = STAC.read(joinpath(examples, "simple-collection", "extended-item.json");
                         extensions = (STAC.EO, STAC.Projection, RemoteData));

julia> typed.extensions.rd.sat_id, typed.extensions.rd.anomalous_pixels
("cool_sat2", 0.14)

julia> collect(keys(typed.properties.other))    # what is left on the tail
5-element Vector{String}:
 "statistics"
 "view:sun_elevation"
 "view:off_nadir"
 "view:sun_azimuth"
 "sci:doi"

A field whose value is a nested object or an array of them stays on the tail rather than getting a typed slot: raster:histogram, raster:bands, sat:orbit_state_vectors, and sci:publications are the four in the shipped set. They round-trip through STAC.json from there.

Exploring a catalog you have not seen ​

extensions = () parses nothing eagerly and gives an Item{Any}, where every prefixed key is on the tail and every access path but the eager field still works.

julia
julia> raw = STAC.read(joinpath(examples, "simple-collection", "extended-item.json");
                       extensions = ());

julia> typeof(raw).parameters[1]
Any

julia> length(collect(keys(raw.properties.other)))
15

julia> get(raw, STAC.EO).cloud_cover
1.2

This is the form for a corpus that mixes extension sets, and the one to reach for when deciding which structs are worth declaring. It costs a dictionary lookup and a lift per access, where an eager field costs neither.