Example #1
0
    def test_null_datetime(self) -> None:
        item = pystac.Item.from_file(
            TestCases.get_path("data-files/item/sample-item.json"))

        with self.assertRaises(pystac.STACError):
            Item(
                "test",
                geometry=item.geometry,
                bbox=item.bbox,
                datetime=None,
                properties={},
            )

        null_dt_item = Item(
            "test",
            geometry=item.geometry,
            bbox=item.bbox,
            datetime=None,
            properties={
                "start_datetime": datetime_to_str(get_opt(item.datetime)),
                "end_datetime": datetime_to_str(get_opt(item.datetime)),
            },
        )

        null_dt_item.validate()
Example #2
0
 def anx_datetime(self, v: Optional[RangeSummary[Datetime]]) -> None:
     self._set_summary(
         ANX_DATETIME_PROP,
         map_opt(
             lambda s: RangeSummary(datetime_to_str(s.minimum),
                                    datetime_to_str(s.maximum)),
             v,
         ),
     )
Example #3
0
    def test_update_extents(self) -> None:

        catalog = TestCases.test_case_2()
        base_collection = catalog.get_child(
            "1a8c1632-fa91-4a62-b33e-3a87c2ebdf16")
        assert isinstance(base_collection, Collection)
        base_extent = base_collection.extent
        collection = base_collection.clone()

        item1 = Item(
            id="test-item-1",
            geometry=ARBITRARY_GEOM,
            bbox=[-180, -90, 180, 90],
            datetime=TEST_DATETIME,
            properties={"key": "one"},
            stac_extensions=["eo", "commons"],
        )

        item2 = Item(
            id="test-item-1",
            geometry=ARBITRARY_GEOM,
            bbox=[-180, -90, 180, 90],
            datetime=None,
            properties={
                "start_datetime":
                datetime_to_str(datetime(2000, 1, 1, 12, 0, 0, 0)),
                "end_datetime":
                datetime_to_str(datetime(2000, 2, 1, 12, 0, 0, 0)),
            },
            stac_extensions=["eo", "commons"],
        )

        collection.add_item(item1)

        collection.update_extent_from_items()
        self.assertEqual([[-180, -90, 180, 90]],
                         collection.extent.spatial.bboxes)
        self.assertEqual(len(base_extent.spatial.bboxes[0]),
                         len(collection.extent.spatial.bboxes[0]))

        self.assertNotEqual(base_extent.temporal.intervals,
                            collection.extent.temporal.intervals)
        collection.remove_item("test-item-1")
        collection.update_extent_from_items()
        self.assertNotEqual([[-180, -90, 180, 90]],
                            collection.extent.spatial.bboxes)
        collection.add_item(item2)

        collection.update_extent_from_items()

        self.assertEqual(
            [[
                item2.common_metadata.start_datetime,
                base_extent.temporal.intervals[0][1],
            ]],
            collection.extent.temporal.intervals,
        )
Example #4
0
 def unpublished(self, v: Optional[RangeSummary[datetime]]) -> None:
     self._set_summary(
         UNPUBLISHED_PROP,
         map_opt(
             lambda s: RangeSummary(datetime_to_str(s.minimum),
                                    datetime_to_str(s.maximum)),
             v,
         ),
     )
Example #5
0
 def expires(self, v: Optional[RangeSummary[datetime]]) -> None:
     self._set_summary(
         EXPIRES_PROP,
         map_opt(
             lambda s: RangeSummary(datetime_to_str(s.minimum),
                                    datetime_to_str(s.maximum)),
             v,
         ),
     )
Example #6
0
    def set_created(self, created, asset=None):
        """Set an Item or an Asset created time.

        If an Asset is supplied, sets the property on the Asset.
        Otherwise sets the Item's value.
        """
        if asset is None:
            self.properties['created'] = datetime_to_str(created)
        else:
            asset.properties['created'] = datetime_to_str(created)
Example #7
0
    def set_end_datetime(self, end_datetime, asset=None):
        """Set an Item or an Asset end_datetime.

        If an Asset is supplied, sets the property on the Asset.
        Otherwise sets the Item's value.
        """
        if asset is None:
            self.properties['end_datetime'] = datetime_to_str(end_datetime)
        else:
            asset.properties['end_datetime'] = datetime_to_str(end_datetime)
Example #8
0
    def test_from_items(self) -> None:
        item1 = Item(
            id="test-item-1",
            geometry=ARBITRARY_GEOM,
            bbox=[-10, -20, 0, -10],
            datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC),
            properties={},
        )

        item2 = Item(
            id="test-item-2",
            geometry=ARBITRARY_GEOM,
            bbox=[0, -9, 10, 1],
            datetime=None,
            properties={
                "start_datetime":
                datetime_to_str(
                    datetime(2000, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC)),
                "end_datetime":
                datetime_to_str(
                    datetime(2000, 7, 1, 12, 0, 0, 0, tzinfo=tz.UTC)),
            },
        )

        item3 = Item(
            id="test-item-2",
            geometry=ARBITRARY_GEOM,
            bbox=[-5, -20, 5, 0],
            datetime=None,
            properties={
                "start_datetime":
                datetime_to_str(
                    datetime(2000, 12, 1, 12, 0, 0, 0, tzinfo=tz.UTC)),
                "end_datetime":
                datetime_to_str(
                    datetime(2001, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC), ),
            },
        )

        extent = Extent.from_items([item1, item2, item3])

        self.assertEqual(len(extent.spatial.bboxes), 1)
        self.assertEqual(extent.spatial.bboxes[0], [-10, -20, 10, 1])

        self.assertEqual(len(extent.temporal.intervals), 1)
        interval = extent.temporal.intervals[0]

        self.assertEqual(interval[0],
                         datetime(2000, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC))
        self.assertEqual(interval[1],
                         datetime(2001, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC))
Example #9
0
def stac(
    input,
    input_datetime,
    extension,
    collection,
    property,
    id,
    asset_name,
    asset_href,
    asset_mediatype,
    output,
):
    """Rasterio stac cli."""
    property = property or {}

    if not input_datetime:
        input_datetime = datetime.datetime.utcnow()
    else:
        if "/" in input_datetime:
            start_datetime, end_datetime = input_datetime.split("/")
            property["start_datetime"] = datetime_to_str(
                str_to_datetime(start_datetime))
            property["end_datetime"] = datetime_to_str(
                str_to_datetime(end_datetime))
            input_datetime = None
        else:
            input_datetime = str_to_datetime(input_datetime)

    if asset_mediatype and asset_mediatype != "auto":
        asset_mediatype = MediaType[asset_mediatype]

    extensions = [e for e in extension if e]

    item = create_stac_item(
        input,
        input_datetime=input_datetime,
        extensions=extensions,
        collection=collection,
        properties=property,
        id=id,
        asset_name=asset_name,
        asset_href=asset_href,
        asset_media_type=asset_mediatype,
    )

    if output:
        with open(output, "w") as f:
            f.write(json.dumps(item.to_dict(), separators=(",", ":")))
    else:
        click.echo(json.dumps(item.to_dict(), separators=(",", ":")))
Example #10
0
    def test_update_extents(self):

        catalog = TestCases.test_case_2()
        base_collection = catalog.get_child(
            '1a8c1632-fa91-4a62-b33e-3a87c2ebdf16')
        base_extent = base_collection.extent
        collection = base_collection.clone()

        item1 = Item(id='test-item-1',
                     geometry=RANDOM_GEOM,
                     bbox=[-180, -90, 180, 90],
                     datetime=TEST_DATETIME,
                     properties={'key': 'one'},
                     stac_extensions=['eo', 'commons'])

        item2 = Item(id='test-item-1',
                     geometry=RANDOM_GEOM,
                     bbox=[-180, -90, 180, 90],
                     datetime=None,
                     properties={
                         'start_datetime':
                         datetime_to_str(datetime(2000, 1, 1, 12, 0, 0, 0)),
                         'end_datetime':
                         datetime_to_str(datetime(2000, 2, 1, 12, 0, 0, 0))
                     },
                     stac_extensions=['eo', 'commons'])

        collection.add_item(item1)

        collection.update_extent_from_items()
        self.assertEqual([[-180, -90, 180, 90]],
                         collection.extent.spatial.bboxes)
        self.assertEqual(len(base_extent.spatial.bboxes[0]),
                         len(collection.extent.spatial.bboxes[0]))

        self.assertNotEqual(base_extent.temporal.intervals,
                            collection.extent.temporal.intervals)
        collection.remove_item('test-item-1')
        collection.update_extent_from_items()
        self.assertNotEqual([[-180, -90, 180, 90]],
                            collection.extent.spatial.bboxes)
        collection.add_item(item2)

        collection.update_extent_from_items()

        self.assertEqual([[
            item2.common_metadata.start_datetime,
            base_extent.temporal.intervals[0][1]
        ]], collection.extent.temporal.intervals)
Example #11
0
    def to_dict(self, include_self_link=True):
        links = self.links
        if not include_self_link:
            links = filter(lambda x: x.rel != 'self', links)

        assets = dict(map(lambda x: (x[0], x[1].to_dict()), self.assets.items()))

        self.properties['datetime'] = datetime_to_str(self.datetime)

        d = {
            'type': 'Feature',
            'stac_version': STAC_VERSION,
            'id': self.id,
            'properties': self.properties,
            'geometry': self.geometry,
            'bbox': self.bbox,
            'links': [l.to_dict() for l in links],
            'assets': assets
        }

        if self.stac_extensions is not None:
            d['stac_extensions'] = self.stac_extensions

        if self.collection_id:
            d['collection'] = self.collection_id

        return deepcopy(d)
Example #12
0
    def to_dict(self, include_self_link=True):
        links = self.links
        if not include_self_link:
            links = filter(lambda x: x.rel != 'self', links)

        assets = dict(map(lambda x: (x[0], x[1].to_dict()), self.assets.items()))

        if self.datetime is not None:
            self.properties['datetime'] = datetime_to_str(self.datetime)
        else:
            self.properties['datetime'] = None

        d = {
            'type': 'Feature',
            'stac_version': pystac.get_stac_version(),
            'id': self.id,
            'properties': self.properties,
            'geometry': self.geometry,
            'links': [link.to_dict() for link in links],
            'assets': assets
        }

        if self.bbox is not None:
            d['bbox'] = self.bbox

        if self.stac_extensions is not None:
            d['stac_extensions'] = self.stac_extensions

        if self.collection_id:
            d['collection'] = self.collection_id

        for key in self.extra_fields:
            d[key] = self.extra_fields[key]

        return deepcopy(d)
Example #13
0
    def test_from_items(self):
        item1 = Item(id='test-item-1',
                     geometry=RANDOM_GEOM,
                     bbox=[-10, -20, 0, -10],
                     datetime=datetime(2000, 2, 1, 12, 0, 0, 0, tzinfo=tz.UTC),
                     properties={})

        item2 = Item(id='test-item-2',
                     geometry=RANDOM_GEOM,
                     bbox=[0, -9, 10, 1],
                     datetime=None,
                     properties={
                         'start_datetime':
                         datetime_to_str(
                             datetime(2000, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC)),
                         'end_datetime':
                         datetime_to_str(
                             datetime(2000, 7, 1, 12, 0, 0, 0, tzinfo=tz.UTC))
                     })

        item3 = Item(id='test-item-2',
                     geometry=RANDOM_GEOM,
                     bbox=[-5, -20, 5, 0],
                     datetime=None,
                     properties={
                         'start_datetime':
                         datetime_to_str(
                             datetime(2000, 12, 1, 12, 0, 0, 0,
                                      tzinfo=tz.UTC)),
                         'end_datetime':
                         datetime_to_str(
                             datetime(2001, 1, 1, 12, 0, 0, 0,
                                      tzinfo=tz.UTC), )
                     })

        extent = Extent.from_items([item1, item2, item3])

        self.assertEqual(len(extent.spatial.bboxes), 1)
        self.assertEqual(extent.spatial.bboxes[0], [-10, -20, 10, 1])

        self.assertEqual(len(extent.temporal.intervals), 1)
        interval = extent.temporal.intervals[0]

        self.assertEqual(interval[0],
                         datetime(2000, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC))
        self.assertEqual(interval[1],
                         datetime(2001, 1, 1, 12, 0, 0, 0, tzinfo=tz.UTC))
Example #14
0
 def _timestamp_setter(self, timestamp, key, asset=None):
     if timestamp is None:
         self.item.properties[key] = timestamp
     else:
         timestamp_str = datetime_to_str(timestamp)
         if asset is not None:
             asset.properties[key] = timestamp_str
         else:
             self.item.properties[key] = timestamp_str
Example #15
0
    def set_datetime(self,
                     datetime: Datetime,
                     asset: Optional[Asset] = None) -> None:
        """Set an Item or an Asset datetime.

        If an Asset is supplied, sets the property on the Asset.
        Otherwise sets the Item's value.
        """
        if asset is None:
            self.datetime = datetime
        else:
            asset.extra_fields["datetime"] = datetime_to_str(datetime)
Example #16
0
def _convert_value_to_stac_type(key: str, value):
    """
    Convert return type as per STAC specification
    """
    # In STAC spec, "instruments" have [String] type
    if key == "eo:instrument":
        return _as_stac_instruments(value)
    # Convert the non-default datetimes to a string
    elif isinstance(value, datetime.datetime) and key != "datetime":
        return datetime_to_str(value)
    else:
        return value
Example #17
0
    def to_dict(self):
        """Generate a dictionary representing the JSON of this TemporalExtent.

        Returns:
            dict: A serializion of the TemporalExtent that can be written out as JSON.
        """
        encoded_intervals = []
        for i in self.intervals:
            start = None
            end = None

            if i[0]:
                start = datetime_to_str(i[0])

            if i[1]:
                end = datetime_to_str(i[1])

            encoded_intervals.append([start, end])

        d = {'interval': encoded_intervals}
        return deepcopy(d)
Example #18
0
    def to_dict(self) -> Dict[str, Any]:
        """Generate a dictionary representing the JSON of this TemporalExtent.

        Returns:
            dict: A serialization of the TemporalExtent that can be written out as JSON.
        """
        encoded_intervals: List[List[Optional[str]]] = []
        for i in self.intervals:
            start = None
            end = None

            if i[0] is not None:
                start = datetime_to_str(i[0])

            if i[1] is not None:
                end = datetime_to_str(i[1])

            encoded_intervals.append([start, end])

        d = {"interval": encoded_intervals, **self.extra_fields}
        return d
Example #19
0
    def test_datetime_to_str(self):
        cases = (
            ('timezone naive, assume utc', datetime(2000, 1, 1), '2000-01-01T00:00:00Z'),
            ('timezone aware, utc', datetime(2000, 1, 1,
                                             tzinfo=timezone.utc), '2000-01-01T00:00:00Z'),
            ('timezone aware, utc -7', datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=-7))),
             '2000-01-01T00:00:00-07:00'),
        )

        for title, dt, expected in cases:
            with self.subTest(title=title):
                got = utils.datetime_to_str(dt)
                self.assertEqual(expected, got)
Example #20
0
    def test_anx_datetime(self) -> None:
        collection = self.collection()
        summaries_ext = SatExtension.summaries(collection, True)
        anx_datetime_range = RangeSummary(
            str_to_datetime("2020-01-01T00:00:00.000Z"),
            str_to_datetime("2020-01-02T00:00:00.000Z"),
        )

        summaries_ext.anx_datetime = anx_datetime_range

        self.assertEqual(
            summaries_ext.anx_datetime,
            anx_datetime_range,
        )

        summaries_dict = collection.to_dict()["summaries"]

        self.assertDictEqual(
            summaries_dict["sat:anx_datetime"],
            {
                "minimum": datetime_to_str(anx_datetime_range.minimum),
                "maximum": datetime_to_str(anx_datetime_range.maximum),
            },
        )
Example #21
0
    def test_unpublished(self) -> None:
        collection = self.collection()
        summaries_ext = TimestampsExtension.summaries(collection, True)
        unpublished_range = RangeSummary(
            str_to_datetime("2020-01-01T00:00:00.000Z"),
            str_to_datetime("2020-01-02T00:00:00.000Z"),
        )

        summaries_ext.unpublished = unpublished_range

        self.assertEqual(
            summaries_ext.unpublished,
            unpublished_range,
        )

        summaries_dict = collection.to_dict()["summaries"]

        self.assertDictEqual(
            summaries_dict["unpublished"],
            {
                "minimum": datetime_to_str(unpublished_range.minimum),
                "maximum": datetime_to_str(unpublished_range.maximum),
            },
        )
Example #22
0
    def to_dict(self,
                include_self_link: bool = True,
                transform_hrefs: bool = True) -> Dict[str, Any]:
        links = self.links
        if not include_self_link:
            links = [x for x in links if x.rel != pystac.RelType.SELF]

        assets = {k: v.to_dict() for k, v in self.assets.items()}

        if self.datetime is not None:
            self.properties["datetime"] = datetime_to_str(self.datetime)
        else:
            self.properties["datetime"] = None

        d: Dict[str, Any] = {
            "type":
            "Feature",
            "stac_version":
            pystac.get_stac_version(),
            "id":
            self.id,
            "properties":
            self.properties,
            "geometry":
            self.geometry,
            "links":
            [link.to_dict(transform_href=transform_hrefs) for link in links],
            "assets":
            assets,
        }

        if self.bbox is not None:
            d["bbox"] = self.bbox

        if self.stac_extensions is not None:
            d["stac_extensions"] = self.stac_extensions

        if self.collection_id:
            d["collection"] = self.collection_id

        for key in self.extra_fields:
            d[key] = self.extra_fields[key]

        return d
Example #23
0
    def test_published(self) -> None:
        timestamps_item = pystac.Item.from_file(self.example_uri)

        # Get
        self.assertIn("published", timestamps_item.properties)
        timestamps_published = TimestampsExtension.ext(
            timestamps_item).published
        self.assertIsInstance(timestamps_published, datetime)
        self.assertEqual(
            datetime_to_str(get_opt(timestamps_published)),
            timestamps_item.properties["published"],
        )

        # Set
        TimestampsExtension.ext(
            timestamps_item).published = self.sample_datetime
        self.assertEqual(self.sample_datetime_str,
                         timestamps_item.properties["published"])

        # Get from Asset
        asset_no_prop = timestamps_item.assets["red"]
        asset_prop = timestamps_item.assets["blue"]
        self.assertEqual(
            TimestampsExtension.ext(asset_no_prop).published,
            TimestampsExtension.ext(timestamps_item).published,
        )
        self.assertEqual(
            TimestampsExtension.ext(asset_prop).published,
            str_to_datetime("2018-11-02T00:00:00Z"),
        )

        # # Set to Asset
        asset_value = str_to_datetime("2019-02-02T00:00:00Z")
        TimestampsExtension.ext(asset_no_prop).published = asset_value
        self.assertNotEqual(
            TimestampsExtension.ext(asset_no_prop).published,
            TimestampsExtension.ext(timestamps_item).published,
        )
        self.assertEqual(
            TimestampsExtension.ext(asset_no_prop).published, asset_value)

        # Validate
        timestamps_item.validate()
Example #24
0
    def test_updated(self) -> None:
        item = self.item.clone()
        cm = item.common_metadata
        analytic = item.assets["analytic"]
        analytic_cm = CommonMetadata(analytic)
        thumbnail = item.assets["thumbnail"]
        thumbnail_cm = CommonMetadata(thumbnail)

        item_value = cm.updated
        a2_known_value = utils.str_to_datetime("2017-05-18T13:22:30.040Z")

        # Get
        self.assertNotEqual(thumbnail_cm.updated, item_value)
        self.assertEqual(thumbnail_cm.updated, a2_known_value)

        # Set
        set_value = utils.str_to_datetime("2014-05-18T13:22:30.040Z")
        analytic_cm.updated = set_value

        self.assertEqual(analytic_cm.updated, set_value)
        self.assertEqual(analytic.to_dict()["updated"],
                         utils.datetime_to_str(set_value))
Example #25
0
    def test_published(self):
        timestamps_item = pystac.read_file(self.example_uri)

        # Get
        self.assertIn("published", timestamps_item.properties)
        timestamps_published = timestamps_item.ext.timestamps.published
        self.assertIsInstance(timestamps_published, datetime)
        self.assertEqual(datetime_to_str(timestamps_published),
                         timestamps_item.properties['published'])

        # Set
        timestamps_item.ext.timestamps.published = self.sample_datetime
        self.assertEqual(self.sample_datetime_str,
                         timestamps_item.properties['published'])

        # Get from Asset
        asset_no_prop = timestamps_item.assets['red']
        asset_prop = timestamps_item.assets['blue']
        self.assertEqual(
            timestamps_item.ext.timestamps.get_published(asset_no_prop),
            timestamps_item.ext.timestamps.get_published())
        self.assertEqual(
            timestamps_item.ext.timestamps.get_published(asset_prop),
            str_to_datetime("2018-11-02T00:00:00Z"))

        # # Set to Asset
        asset_value = str_to_datetime("2019-02-02T00:00:00Z")
        timestamps_item.ext.timestamps.set_published(asset_value,
                                                     asset_no_prop)
        self.assertNotEqual(
            timestamps_item.ext.timestamps.get_published(asset_no_prop),
            timestamps_item.ext.timestamps.get_published())
        self.assertEqual(
            timestamps_item.ext.timestamps.get_published(asset_no_prop),
            asset_value)

        # Validate
        timestamps_item.validate()
Example #26
0
def download_and_cog_chirps(
    year: str,
    month: str,
    s3_dst: str,
    day: str = None,
    overwrite: bool = False,
    slack_url: str = None,
):
    # Cleaning and sanity checks
    s3_dst = s3_dst.rstrip("/")

    # Set up file strings
    if day is not None:
        # Set up a daily process
        in_file = f"chirps-v2.0.{year}.{month}.{day}.tif.gz"
        in_href = DAILY_URL_TEMPLATE.format(year=year, in_file=in_file)
        in_data = f"/vsigzip//vsicurl/{in_href}"
        if not check_for_url_existence(in_href):
            log.warning("Couldn't find the gzipped file, trying the .tif")
            in_file = f"chirps-v2.0.{year}.{month}.{day}.tif"
            in_href = DAILY_URL_TEMPLATE.format(year=year, in_file=in_file)
            in_data = f"/vsicurl/{in_href}"

            if not check_for_url_existence(in_href):
                log.error("Couldn't find the .tif file either, aborting")
                sys.exit(1)

        file_base = f"{s3_dst}/{year}/{month}/chirps-v2.0_{year}.{month}.{day}"
        out_data = f"{file_base}.tif"
        out_stac = f"{file_base}.stac-item.json"

        start_datetime = f"{year}-{month}-{day}T00:00:00Z"
        end_datetime = f"{year}-{month}-{day}T23:59:59Z"
        product_name = "rainfall_chirps_daily"
    else:
        # Set up a monthly process
        in_file = f"chirps-v2.0.{year}.{month}.tif.gz"
        in_href = MONTHLY_URL_TEMPLATE.format(in_file=in_file)
        in_data = f"/vsigzip//vsicurl/{in_href}"
        if not check_for_url_existence(in_href):
            log.warning("Couldn't find the gzipped file, trying the .tif")
            in_file = f"chirps-v2.0.{year}.{month}.tif"
            in_href = MONTHLY_URL_TEMPLATE.format(in_file=in_file)
            in_data = f"/vsicurl/{in_href}"

            if not check_for_url_existence(in_href):
                log.error("Couldn't find the .tif file either, aborting")
                sys.exit(1)

        file_base = f"{s3_dst}/chirps-v2.0_{year}.{month}"
        out_data = f"{file_base}.tif"
        out_stac = f"{file_base}.stac-item.json"

        _, end = calendar.monthrange(int(year), int(month))
        start_datetime = f"{year}-{month}-01T00:00:00Z"
        end_datetime = f"{year}-{month}-{end}T23:59:59Z"
        product_name = "rainfall_chirps_monthly"

        # Set to 15 for the STAC metadata
        day = 15

    try:
        # Check if file already exists
        log.info(f"Working on {in_file}")
        if not overwrite and s3_head_object(out_stac) is not None:
            log.warning(f"File {out_stac} already exists. Skipping.")
            return

        # COG and STAC
        with MemoryFile() as mem_dst:
            # Creating the COG, with a memory cache and no download. Shiny.
            cog_translate(
                in_data,
                mem_dst.name,
                cog_profiles.get("deflate"),
                in_memory=True,
                nodata=-9999,
            )
            # Creating the STAC document with appropriate date range
            _, end = calendar.monthrange(int(year), int(month))
            item = create_stac_item(
                mem_dst,
                id=str(odc_uuid("chirps", "2.0", [in_file])),
                with_proj=True,
                input_datetime=datetime(int(year), int(month), int(day)),
                properties={
                    "odc:processing_datetime": datetime_to_str(datetime.now()),
                    "odc:product": product_name,
                    "start_datetime": start_datetime,
                    "end_datetime": end_datetime,
                },
            )
            item.set_self_href(out_stac)
            # Manually redo the asset
            del item.assets["asset"]
            item.assets["rainfall"] = pystac.Asset(
                href=out_data,
                title="CHIRPS-v2.0",
                media_type=pystac.MediaType.COG,
                roles=["data"],
            )
            # Let's add a link to the source
            item.add_links([
                pystac.Link(
                    target=in_href,
                    title="Source file",
                    rel=pystac.RelType.DERIVED_FROM,
                    media_type="application/gzip",
                )
            ])

            # Dump the data to S3
            mem_dst.seek(0)
            log.info(f"Writing DATA to: {out_data}")
            s3_dump(mem_dst, out_data, ACL="bucket-owner-full-control")
            # Write STAC to S3
            log.info(f"Writing STAC to: {out_stac}")
            s3_dump(
                json.dumps(item.to_dict(), indent=2),
                out_stac,
                ContentType="application/json",
                ACL="bucket-owner-full-control",
            )
            # All done!
            log.info(f"Completed work on {in_file}")

    except Exception as e:
        message = f"Failed to handle {in_file} with error {e}"

        if slack_url is not None:
            send_slack_notification(slack_url, "Chirps Rainfall Monthly",
                                    message)
        log.exception(message)

        exit(1)