Languageen
3-create-stac-item-with-extension.ipynb

Create a STAC Item that Implements the EO Extension Using PySTAC

In the previous tutorial, you created a STAC Item and added it to a STAC Catalog. The item you created only implemented the core STAC Item specification. With extensions, we can record more information and add additional functionality to the item.

This tutorial builds off of the knowledge from the previous tutorial. Let's create an item that utilizes a common STAC Extension: the Electro-Optical (EO) Extension.

We will continue to use the data from the previous tutorial and will add the item we create into the STAC Catalog you just created.

Given that we know the tiff image we are working with is a WorldView-3 image that has earth observation data, we can enable the EO Extension to add band information.

Dependencies

If you need to install pystac, rasterio, or pystac, uncomment the lines below and run the cell.

In [ ]:
# ! pip install pystac
# ! pip install asterio
# ! pip install shapely

Import Packages and Store Data

To begin, import the packages and classes that you need to access data and work with STAC Items in Python.

In [ ]:
import os
import rasterio
import urllib.request
import pystac

from shapely.geometry import Polygon, mapping
from datetime import datetime, timezone
from pystac.extensions.eo import Band, EOExtension
from tempfile import TemporaryDirectory

Just like in the previous tutorial, we will set up a temporary directory and store the same image in it.

In [ ]:
# Set the temporary directory to store source data
tmp_dir = TemporaryDirectory()
img_path = os.path.join(tmp_dir.name, 'image.tif')

# Fetch and store data
url = ('https://spacenet-dataset.s3.amazonaws.com/'
       'spacenet/SN5_roads/train/AOI_7_Moscow/MS/'
       'SN5_roads_train_AOI_7_Moscow_MS_chip996.tif')
urllib.request.urlretrieve(url, img_path)

Define the Bands of WorldView-3

To add the EO information to an item we'll need to specify some more data. First, let's define the bands of WorldView-3. We have collected this band information from the WorldView-3 Data Sheet.

In [ ]:
wv3_bands = [Band.create(name='Coastal', description='Coastal: 400 - 450 nm', common_name='coastal'),
             Band.create(name='Blue', description='Blue: 450 - 510 nm', common_name='blue'),
             Band.create(name='Green', description='Green: 510 - 580 nm', common_name='green'),
             Band.create(name='Yellow', description='Yellow: 585 - 625 nm', common_name='yellow'),
             Band.create(name='Red', description='Red: 630 - 690 nm', common_name='red'),
             Band.create(name='Red Edge', description='Red Edge: 705 - 745 nm', common_name='rededge'),
             Band.create(name='Near-IR1', description='Near-IR1: 770 - 895 nm', common_name='nir08'),
             Band.create(name='Near-IR2', description='Near-IR2: 860 - 1040 nm', common_name='nir09')]

Notice that we used the .create method to create new band information.

Collect the Item's geometry and bbox

To get the bounding box and footprint of the image, we will utilize the get_bbox_and_footprint function we first used in the Create a STAC Catalog Tutorial.

In [ ]:
def get_bbox_and_footprint(raster):
    with rasterio.open(raster) as r:
        bounds = r.bounds
        bbox = [bounds.left, bounds.bottom, bounds.right, bounds.top]
        footprint = Polygon([
            [bounds.left, bounds.bottom],
            [bounds.left, bounds.top],
            [bounds.right, bounds.top],
            [bounds.right, bounds.bottom]
        ])
        
        return (bbox, mapping(footprint))
In [ ]:
bbox, footprint = get_bbox_and_footprint(img_path)

We can now create an item, enable the EO Extension, add the band information and add it to our catalog:

In [ ]:
eo_item = pystac.Item(id='local-image-eo',
                      geometry=footprint,
                      bbox=bbox,
                      datetime=datetime.utcnow(),
                      properties={})
eo = EOExtension.ext(eo_item, add_if_missing=True)
eo.apply(bands=wv3_bands)

There are also common metadata fields that we can use to capture additional information about the WorldView-3 imagery:

In [ ]:
eo_item.common_metadata.platform = "Maxar"
eo_item.common_metadata.instruments = ["WorldView3"]
eo_item.common_metadata.gsd = 0.3
In [ ]:
print(eo_item)

We can use the EO Extension to add bands to the assets we add to the item:

In [ ]:
asset = pystac.Asset(href=img_path, 
                     media_type=pystac.MediaType.GEOTIFF)
eo_item.add_asset("image", asset)
eo_on_asset = EOExtension.ext(eo_item.assets["image"])
eo_on_asset.apply(wv3_bands)

If we look at the asset's JSON representation, we can see the appropriate band indexes are set:

In [ ]:
asset.to_dict()

Let's create a catalog, add the item that uses the EO Extension, and save to a new STAC.

In [ ]:
catalog = pystac.Catalog(id='tutorial-catalog', 
                         description='This Catalog is a basic demonstration Catalog to teach the use of the eo extension.')
In [ ]:
catalog.add_item(eo_item)
list(catalog.get_items())
In [ ]:
catalog.normalize_and_save(root_href=os.path.join(tmp_dir.name, 'stac-eo'), 
                           catalog_type=pystac.CatalogType.SELF_CONTAINED)

Now, if we read the catalog from the filesystem, PySTAC recognizes that the item implements eo and to use its functionality, e.g. getting the bands off the asset:

In [ ]:
catalog2 = pystac.read_file(os.path.join(tmp_dir.name, 'stac-eo', 'catalog.json'))
In [ ]:
assert isinstance(catalog2, pystac.Catalog)
list(catalog2.get_items())
In [ ]:
item: pystac.Item = next(catalog2.get_all_items())
In [ ]:
assert EOExtension.has_extension(item)
In [ ]:
eo_on_asset = EOExtension.ext(item.assets["image"])
print(eo_on_asset.bands)

Cleanup

Don't forget to clean up the temporary directory.

In [ ]:
tmp_dir.cleanup()

Now you have created an Item with a STAC Extension. In the following tutorial, you will learn how to add a Collection to this STAC Catalog.

Join the conversation

If you have any questions, you’re welcome to ask our community on Gitter.