diff --git a/extensions/eo/eo.go b/extensions/eo/eo.go index 7b3cccf..8895338 100644 --- a/extensions/eo/eo.go +++ b/extensions/eo/eo.go @@ -18,12 +18,42 @@ func init() { return &Asset{} }, ) + + stac.RegisterItemExtension( + regexp.MustCompile(extensionPattern), + func() stac.Extension { + return &Item{} + }, + ) } -type Asset struct { +type Item struct { CloudCover *float64 `json:"eo:cloud_cover,omitempty"` SnowCover *float64 `json:"eo:snow_cover,omitempty"` - Bands []*Band `json:"eo:bands,omitempty"` +} + +var _ stac.Extension = (*Item)(nil) + +func (*Item) URI() string { + return extensionUri +} + +func (e *Item) Encode(itemMap map[string]any) error { + return stac.EncodeExtendedItemProperties(e, itemMap) +} + +func (e *Item) Decode(itemMap map[string]any) error { + if err := stac.DecodeExtendedItemProperties(e, itemMap); err != nil { + return err + } + if e.CloudCover == nil && e.SnowCover == nil { + return stac.ErrExtensionDoesNotApply + } + return nil +} + +type Asset struct { + Bands []*Band `json:"eo:bands,omitempty"` } type Band struct { @@ -46,5 +76,11 @@ func (e *Asset) Encode(assetMap map[string]any) error { } func (e *Asset) Decode(assetMap map[string]any) error { - return stac.DecodeExtendedMap(e, assetMap) + if err := stac.DecodeExtendedMap(e, assetMap); err != nil { + return err + } + if len(e.Bands) == 0 { + return stac.ErrExtensionDoesNotApply + } + return nil } diff --git a/extensions/eo/eo_test.go b/extensions/eo/eo_test.go index e0fa48b..956322e 100644 --- a/extensions/eo/eo_test.go +++ b/extensions/eo/eo_test.go @@ -2,10 +2,10 @@ package eo_test import ( "encoding/json" - "github.com/planetlabs/go-stac/extensions/eo" "testing" "github.com/planetlabs/go-stac" + "github.com/planetlabs/go-stac/extensions/eo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -13,6 +13,72 @@ import ( func TestItemExtendedMarshal(t *testing.T) { cloudCover := float64(25) snowCover := float64(10) + item := &stac.Item{ + Version: "1.0.0", + Id: "item-id", + Geometry: map[string]any{ + "type": "Point", + "coordinates": []float64{0, 0}, + }, + Properties: map[string]any{ + "test": "value", + }, + Links: []*stac.Link{ + {Href: "https://example.com/stac/item-id", Rel: "self"}, + }, + Assets: map[string]*stac.Asset{ + "image": { + Title: "Image", + Href: "https://example.com/stac/item-id/image.tif", + Type: "image/tif", + }, + }, + Extensions: []stac.Extension{ + &eo.Item{ + CloudCover: &cloudCover, + SnowCover: &snowCover, + }, + }, + } + + data, err := json.Marshal(item) + require.NoError(t, err) + + expected := `{ + "type": "Feature", + "stac_version": "1.0.0", + "id": "item-id", + "geometry": { + "type": "Point", + "coordinates": [0, 0] + }, + "properties": { + "test": "value", + "eo:cloud_cover": 25, + "eo:snow_cover": 10 + }, + "links": [ + { + "rel": "self", + "href": "https://example.com/stac/item-id" + } + ], + "assets": { + "image": { + "title": "Image", + "href": "https://example.com/stac/item-id/image.tif", + "type": "image/tif" + } + }, + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.1.0/schema.json" + ] + }` + + assert.JSONEq(t, expected, string(data)) +} + +func TestAssetExtendedMarshal(t *testing.T) { centerWavelength := float64(0.85) item := &stac.Item{ Version: "1.0.0", @@ -34,8 +100,6 @@ func TestItemExtendedMarshal(t *testing.T) { Type: "image/tif", Extensions: []stac.Extension{ &eo.Asset{ - CloudCover: &cloudCover, - SnowCover: &snowCover, Bands: []*eo.Band{ { Name: "NIR", @@ -52,82 +116,223 @@ func TestItemExtendedMarshal(t *testing.T) { require.NoError(t, err) expected := `{ - "type": "Feature", - "stac_version": "1.0.0", - "id": "item-id", - "geometry": { - "type": "Point", - "coordinates": [0, 0] - }, - "properties": { - "test": "value" - }, - "links": [ - { - "rel": "self", - "href": "https://example.com/stac/item-id" - } - ], - "assets": { - "image": { - "title": "Image", - "href": "https://example.com/stac/item-id/image.tif", - "type": "image/tif", - "eo:cloud_cover": 25, - "eo:snow_cover": 10, - "eo:bands": [ - { - "name": "NIR", - "center_wavelength": 0.85 - } - ] - } - }, - "stac_extensions": [ - "https://stac-extensions.github.io/eo/v1.1.0/schema.json" - ] - }` + "type": "Feature", + "stac_version": "1.0.0", + "id": "item-id", + "geometry": { + "type": "Point", + "coordinates": [0, 0] + }, + "properties": { + "test": "value" + }, + "links": [ + { + "rel": "self", + "href": "https://example.com/stac/item-id" + } + ], + "assets": { + "image": { + "title": "Image", + "href": "https://example.com/stac/item-id/image.tif", + "type": "image/tif", + "eo:bands": [ + { + "name": "NIR", + "center_wavelength": 0.85 + } + ] + } + }, + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.1.0/schema.json" + ] + }` assert.JSONEq(t, expected, string(data)) } func TestItemExtendedUnmarshal(t *testing.T) { data := []byte(`{ - "type": "Feature", - "stac_version": "1.0.0", - "id": "item-id", - "geometry": { - "type": "Point", - "coordinates": [0, 0] - }, - "properties": { - "test": "value" - }, - "links": [ - { - "rel": "self", - "href": "https://example.com/stac/item-id" - } - ], - "assets": { - "image": { - "title": "Image", - "href": "https://example.com/stac/item-id/image.tif", - "type": "image/tif", - "eo:cloud_cover": 25, - "eo:snow_cover": 10, - "eo:bands": [ - { - "name": "NIR", - "center_wavelength": 0.85 - } - ] - } - }, - "stac_extensions": [ - "https://stac-extensions.github.io/eo/v1.1.0/schema.json" - ] - }`) + "type": "Feature", + "stac_version": "1.0.0", + "id": "item-id", + "geometry": { + "type": "Point", + "coordinates": [0, 0] + }, + "properties": { + "test": "value", + "eo:cloud_cover": 25, + "eo:snow_cover": 10 + }, + "links": [ + { + "rel": "self", + "href": "https://example.com/stac/item-id" + } + ], + "assets": { + "image": { + "title": "Image", + "href": "https://example.com/stac/item-id/image.tif", + "type": "image/tif" + } + }, + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.1.0/schema.json" + ] + }`) + + item := &stac.Item{} + require.NoError(t, json.Unmarshal(data, item)) + + cloudCover := float64(25) + snowCover := float64(10) + expected := &stac.Item{ + Version: "1.0.0", + Id: "item-id", + Geometry: map[string]any{ + "type": "Point", + "coordinates": []any{float64(0), float64(0)}, + }, + Properties: map[string]any{ + "test": "value", + }, + Links: []*stac.Link{ + {Href: "https://example.com/stac/item-id", Rel: "self"}, + }, + Assets: map[string]*stac.Asset{ + "image": { + Title: "Image", + Href: "https://example.com/stac/item-id/image.tif", + Type: "image/tif", + }, + }, + Extensions: []stac.Extension{ + &eo.Item{ + CloudCover: &cloudCover, + SnowCover: &snowCover, + }, + }, + } + + assert.Equal(t, expected, item) +} + +func TestAssetExtendedUnmarshal(t *testing.T) { + data := []byte(`{ + "type": "Feature", + "stac_version": "1.0.0", + "id": "item-id", + "geometry": { + "type": "Point", + "coordinates": [0, 0] + }, + "properties": { + "test": "value" + }, + "links": [ + { + "rel": "self", + "href": "https://example.com/stac/item-id" + } + ], + "assets": { + "image": { + "title": "Image", + "href": "https://example.com/stac/item-id/image.tif", + "type": "image/tif", + "eo:bands": [ + { + "name": "NIR", + "center_wavelength": 0.85 + } + ] + } + }, + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.1.0/schema.json" + ] + }`) + + item := &stac.Item{} + require.NoError(t, json.Unmarshal(data, item)) + + centerWavelength := float64(0.85) + expected := &stac.Item{ + Version: "1.0.0", + Id: "item-id", + Geometry: map[string]any{ + "type": "Point", + "coordinates": []any{float64(0), float64(0)}, + }, + Properties: map[string]any{ + "test": "value", + }, + Links: []*stac.Link{ + {Href: "https://example.com/stac/item-id", Rel: "self"}, + }, + Assets: map[string]*stac.Asset{ + "image": { + Title: "Image", + Href: "https://example.com/stac/item-id/image.tif", + Type: "image/tif", + Extensions: []stac.Extension{ + &eo.Asset{ + Bands: []*eo.Band{ + { + Name: "NIR", + CenterWavelength: ¢erWavelength, + }, + }, + }, + }, + }, + }, + } + + assert.Equal(t, expected, item) +} + +func TestBothExtendedUnmarshal(t *testing.T) { + data := []byte(`{ + "type": "Feature", + "stac_version": "1.0.0", + "id": "item-id", + "geometry": { + "type": "Point", + "coordinates": [0, 0] + }, + "properties": { + "test": "value", + "eo:cloud_cover": 25, + "eo:snow_cover": 10 + }, + "links": [ + { + "rel": "self", + "href": "https://example.com/stac/item-id" + } + ], + "assets": { + "image": { + "title": "Image", + "href": "https://example.com/stac/item-id/image.tif", + "type": "image/tif", + "eo:bands": [ + { + "name": "NIR", + "center_wavelength": 0.85 + } + ] + } + }, + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.1.0/schema.json" + ] + }`) item := &stac.Item{} require.NoError(t, json.Unmarshal(data, item)) @@ -155,8 +360,6 @@ func TestItemExtendedUnmarshal(t *testing.T) { Type: "image/tif", Extensions: []stac.Extension{ &eo.Asset{ - CloudCover: &cloudCover, - SnowCover: &snowCover, Bands: []*eo.Band{ { Name: "NIR", @@ -167,6 +370,12 @@ func TestItemExtendedUnmarshal(t *testing.T) { }, }, }, + Extensions: []stac.Extension{ + &eo.Item{ + CloudCover: &cloudCover, + SnowCover: &snowCover, + }, + }, } assert.Equal(t, expected, item) diff --git a/item.go b/item.go index 162135b..d45ff15 100644 --- a/item.go +++ b/item.go @@ -2,6 +2,7 @@ package stac import ( "encoding/json" + "errors" "fmt" "regexp" @@ -137,6 +138,9 @@ func (item *Item) UnmarshalJSON(data []byte) error { continue } if err := extension.Decode(itemMap); err != nil { + if errors.Is(err, ErrExtensionDoesNotApply) { + continue + } return fmt.Errorf("decoding error for %s: %w", uri, err) } item.Extensions = append(item.Extensions, extension)