From ff6da342a2379ad19d69ca3676befa74586f3990 Mon Sep 17 00:00:00 2001 From: Matthias Mohr Date: Wed, 11 Sep 2024 23:13:35 +0200 Subject: [PATCH] STACIT: STAC 1.1 support Fixes #11753 Co-authored-by: Even Rouault --- .../gdrivers/data/stacit/test_stac_1.1.json | 53 ++++++++++++++ autotest/gdrivers/stacit.py | 25 +++++++ frmts/stacit/stacitdataset.cpp | 69 +++++++++++-------- 3 files changed, 120 insertions(+), 27 deletions(-) create mode 100644 autotest/gdrivers/data/stacit/test_stac_1.1.json diff --git a/autotest/gdrivers/data/stacit/test_stac_1.1.json b/autotest/gdrivers/data/stacit/test_stac_1.1.json new file mode 100644 index 000000000000..bcf1dd31800f --- /dev/null +++ b/autotest/gdrivers/data/stacit/test_stac_1.1.json @@ -0,0 +1,53 @@ +{ + "type": "Feature", + "stac_version": "1.1.0", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v2.0.0/schema.json", + "https://stac-extensions.github.io/projection/v2.0.0/schema.json" + ], + "id": "byte", + "geometry": null, + "properties": { + "datetime": "2021-07-19T10:57:30Z", + "proj:code": "EPSG:26711", + "proj:bbox": [ + 440720.000, 3750120.000, + 441920.000, 3751320.000 + ], + "proj:transform": [ + 60, + 0, + 440720, + 0, + -60, + 3751320 + ] + }, + "collection": "my_collection", + "assets": { + "metadata": { + "title": "Original XML metadata", + "type": "application/xml", + "roles": [ + "metadata" + ], + "href": "https://example.com/metadata.xml" + }, + "B01": { + "title": "Band 1 (coastal)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "bands": [ + { + "name": "B01", + "eo:common_name": "coastal", + "eo:center_wavelength": 0.4439, + "eo:full_width_half_max": 0.027 + } + ], + "href": "data/byte.tif" + } + } +} diff --git a/autotest/gdrivers/stacit.py b/autotest/gdrivers/stacit.py index eaec895eb94a..ac609a92a598 100755 --- a/autotest/gdrivers/stacit.py +++ b/autotest/gdrivers/stacit.py @@ -361,3 +361,28 @@ def test_stacit_single_feature(tmp_vsimem): assert ds is not None assert ds.RasterXSize == 20 assert ds.GetRasterBand(1).Checksum() == 4672 + + +############################################################################### +# Test STAC 1.1 + + +def test_stacit_stac_1_1(tmp_vsimem): + + filename = str(tmp_vsimem / "feature.json") + with gdaltest.tempfile( + filename, open("data/stacit/test_stac_1.1.json", "rb").read() + ): + ds = gdal.Open(filename) + assert ds is not None + assert ds.RasterXSize == 20 + assert ds.GetSpatialRef().GetName() == "NAD27 / UTM zone 11N" + assert ds.GetGeoTransform() == pytest.approx( + [440720.0, 60.0, 0.0, 3751320.0, 0.0, -60.0], rel=1e-8 + ) + assert ds.GetRasterBand(1).GetMetadata() == { + "eo:center_wavelength": "0.4439", + "eo:full_width_half_max": "0.027", + } + assert ds.GetRasterBand(1).GetColorInterpretation() == gdal.GCI_CoastalBand + assert ds.GetRasterBand(1).Checksum() == 4672 diff --git a/frmts/stacit/stacitdataset.cpp b/frmts/stacit/stacitdataset.cpp index 993b67ca3dd8..51f8510eef55 100644 --- a/frmts/stacit/stacitdataset.cpp +++ b/frmts/stacit/stacitdataset.cpp @@ -44,7 +44,7 @@ struct AssetSetByProjection struct Asset { std::string osName{}; - CPLJSONArray eoBands{}; + CPLJSONArray bands{}; std::map assets{}; }; @@ -124,7 +124,8 @@ int STACITDataset::Identify(GDALOpenInfo *poOpenInfo) } if (strstr(pszHeader, "\"stac_version\"") != nullptr && - strstr(pszHeader, "\"proj:transform\"") != nullptr) + (strstr(pszHeader, "\"proj:transform\"") != nullptr || + strstr(pszHeader, "\"proj:bbox\"") != nullptr)) { return true; } @@ -218,34 +219,43 @@ static void ParseAsset(const CPLJSONObject &jAsset, return oProperties[pszName]; }; - auto oProjEPSG = GetAssetOrFeatureProperty("proj:epsg"); std::string osProjUserString; - if (oProjEPSG.IsValid() && oProjEPSG.GetType() != CPLJSONObject::Type::Null) + auto oProjCode = GetAssetOrFeatureProperty("proj:code"); + if (oProjCode.IsValid() && oProjCode.GetType() != CPLJSONObject::Type::Null) { - osProjUserString = "EPSG:" + oProjEPSG.ToString(); + osProjUserString = oProjCode.ToString(); } else { - auto oProjWKT2 = GetAssetOrFeatureProperty("proj:wkt2"); - if (oProjWKT2.IsValid() && - oProjWKT2.GetType() == CPLJSONObject::Type::String) + auto oProjEPSG = GetAssetOrFeatureProperty("proj:epsg"); + if (oProjEPSG.IsValid() && + oProjEPSG.GetType() != CPLJSONObject::Type::Null) { - osProjUserString = oProjWKT2.ToString(); + osProjUserString = "EPSG:" + oProjEPSG.ToString(); } else { - auto oProjPROJJSON = GetAssetOrFeatureProperty("proj:projjson"); - if (oProjPROJJSON.IsValid() && - oProjPROJJSON.GetType() == CPLJSONObject::Type::Object) + auto oProjWKT2 = GetAssetOrFeatureProperty("proj:wkt2"); + if (oProjWKT2.IsValid() && + oProjWKT2.GetType() == CPLJSONObject::Type::String) { - osProjUserString = oProjPROJJSON.ToString(); + osProjUserString = oProjWKT2.ToString(); } else { - CPLDebug("STACIT", - "Skipping asset %s that lacks a valid CRS member", - osAssetName.c_str()); - return; + auto oProjPROJJSON = GetAssetOrFeatureProperty("proj:projjson"); + if (oProjPROJJSON.IsValid() && + oProjPROJJSON.GetType() == CPLJSONObject::Type::Object) + { + osProjUserString = oProjPROJJSON.ToString(); + } + else + { + CPLDebug("STACIT", + "Skipping asset %s that lacks a valid CRS member", + osAssetName.c_str()); + return; + } } } } @@ -380,7 +390,9 @@ static void ParseAsset(const CPLJSONObject &jAsset, { Asset asset; asset.osName = osAssetName; - asset.eoBands = jAsset.GetArray("eo:bands"); + asset.bands = jAsset.GetArray("bands"); + if (!asset.bands.IsValid()) + asset.bands = jAsset.GetArray("eo:bands"); collection.assets[osAssetName] = std::move(asset); } @@ -584,15 +596,17 @@ bool STACITDataset::SetupDataset( poVRTBand->SetColorInterpretation(eInterp); // Set band properties - if (asset.eoBands.IsValid() && - asset.eoBands.Size() == poItemDS->GetRasterCount()) + if (asset.bands.IsValid() && + asset.bands.Size() == poItemDS->GetRasterCount()) { - const auto &eoBand = asset.eoBands[i]; - const auto osBandName = eoBand["name"].ToString(); + const auto &band = asset.bands[i]; + const auto osBandName = band["name"].ToString(); if (!osBandName.empty()) poVRTBand->SetDescription(osBandName.c_str()); - const auto osCommonName = eoBand["common_name"].ToString(); + auto osCommonName = band["eo:common_name"].ToString(); + if (osCommonName.empty()) + osCommonName = band["common_name"].ToString(); if (!osCommonName.empty()) { const auto eInterpFromCommonName = @@ -601,13 +615,14 @@ bool STACITDataset::SetupDataset( poVRTBand->SetColorInterpretation(eInterpFromCommonName); } - for (const auto &eoBandChild : eoBand.GetChildren()) + for (const auto &bandChild : band.GetChildren()) { - const auto osChildName = eoBandChild.GetName(); - if (osChildName != "name" && osChildName != "common_name") + const auto osChildName = bandChild.GetName(); + if (osChildName != "name" && osChildName != "common_name" && + osChildName != "eo:common_name") { poVRTBand->SetMetadataItem(osChildName.c_str(), - eoBandChild.ToString().c_str()); + bandChild.ToString().c_str()); } } }