diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a6a51b0..0ee36440 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). -## [1.6.0](https://github.com/Terradue/DotNetStac/compare/1.5.0...1.6.0) +## [1.6.1](https://github.com/Terradue/DotNetStac/compare/1.6.0...1.6.1) -add datacube extension support +Added patching function for Stac Objects (RFC 7386) + +### Merged + +- Added patching function for Stac Objects (RFC 7386) [`#18`](https://github.com/Terradue/DotNetStac/pull/18) + +### Commits + +- liniting code [`9338969`](https://github.com/Terradue/DotNetStac/commit/9338969117132ad8fd8eb562f43da4e96a849a6a) +- fix error IDE0038 [`01ff3ed`](https://github.com/Terradue/DotNetStac/commit/01ff3ede7d3c8726d8dc6005d80d52a6574edb69) +- tests fixed [`1bd1dd8`](https://github.com/Terradue/DotNetStac/commit/1bd1dd8f62c71371c1f9f5962ecd88a800a4d790) + +## [1.6.0](https://github.com/Terradue/DotNetStac/compare/1.5.0...1.6.0) - 2022-10-27 ### Merged diff --git a/README.md b/README.md index af0a3fa4..6082e3ab 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@

-![Build Status](https://github.com/Terradue/DotNetStac/actions/workflows/build.yaml/badge.svg?branch=release/1.6.0) +![Build Status](https://github.com/Terradue/DotNetStac/actions/workflows/build.yaml/badge.svg?branch=release/1.6.1) [![NuGet](https://img.shields.io/nuget/vpre/DotNetStac)](https://www.nuget.org/packages/DotNetStac/) -[![codecov](https://codecov.io/gh/Terradue/DotNetStac/branch/release/1.6.0/graph/badge.svg)](https://codecov.io/gh/Terradue/DotNetStac) +[![codecov](https://codecov.io/gh/Terradue/DotNetStac/branch/release/1.6.1/graph/badge.svg)](https://codecov.io/gh/Terradue/DotNetStac) [![Gitter](https://img.shields.io/gitter/room/SpatioTemporal-Asset-Catalog/Lobby?color=yellow)](https://gitter.im/SpatioTemporal-Asset-Catalog/Lobby) [![License](https://img.shields.io/badge/license-AGPL3-blue.svg)](LICENSE) [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/Terradue/DotNetStac/master?filepath=example.ipynb) diff --git a/src/DotNetStac.Test/Common/PatchHelpersTests.cs b/src/DotNetStac.Test/Common/PatchHelpersTests.cs new file mode 100644 index 00000000..8ca024d9 --- /dev/null +++ b/src/DotNetStac.Test/Common/PatchHelpersTests.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using GeoJSON.Net.Geometry; +using Newtonsoft.Json; +using Stac.Collection; +using Stac.Common; +using Xunit; + +namespace Stac.Test.Item +{ + public class PatchHelpersTests : TestBase + { + [Fact] + public void PatchStacItemTest() + { + StacItem baseItem = StacConvert.Deserialize(GetJson("Common", "BaseItem")); + + StacItem patchItem = StacConvert.Deserialize(GetJson("Common", "Patch")); + + StacItem patchedItem = baseItem.Patch(patchItem); + + JsonAssert.AreEqual(GetJson("Common", "PatchedItem"), StacConvert.Serialize(patchedItem)); + } + + [Fact] + public void PatchStacItemTest2() + { + StacItem baseItem = StacConvert.Deserialize(GetJson("Common", "BaseItem")); + + Patch patchItem = JsonConvert.DeserializeObject(GetJson("Common", "Patch2")); + + StacItem patchedItem = baseItem.Patch(patchItem); + + JsonAssert.AreEqual(GetJson("Common", "PatchedItem2"), StacConvert.Serialize(patchedItem)); + } + } +} diff --git a/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_BaseItem.json b/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_BaseItem.json new file mode 100644 index 00000000..5ce267bb --- /dev/null +++ b/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_BaseItem.json @@ -0,0 +1,662 @@ +{ + "type": "Feature", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/sat/v1.0.0/schema.json" + ], + "id": "S2A_30VWN_20200830_0_L2A", + "bbox": [ + -3.000355032202007, + 60.33378967715628, + -0.9494552273687494, + 61.33443350563737 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -1.0115813842949837, + 60.33378967715628 + ], + [ + -3.000344266875852, + 60.34865379793714 + ], + [ + -3.000355032202007, + 61.33443350563737 + ], + [ + -0.9494552273687494, + 61.31895930227699 + ], + [ + -1.0115813842949837, + 60.33378967715628 + ] + ] + ] + }, + "properties": { + "datetime": "2020-08-30T11:34:28Z", + "platform": "sentinel-2a", + "constellation": "sentinel-2", + "instruments": [ + "msi" + ], + "gsd": 10, + "view:off_nadir": 0, + "proj:epsg": 32630, + "sat:relative_orbit": 80, + "sentinel:utm_zone": 30, + "sentinel:latitude_band": "V", + "sentinel:grid_square": "WN", + "sentinel:sequence": "0", + "sentinel:product_id": "S2A_MSIL2A_20200830T113321_N0214_R080_T30VWN_20200830T121958", + "sentinel:data_coverage": 100, + "eo:cloud_cover": 0, + "sentinel:valid_cloud_cover": false, + "created": "2020-08-31T09:57:42.772Z", + "updated": "2020-08-31T09:57:42.772Z" + }, + "collection": "sentinel-s2-l2a-cogs", + "assets": { + "thumbnail": { + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ], + "href": "https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/30/V/WN/2020/8/30/0/preview.jpg" + }, + "overview": { + "title": "True color image", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "overview" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "center_wavelength": 0.6645, + "full_width_half_max": 0.038 + }, + { + "name": "B03", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + }, + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/L2A_PVI.tif", + "proj:shape": [ + 343, + 343 + ], + "proj:transform": [ + 320, + 0, + 499980, + 0, + -320, + 6800040, + 0, + 0, + 1 + ] + }, + "info": { + "title": "Original JSON metadata", + "type": "application/json", + "roles": [ + "metadata" + ], + "href": "https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/30/V/WN/2020/8/30/0/tileInfo.json" + }, + "metadata": { + "title": "Original XML metadata", + "type": "application/xml", + "roles": [ + "metadata" + ], + "href": "https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/30/V/WN/2020/8/30/0/metadata.xml" + }, + "visual": { + "title": "True color image", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "overview" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "center_wavelength": 0.6645, + "full_width_half_max": 0.038 + }, + { + "name": "B03", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + }, + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/TCI.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B01": { + "title": "Band 1 (coastal)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 60, + "eo:bands": [ + { + "name": "B01", + "common_name": "coastal", + "center_wavelength": 0.4439, + "full_width_half_max": 0.027 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B01.tif", + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60, + 0, + 499980, + 0, + -60, + 6800040, + 0, + 0, + 1 + ] + }, + "B02": { + "title": "Band 2 (blue)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B02.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B03": { + "title": "Band 3 (green)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B03", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B03.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B04": { + "title": "Band 4 (red)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "center_wavelength": 0.6645, + "full_width_half_max": 0.038 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B04.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B05": { + "title": "Band 5", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B05", + "center_wavelength": 0.7039, + "full_width_half_max": 0.019 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B05.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B06": { + "title": "Band 6", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B06", + "center_wavelength": 0.7402, + "full_width_half_max": 0.018 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B06.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B07": { + "title": "Band 7", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B07", + "center_wavelength": 0.7825, + "full_width_half_max": 0.028 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B07.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B08": { + "title": "Band 8 (nir)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B08", + "common_name": "nir", + "center_wavelength": 0.8351, + "full_width_half_max": 0.145 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B08.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B8A": { + "title": "Band 8A", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B8A", + "center_wavelength": 0.8648, + "full_width_half_max": 0.033 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B8A.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B09": { + "title": "Band 9", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 60, + "eo:bands": [ + { + "name": "B09", + "center_wavelength": 0.945, + "full_width_half_max": 0.026 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B09.tif", + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60, + 0, + 499980, + 0, + -60, + 6800040, + 0, + 0, + 1 + ] + }, + "B11": { + "title": "Band 11 (swir16)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B11", + "common_name": "swir16", + "center_wavelength": 1.6137, + "full_width_half_max": 0.143 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B11.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B12": { + "title": "Band 12 (swir22)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B12", + "common_name": "swir22", + "center_wavelength": 2.22024, + "full_width_half_max": 0.242 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B12.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "AOT": { + "title": "Aerosol Optical Thickness (AOT)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/AOT.tif", + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60, + 0, + 499980, + 0, + -60, + 6800040, + 0, + 0, + 1 + ] + }, + "WVP": { + "title": "Water Vapour (WVP)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/WVP.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "SCL": { + "title": "Scene Classification Map (SCL)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/SCL.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + } + }, + "links": [ + { + "rel": "self", + "href": "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/S2A_30VWN_20200830_0_L2A" + }, + { + "rel": "canonical", + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/S2A_30VWN_20200830_0_L2A.json", + "type": "application/json" + }, + { + "rel": "canonical", + "href": "https://cirrus-v0-data-1qm7gekzjucbq.s3.us-west-2.amazonaws.com/sentinel-s2-l2a/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/S2A_30VWN_20200830_0_L2A.json", + "type": "application/json" + }, + { + "title": "Source STAC Item", + "rel": "derived_from", + "href": "https://cirrus-v0-data-1qm7gekzjucbq.s3.us-west-2.amazonaws.com/sentinel-s2-l2a/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/S2A_30VWN_20200830_0_L2A.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs" + }, + { + "rel": "collection", + "href": "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs" + }, + { + "rel": "root", + "href": "https://earth-search.aws.element84.com/v0/" + } + ] +} \ No newline at end of file diff --git a/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_Patch.json b/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_Patch.json new file mode 100644 index 00000000..2139a73f --- /dev/null +++ b/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_Patch.json @@ -0,0 +1,88 @@ +{ + "id": "S2A_30VWN_20200830_0_L2A", + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.0, + 61.0 + ], + [ + -3.5, + 50.0 + ], + [ + -3.5, + 63 + ], + [ + -1.0, + 63.0 + ], + [ + -2.0, + 61.0 + ] + ] + ] + }, + "properties": { + "datetime": "2022-08-30T11:34:28Z", + "platform": "sentinel-2c", + }, + "collection": "sentinel-s2-l3a-cogs", + "assets": { + "B01": { + "title": "Band 1 (coastal)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 70, + "eo:bands": [ + { + "name": "B01", + "common_name": "blue", + "center_wavelength": 0.5, + "full_width_half_max": 0.127 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B01.tiff", + "proj:shape": null, + }, + "B2000": { + "title": "Band 2 (blue)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B02.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + } + } +} \ No newline at end of file diff --git a/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_Patch2.json b/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_Patch2.json new file mode 100644 index 00000000..6a29b316 --- /dev/null +++ b/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_Patch2.json @@ -0,0 +1,85 @@ +{ + "geometry": { + "coordinates": [ + [ + [ + -2.0, + 61.0 + ], + [ + -3.5, + 50.0 + ], + [ + -3.5, + 63 + ], + [ + -1.0, + 63.0 + ], + [ + -2.0, + 61.0 + ] + ] + ] + }, + "properties": { + "datetime": "2022-08-30T11:34:28Z", + "platform": "sentinel-2c", + }, + "collection": null, + "assets": { + "B01": { + "title": "Band 1 (coastal)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 70, + "eo:bands": [ + { + "name": "B01", + "common_name": "blue", + "center_wavelength": 0.5, + "full_width_half_max": 0.127 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B01.tiff", + "proj:shape": null, + }, + "B2000": { + "title": "Band 2 (blue)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B02.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + } + } +} \ No newline at end of file diff --git a/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_PatchedItem.json b/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_PatchedItem.json new file mode 100644 index 00000000..55be4665 --- /dev/null +++ b/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_PatchedItem.json @@ -0,0 +1,690 @@ +{ + "type": "Feature", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/sat/v1.0.0/schema.json" + ], + "id": "S2A_30VWN_20200830_0_L2A", + "bbox": [ + -3.5, + 50.0, + -1.0, + 63.0 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.0, + 61.0 + ], + [ + -3.5, + 50.0 + ], + [ + -3.5, + 63.0 + ], + [ + -1.0, + 63.0 + ], + [ + -2.0, + 61.0 + ] + ] + ] + }, + "properties": { + "datetime": "2022-08-30T11:34:28Z", + "platform": "sentinel-2c", + "constellation": "sentinel-2", + "instruments": [ + "msi" + ], + "gsd": 10, + "view:off_nadir": 0, + "proj:epsg": 32630, + "sat:relative_orbit": 80, + "sentinel:utm_zone": 30, + "sentinel:latitude_band": "V", + "sentinel:grid_square": "WN", + "sentinel:sequence": "0", + "sentinel:product_id": "S2A_MSIL2A_20200830T113321_N0214_R080_T30VWN_20200830T121958", + "sentinel:data_coverage": 100, + "eo:cloud_cover": 0, + "sentinel:valid_cloud_cover": false, + "created": "2020-08-31T09:57:42.772Z", + "updated": "2020-08-31T09:57:42.772Z" + }, + "collection": "sentinel-s2-l3a-cogs", + "assets": { + "thumbnail": { + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ], + "href": "https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/30/V/WN/2020/8/30/0/preview.jpg" + }, + "overview": { + "title": "True color image", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "overview" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "center_wavelength": 0.6645, + "full_width_half_max": 0.038 + }, + { + "name": "B03", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + }, + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/L2A_PVI.tif", + "proj:shape": [ + 343, + 343 + ], + "proj:transform": [ + 320, + 0, + 499980, + 0, + -320, + 6800040, + 0, + 0, + 1 + ] + }, + "info": { + "title": "Original JSON metadata", + "type": "application/json", + "roles": [ + "metadata" + ], + "href": "https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/30/V/WN/2020/8/30/0/tileInfo.json" + }, + "metadata": { + "title": "Original XML metadata", + "type": "application/xml", + "roles": [ + "metadata" + ], + "href": "https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/30/V/WN/2020/8/30/0/metadata.xml" + }, + "visual": { + "title": "True color image", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "overview" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "center_wavelength": 0.6645, + "full_width_half_max": 0.038 + }, + { + "name": "B03", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + }, + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/TCI.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B01": { + "title": "Band 1 (coastal)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 70, + "eo:bands": [ + { + "name": "B01", + "common_name": "blue", + "center_wavelength": 0.5, + "full_width_half_max": 0.127 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B01.tiff", + "proj:transform": [ + 60, + 0, + 499980, + 0, + -60, + 6800040, + 0, + 0, + 1 + ] + }, + "B2000": { + "title": "Band 2 (blue)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B02.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B02": { + "title": "Band 2 (blue)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B02.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B03": { + "title": "Band 3 (green)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B03", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B03.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B04": { + "title": "Band 4 (red)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "center_wavelength": 0.6645, + "full_width_half_max": 0.038 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B04.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B05": { + "title": "Band 5", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B05", + "center_wavelength": 0.7039, + "full_width_half_max": 0.019 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B05.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B06": { + "title": "Band 6", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B06", + "center_wavelength": 0.7402, + "full_width_half_max": 0.018 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B06.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B07": { + "title": "Band 7", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B07", + "center_wavelength": 0.7825, + "full_width_half_max": 0.028 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B07.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B08": { + "title": "Band 8 (nir)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B08", + "common_name": "nir", + "center_wavelength": 0.8351, + "full_width_half_max": 0.145 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B08.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B8A": { + "title": "Band 8A", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B8A", + "center_wavelength": 0.8648, + "full_width_half_max": 0.033 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B8A.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B09": { + "title": "Band 9", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 60, + "eo:bands": [ + { + "name": "B09", + "center_wavelength": 0.945, + "full_width_half_max": 0.026 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B09.tif", + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60, + 0, + 499980, + 0, + -60, + 6800040, + 0, + 0, + 1 + ] + }, + "B11": { + "title": "Band 11 (swir16)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B11", + "common_name": "swir16", + "center_wavelength": 1.6137, + "full_width_half_max": 0.143 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B11.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B12": { + "title": "Band 12 (swir22)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B12", + "common_name": "swir22", + "center_wavelength": 2.22024, + "full_width_half_max": 0.242 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B12.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "AOT": { + "title": "Aerosol Optical Thickness (AOT)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/AOT.tif", + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60, + 0, + 499980, + 0, + -60, + 6800040, + 0, + 0, + 1 + ] + }, + "WVP": { + "title": "Water Vapour (WVP)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/WVP.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "SCL": { + "title": "Scene Classification Map (SCL)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/SCL.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + } + }, + "links": [ + { + "rel": "self", + "href": "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/S2A_30VWN_20200830_0_L2A" + }, + { + "rel": "canonical", + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/S2A_30VWN_20200830_0_L2A.json", + "type": "application/json" + }, + { + "rel": "canonical", + "href": "https://cirrus-v0-data-1qm7gekzjucbq.s3.us-west-2.amazonaws.com/sentinel-s2-l2a/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/S2A_30VWN_20200830_0_L2A.json", + "type": "application/json" + }, + { + "title": "Source STAC Item", + "rel": "derived_from", + "href": "https://cirrus-v0-data-1qm7gekzjucbq.s3.us-west-2.amazonaws.com/sentinel-s2-l2a/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/S2A_30VWN_20200830_0_L2A.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs" + }, + { + "rel": "collection", + "href": "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs" + }, + { + "rel": "root", + "href": "https://earth-search.aws.element84.com/v0/" + } + ] +} \ No newline at end of file diff --git a/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_PatchedItem2.json b/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_PatchedItem2.json new file mode 100644 index 00000000..8f3dec4f --- /dev/null +++ b/src/DotNetStac.Test/Resources/Common/PatchHelpersTests_PatchedItem2.json @@ -0,0 +1,689 @@ +{ + "type": "Feature", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/sat/v1.0.0/schema.json" + ], + "id": "S2A_30VWN_20200830_0_L2A", + "bbox": [ + -3.000355032202007, + 60.33378967715628, + -0.9494552273687494, + 61.33443350563737 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -2.0, + 61.0 + ], + [ + -3.5, + 50.0 + ], + [ + -3.5, + 63.0 + ], + [ + -1.0, + 63.0 + ], + [ + -2.0, + 61.0 + ] + ] + ] + }, + "properties": { + "datetime": "2022-08-30T11:34:28Z", + "platform": "sentinel-2c", + "constellation": "sentinel-2", + "instruments": [ + "msi" + ], + "gsd": 10, + "view:off_nadir": 0, + "proj:epsg": 32630, + "sat:relative_orbit": 80, + "sentinel:utm_zone": 30, + "sentinel:latitude_band": "V", + "sentinel:grid_square": "WN", + "sentinel:sequence": "0", + "sentinel:product_id": "S2A_MSIL2A_20200830T113321_N0214_R080_T30VWN_20200830T121958", + "sentinel:data_coverage": 100, + "eo:cloud_cover": 0, + "sentinel:valid_cloud_cover": false, + "created": "2020-08-31T09:57:42.772Z", + "updated": "2020-08-31T09:57:42.772Z" + }, + "assets": { + "thumbnail": { + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ], + "href": "https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/30/V/WN/2020/8/30/0/preview.jpg" + }, + "overview": { + "title": "True color image", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "overview" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "center_wavelength": 0.6645, + "full_width_half_max": 0.038 + }, + { + "name": "B03", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + }, + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/L2A_PVI.tif", + "proj:shape": [ + 343, + 343 + ], + "proj:transform": [ + 320, + 0, + 499980, + 0, + -320, + 6800040, + 0, + 0, + 1 + ] + }, + "info": { + "title": "Original JSON metadata", + "type": "application/json", + "roles": [ + "metadata" + ], + "href": "https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/30/V/WN/2020/8/30/0/tileInfo.json" + }, + "metadata": { + "title": "Original XML metadata", + "type": "application/xml", + "roles": [ + "metadata" + ], + "href": "https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/30/V/WN/2020/8/30/0/metadata.xml" + }, + "visual": { + "title": "True color image", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "overview" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "center_wavelength": 0.6645, + "full_width_half_max": 0.038 + }, + { + "name": "B03", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + }, + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/TCI.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B01": { + "title": "Band 1 (coastal)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 70, + "eo:bands": [ + { + "name": "B01", + "common_name": "blue", + "center_wavelength": 0.5, + "full_width_half_max": 0.127 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B01.tiff", + "proj:transform": [ + 60, + 0, + 499980, + 0, + -60, + 6800040, + 0, + 0, + 1 + ] + }, + "B2000": { + "title": "Band 2 (blue)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B02.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B02": { + "title": "Band 2 (blue)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B02.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B03": { + "title": "Band 3 (green)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B03", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B03.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B04": { + "title": "Band 4 (red)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "center_wavelength": 0.6645, + "full_width_half_max": 0.038 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B04.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B05": { + "title": "Band 5", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B05", + "center_wavelength": 0.7039, + "full_width_half_max": 0.019 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B05.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B06": { + "title": "Band 6", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B06", + "center_wavelength": 0.7402, + "full_width_half_max": 0.018 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B06.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B07": { + "title": "Band 7", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B07", + "center_wavelength": 0.7825, + "full_width_half_max": 0.028 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B07.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B08": { + "title": "Band 8 (nir)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B08", + "common_name": "nir", + "center_wavelength": 0.8351, + "full_width_half_max": 0.145 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B08.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B8A": { + "title": "Band 8A", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B8A", + "center_wavelength": 0.8648, + "full_width_half_max": 0.033 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B8A.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B09": { + "title": "Band 9", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 60, + "eo:bands": [ + { + "name": "B09", + "center_wavelength": 0.945, + "full_width_half_max": 0.026 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B09.tif", + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60, + 0, + 499980, + 0, + -60, + 6800040, + 0, + 0, + 1 + ] + }, + "B11": { + "title": "Band 11 (swir16)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B11", + "common_name": "swir16", + "center_wavelength": 1.6137, + "full_width_half_max": 0.143 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B11.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B12": { + "title": "Band 12 (swir22)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B12", + "common_name": "swir22", + "center_wavelength": 2.22024, + "full_width_half_max": 0.242 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B12.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "AOT": { + "title": "Aerosol Optical Thickness (AOT)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/AOT.tif", + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60, + 0, + 499980, + 0, + -60, + 6800040, + 0, + 0, + 1 + ] + }, + "WVP": { + "title": "Water Vapour (WVP)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/WVP.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "SCL": { + "title": "Scene Classification Map (SCL)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/SCL.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + } + }, + "links": [ + { + "rel": "self", + "href": "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/S2A_30VWN_20200830_0_L2A" + }, + { + "rel": "canonical", + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/S2A_30VWN_20200830_0_L2A.json", + "type": "application/json" + }, + { + "rel": "canonical", + "href": "https://cirrus-v0-data-1qm7gekzjucbq.s3.us-west-2.amazonaws.com/sentinel-s2-l2a/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/S2A_30VWN_20200830_0_L2A.json", + "type": "application/json" + }, + { + "title": "Source STAC Item", + "rel": "derived_from", + "href": "https://cirrus-v0-data-1qm7gekzjucbq.s3.us-west-2.amazonaws.com/sentinel-s2-l2a/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/S2A_30VWN_20200830_0_L2A.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs" + }, + { + "rel": "collection", + "href": "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs" + }, + { + "rel": "root", + "href": "https://earth-search.aws.element84.com/v0/" + } + ] +} \ No newline at end of file diff --git a/src/DotNetStac/Common/JsonMergeUtils.cs b/src/DotNetStac/Common/JsonMergeUtils.cs new file mode 100644 index 00000000..46501846 --- /dev/null +++ b/src/DotNetStac/Common/JsonMergeUtils.cs @@ -0,0 +1,246 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Stac.Common +{ + public class JsonMergeUtils + { + + /// + /// Return the result of merging the original JSON document with the JSON Merge patch document + /// according to https://tools.ietf.org/html/rfc7386 + /// + /// + /// + /// Writer options used to write the merge result. + /// The document that represents the merge result. + public static string Merge(string original, string patch, JsonWriterOptions? writerOptions = null) + { + var memStream = new MemoryStream(); + + using (var originalDoc = JsonDocument.Parse(original)) + using (var patchDoc = JsonDocument.Parse(patch)) + using (var jsonWriter = new Utf8JsonWriter(memStream, writerOptions ?? new JsonWriterOptions { Indented = true })) + { + + var originalKind = originalDoc.RootElement.ValueKind; + var patchKind = patchDoc.RootElement.ValueKind; + + if (originalKind != JsonValueKind.Object) + { + throw new InvalidOperationException($"The original JSON document to merge new content into must be an object type. Instead it is {originalKind}."); + } + + if (patchKind != JsonValueKind.Object) + { + throw new InvalidOperationException($"The patch JSON document must be an object type. Instead it is {originalKind}."); + } + + if (originalKind != patchKind) + { + return original; + } + + MergeObjects(jsonWriter, originalDoc.RootElement, patchDoc.RootElement); + } + + + return Encoding.UTF8.GetString(memStream.ToArray()); + } + + /// + /// Return the result of merging the original JSON document with the JSON Merge patch document + /// according to https://tools.ietf.org/html/rfc7386 + /// + /// + /// + /// Writer options used to write the merge result. + public static async Task MergeAsync(string original, System.IO.Stream patch, CancellationToken token = default, JsonWriterOptions? writerOptions = null) + { + var outputBuffer = new MemoryStream(); + var jsonDocumentOptions = new JsonDocumentOptions(); + using (var originalDoc = JsonDocument.Parse(original, jsonDocumentOptions)) + using (var patchDoc = await JsonDocument.ParseAsync(patch, jsonDocumentOptions, token)) + using (var jsonWriter = new Utf8JsonWriter(outputBuffer, writerOptions ?? new JsonWriterOptions { Indented = true })) + { + var originalKind = originalDoc.RootElement.ValueKind; + var patchKind = patchDoc.RootElement.ValueKind; + + if (originalKind != JsonValueKind.Object) + { + throw new InvalidOperationException($"The original JSON document to merge new content into must be an object type. Instead it is {originalKind}."); + } + + if (patchKind != JsonValueKind.Object) + { + throw new InvalidOperationException($"The patch JSON document must be an object type. Instead it is {originalKind}."); + } + + if (originalKind != patchKind) + { + originalDoc.WriteTo(jsonWriter); + } + else + { + MergeObjects(jsonWriter, originalDoc.RootElement, patchDoc.RootElement); + } + } + + return Encoding.UTF8.GetString(outputBuffer.ToArray()); + } + + /// + /// Extract property names with a null value. + /// + /// Nested field names are returned joined by "." + /// Array items are ignored. + /// + /// + /// + /// Writer options used to write the merge result. + /// The list of null properties. + public static List ExtractNullProperties(string patch) + { + var patchDoc = JsonDocument.Parse(patch); + if (patchDoc.RootElement.ValueKind != JsonValueKind.Object) + { + throw new InvalidOperationException($"The patch JSON document must be an object type. Instead it is {patchDoc.RootElement.ValueKind}."); + } + + return ExtractNullPropertiesFromObject(patchDoc.RootElement).ToList(); + } + + /// + /// Extract property names with a null value. + /// + /// Nested field names are returned joined by "." + /// Array items are ignored. + /// + /// + /// + /// Writer options used to write the merge result. + /// Cancellation token. + /// The list of null properties. + public static async Task> ExtractNullPropertiesAsync(System.IO.Stream patch, CancellationToken token = default) + { + var patchDoc = await JsonDocument.ParseAsync(patch, new JsonDocumentOptions(), token); + if (patchDoc.RootElement.ValueKind != JsonValueKind.Object) + { + throw new InvalidOperationException($"The patch JSON document must be an object type. Instead it is {patchDoc.RootElement.ValueKind}."); + } + + return ExtractNullPropertiesFromObject(patchDoc.RootElement).ToList(); + } + + /// + /// Apply the result of a JSON merge patch to the given model, using System.Text.Json serializer + /// to serialize and deserialize the model. + /// + /// the model type + /// + /// + /// JSON serialization options + /// A new model representing the patched instance. + public static T MergeModel(T original, string patch, JsonSerializerOptions options = null) + { + var originalJson = JsonSerializer.Serialize(original, options); + return JsonSerializer.Deserialize(Merge(originalJson, patch), options); + } + + /// + /// Apply the result of a JSON merge patch to the given model, using System.Text.Json serializer + /// to serialize and deserialize the model. + /// + /// + /// the model type + /// + /// + /// JSON serialization options + /// Cancellation token + /// A task that returns a new model representing the patched instance. + public static async Task MergeModelAsync(T original, System.IO.Stream patch, JsonSerializerOptions options = null, CancellationToken token = default) + { + var originalJson = JsonSerializer.Serialize(original, options); + return JsonSerializer.Deserialize(await MergeAsync(originalJson, patch, token), options); + } + + private static IEnumerable ExtractNullPropertiesFromObject(JsonElement patch) + { + Debug.Assert(patch.ValueKind == JsonValueKind.Object); + foreach (var property in patch.EnumerateObject()) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + yield return property.Name; + } + else if (property.Value.ValueKind == JsonValueKind.Object) + { + foreach (var field in ExtractNullPropertiesFromObject(property.Value)) + { + yield return string.Join(".", property.Name, field); + } + } + } + } + + private static void MergeObjects(Utf8JsonWriter jsonWriter, JsonElement original, JsonElement patch) + { + Debug.Assert(original.ValueKind == JsonValueKind.Object); + Debug.Assert(patch.ValueKind == JsonValueKind.Object); + + jsonWriter.WriteStartObject(); + + // Write all the properties of the original document. + // If a property exists in both documents, either: + // * Merge them, if they are both objects + // * Completely override the value of the original with the one from the patch, if the value kind mismatches (e.g. one is object, while the other is an array or string) + // * Ignore the original property if the patch property value is null + foreach (var property in original.EnumerateObject()) + { + if (patch.TryGetProperty(property.Name, out JsonElement patchPropValue)) + { + if (patchPropValue.ValueKind == JsonValueKind.Null) + { + continue; + } + + jsonWriter.WritePropertyName(property.Name); + + var propValue = property.Value; + + if (patchPropValue.ValueKind == JsonValueKind.Object && propValue.ValueKind == JsonValueKind.Object) + { + MergeObjects(jsonWriter, propValue, patchPropValue); // Recursive call + } + else + { + patchPropValue.WriteTo(jsonWriter); + } + } + else + { + property.WriteTo(jsonWriter); + } + } + + // Write all the properties of the patch document that are unique to it (beside null values). + foreach (var property in patch.EnumerateObject()) + { + if (!original.TryGetProperty(property.Name, out JsonElement patchPropValue) && patchPropValue.ValueKind != JsonValueKind.Null) + { + property.WriteTo(jsonWriter); + } + } + + jsonWriter.WriteEndObject(); + } + } +} diff --git a/src/DotNetStac/Common/PatchHelpers.cs b/src/DotNetStac/Common/PatchHelpers.cs new file mode 100644 index 00000000..89ca9bd8 --- /dev/null +++ b/src/DotNetStac/Common/PatchHelpers.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using GeoJSON.Net.Geometry; +using Newtonsoft.Json; + +namespace Stac.Common +{ + public static class PatchHelpers + { + public static T Patch(this T stacObject, IDictionary patch) where T : IStacObject + { + var itemJson = StacConvert.Serialize(stacObject); + var patchJson = JsonConvert.SerializeObject(patch); + var patchedJson = JsonMergeUtils.Merge(itemJson, patchJson, new System.Text.Json.JsonWriterOptions { Indented = false }); + return StacConvert.Deserialize(patchedJson); + } + + public static T Patch(this T stacObject, IStacObject patch) where T : IStacObject + { + var itemJson = StacConvert.Serialize(stacObject); + var patchJson = StacConvert.Serialize(patch); + IDictionary patchdic = JsonConvert.DeserializeObject>(patchJson); + if (patchdic.ContainsKey("links") && patchdic["links"] is IEnumerable en + && ((IEnumerable)patchdic["links"]).Count() == 0) + { + patchdic.Remove("links"); + } + return Patch(stacObject, patchdic); + } + } +} diff --git a/src/DotNetStac/Common/PatchStacItem.cs b/src/DotNetStac/Common/PatchStacItem.cs new file mode 100644 index 00000000..e450a3bf --- /dev/null +++ b/src/DotNetStac/Common/PatchStacItem.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Stac.Common +{ + public class Patch : Dictionary + { + } +} diff --git a/src/DotNetStac/DotNetStac.csproj b/src/DotNetStac/DotNetStac.csproj index 2640627b..9a12b263 100644 --- a/src/DotNetStac/DotNetStac.csproj +++ b/src/DotNetStac/DotNetStac.csproj @@ -4,7 +4,7 @@ DotNetStac Terradue .Net library for working with any SpatioTemporal Asset Catalog LICENSE - 1.6.0 + 1.6.1 Emmanuel Mathot emmanuelmathot Terradue @@ -33,6 +33,7 @@ + diff --git a/src/DotNetStac/StacItem.cs b/src/DotNetStac/StacItem.cs index 77b12d2f..e412812f 100644 --- a/src/DotNetStac/StacItem.cs +++ b/src/DotNetStac/StacItem.cs @@ -50,6 +50,8 @@ public StacItem(StacItem stacItem) : base(Preconditions.CheckNotNull(stacItem, " this.Assets = new Dictionary(stacItem.Assets.Select(a => new KeyValuePair(a.Key, new StacAsset(a.Value, this))) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); this.Collection = stacItem.Collection; + this.BoundingBoxes = stacItem.BoundingBoxes; + this.CRS = stacItem.CRS; } private void LinksCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)