From d1c2d5791fc9cccd37219b3d19a34c60bc1e838b Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Tue, 17 Oct 2023 16:56:19 -0600 Subject: [PATCH 01/25] feat: add Item.intersects --- stac/CHANGELOG.md | 8 ++++++++ stac/src/item.rs | 26 ++++++++++++++------------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/stac/CHANGELOG.md b/stac/CHANGELOG.md index f0864107..095bbce1 100644 --- a/stac/CHANGELOG.md +++ b/stac/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- `Item.intersects` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) + +### Removed + +- `Item.intersects_bbox` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) + ## [0.5.1] - 2023-09-14 ### Added diff --git a/stac/src/item.rs b/stac/src/item.rs index d86a60d1..a3ff5f21 100644 --- a/stac/src/item.rs +++ b/stac/src/item.rs @@ -209,29 +209,31 @@ impl Item { Ok(()) } - /// Returns true if this item's geometry intersects the provided bounding box. - /// - /// TODO support three dimensional bounding boxes. + /// Returns true if this item's geometry intersects the provided geojson geometry. /// /// # Examples /// /// ``` /// use stac::Item; /// use geojson::{Geometry, Value}; + /// use geo::{Rect, coord}; /// /// let mut item = Item::new("an-id"); /// item.set_geometry(Some(Geometry::new(Value::Point(vec![-105.1, 41.1])))); - /// let bbox = stac::geo::bbox(&vec![-106.0, 41.0, -105.0, 42.0]).unwrap(); - /// assert!(item.intersects_bbox(bbox).unwrap()); + /// let intersects = Rect::new( + /// coord! { x: -106.0, y: 40.0 }, + /// coord! { x: -105.0, y: 42.0 }, + /// ); + /// assert!(item.intersects(&intersects).unwrap()); /// ``` #[cfg(feature = "geo")] - pub fn intersects_bbox(&self, bbox: geo::Rect) -> Result { - // TODO support three dimensional - use geo::Intersects; - + pub fn intersects(&self, intersects: &T) -> Result + where + T: geo::Intersects, + { if let Some(geometry) = self.geometry.clone() { let geometry: geo::Geometry = geometry.try_into()?; - Ok(geometry.intersects(&bbox)) + Ok(intersects.intersects(&geometry)) } else { Ok(false) } @@ -441,7 +443,7 @@ mod tests { #[test] #[cfg(feature = "geo")] - fn insersects_bbox() { + fn insersects() { use geojson::Geometry; let mut item = Item::new("an-id"); item.set_geometry(Some(Geometry::new(geojson::Value::Point(vec![ @@ -449,7 +451,7 @@ mod tests { ])))) .unwrap(); assert!(item - .intersects_bbox(crate::geo::bbox(&vec![-106.0, 41.0, -105.0, 42.0]).unwrap()) + .intersects(&crate::geo::bbox(&vec![-106.0, 41.0, -105.0, 42.0]).unwrap()) .unwrap()); } From bfa9c2c78e252a30cb3f79302e1c9ad79e24a683 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 05:40:06 -0600 Subject: [PATCH 02/25] feat: add common metadata fields --- stac/CHANGELOG.md | 1 + stac/src/asset.rs | 22 ++++++++++++++++ stac/src/item.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/stac/CHANGELOG.md b/stac/CHANGELOG.md index 095bbce1..6b3db09e 100644 --- a/stac/CHANGELOG.md +++ b/stac/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - `Item.intersects` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) +- Common metadata fields ([#203](https://github.com/stac-utils/stac-rs/pull/203)) ### Removed diff --git a/stac/src/asset.rs b/stac/src/asset.rs index 0b96e8b5..23ba5cfb 100644 --- a/stac/src/asset.rs +++ b/stac/src/asset.rs @@ -31,6 +31,26 @@ pub struct Asset { #[serde(skip_serializing_if = "Option::is_none")] pub roles: Option>, + /// Creation date and time of the corresponding data, in UTC. + /// + /// This identifies the creation time of the data. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub created: Option, + + /// Date and time the metadata was updated last, in UTC. + /// + /// This identifies the updated time of the data. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub updated: Option, + /// Additional fields on the asset. #[serde(flatten)] pub additional_fields: Map, @@ -84,6 +104,8 @@ impl Asset { description: None, r#type: None, roles: None, + created: None, + updated: None, additional_fields: Map::new(), } } diff --git a/stac/src/item.rs b/stac/src/item.rs index a3ff5f21..5f0dd80a 100644 --- a/stac/src/item.rs +++ b/stac/src/item.rs @@ -105,6 +105,64 @@ pub struct Properties { /// requires `start_datetime` and `end_datetime` from common metadata to be set. pub datetime: Option, + /// The first or start date and time for the Item, in UTC. + /// + /// It is formatted as date-time according to RFC 3339, section 5.6. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub start_datetime: Option, + + /// The last or end date and time for the Item, in UTC. + /// + /// It is formatted as date-time according to RFC 3339, section 5.6. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub end_datetime: Option, + + /// A human readable title describing the Item. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + + /// Detailed multi-line description to fully explain the Item. + /// + /// CommonMark 0.29 syntax MAY be used for rich text representation. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + /// Creation date and time of the corresponding data, in UTC. + /// + /// This identifies the creation time of the metadata. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub created: Option, + + /// Date and time the metadata was updated last, in UTC. + /// + /// This identifies the updated time of the metadata. + /// + /// This is a [common + /// metadata](https://github.com/radiantearth/stac-spec/blob/master/item-spec/common-metadata.md) + /// field. + #[serde(skip_serializing_if = "Option::is_none")] + pub updated: Option, + /// Additional fields on the properties. #[serde(flatten)] pub additional_fields: Map, @@ -114,6 +172,12 @@ impl Default for Properties { fn default() -> Properties { Properties { datetime: Some(Utc::now().to_rfc3339()), + start_datetime: None, + end_datetime: None, + title: None, + description: None, + created: None, + updated: None, additional_fields: Map::new(), } } From 81e27cf0a74d90c2a91817fb7861b36cd018732c Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 05:43:49 -0600 Subject: [PATCH 03/25] fix: deprecate, don't remove, intersects_bbox --- RELEASING.md | 7 ++++--- stac/CHANGELOG.md | 4 ++-- stac/src/item.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index c76756f3..0427f330 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -8,9 +8,10 @@ 4. Update the package's `Cargo.toml` file accordingly, and update the other packages' `Cargo.toml` if they depend on this package. 5. Scan the package's README for references to version numbers, and update any that are needed. 6. Update the package's CHANGELOG with a new section for the new version. Don't forget to update the links at the bottom, too. -7. Test the release with `cargo release -p {package name}`. By default, this does a dry-run, so it won't actually do anything. -8. Use the normal pull request workflow to merge your branch. -9. Once merged, run `cargo release --execute` to do the release. Use the same `-p` flags as you did during the dry run. +7. If it's a breaking release, search for any deprecated functions that should be removed. +8. Test the release with `cargo release -p {package name}`. By default, this does a dry-run, so it won't actually do anything. +9. Use the normal pull request workflow to merge your branch. +10. Once merged, run `cargo release --execute` to do the release. Use the same `-p` flags as you did during the dry run. ## After-the-fact releases diff --git a/stac/CHANGELOG.md b/stac/CHANGELOG.md index 6b3db09e..f0faf490 100644 --- a/stac/CHANGELOG.md +++ b/stac/CHANGELOG.md @@ -11,9 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - `Item.intersects` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) - Common metadata fields ([#203](https://github.com/stac-utils/stac-rs/pull/203)) -### Removed +### Deprecated -- `Item.intersects_bbox` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) +- `Item.intersects_bbox` ([#204](https://github.com/stac-utils/stac-rs/pull/204)) ## [0.5.1] - 2023-09-14 diff --git a/stac/src/item.rs b/stac/src/item.rs index 5f0dd80a..15371fe6 100644 --- a/stac/src/item.rs +++ b/stac/src/item.rs @@ -303,6 +303,34 @@ impl Item { } } + /// Returns true if this item's geometry intersects the provided bounding box. + /// + /// DEPRECATED Use `intersects` instead. + /// + /// # Examples + /// + /// ``` + /// use stac::Item; + /// use geojson::{Geometry, Value}; + /// + /// let mut item = Item::new("an-id"); + /// item.set_geometry(Some(Geometry::new(Value::Point(vec![-105.1, 41.1])))); + /// let bbox = stac::geo::bbox(&vec![-106.0, 41.0, -105.0, 42.0]).unwrap(); + /// assert!(item.intersects_bbox(bbox).unwrap()); + /// ``` + #[cfg(feature = "geo")] + #[deprecated(since = "0.5.2", note = "Use intersects instead")] + pub fn intersects_bbox(&self, bbox: geo::Rect) -> Result { + use geo::Intersects; + + if let Some(geometry) = self.geometry.clone() { + let geometry: geo::Geometry = geometry.try_into()?; + Ok(geometry.intersects(&bbox)) + } else { + Ok(false) + } + } + /// Returns true if this item's datetime (or start and end datetimes) /// intersects the provided datetime. /// From 8123f1fd7d27266434c9ab9fcdc9690463484a78 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 05:59:10 -0600 Subject: [PATCH 04/25] release: stac v0.5.2 --- stac/CHANGELOG.md | 5 ++++- stac/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/stac/CHANGELOG.md b/stac/CHANGELOG.md index f0faf490..849d8586 100644 --- a/stac/CHANGELOG.md +++ b/stac/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [0.5.2] - 2023-10-18 + ### Added - `Item.intersects` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) @@ -248,7 +250,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), Initial release. -[Unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.1...main +[Unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.2...main +[0.5.2]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.1...stac-v0.5.2 [0.5.1]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.0...stac-v0.5.1 [0.5.0]: https://github.com/stac-utils/stac-rs/compare/stac-v0.4.0...stac-v0.5.0 [0.4.0]: https://github.com/stac-utils/stac-rs/compare/stac-v0.3.2...stac-v0.4.0 diff --git a/stac/Cargo.toml b/stac/Cargo.toml index 339d0336..e2007a50 100644 --- a/stac/Cargo.toml +++ b/stac/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stac" -version = "0.5.1" +version = "0.5.2" authors = ["Pete Gadomski "] edition = "2021" description = "Rust library for the SpatioTemporal Asset Catalog (STAC) specification" From 95442a472badab6636aa70aab337824a21fb0f1b Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 06:39:43 -0600 Subject: [PATCH 05/25] feat: add Geometry::point --- stac/src/geometry.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++ stac/src/item.rs | 27 +----------------- stac/src/lib.rs | 4 ++- 3 files changed, 71 insertions(+), 27 deletions(-) create mode 100644 stac/src/geometry.rs diff --git a/stac/src/geometry.rs b/stac/src/geometry.rs new file mode 100644 index 00000000..d597e8b8 --- /dev/null +++ b/stac/src/geometry.rs @@ -0,0 +1,67 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; + +#[cfg(feature = "geo")] +use crate::{Error, Result}; + +/// Additional metadata fields can be added to the GeoJSON Object Properties. +/// +/// We can't just use the [geojson] crate because it doesn't implement [schemars]. +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct Geometry { + /// The geometry type. + pub r#type: String, + + /// The other geometry attributes. + /// + /// `GeometryCollection` doesn't have a `coordinates` member, so we must + /// capture everything in a flat, generic array. + #[serde(flatten)] + pub attributes: Map, +} + +impl Geometry { + /// Creates a point geometry. + /// + /// # Examples + /// + /// ``` + /// use stac::Geometry; + /// let geometry = Geometry::point(-108.0, 42.0); + /// ``` + pub fn point(x: f64, y: f64) -> Geometry { + use serde_json::json; + + let mut attributes = Map::new(); + let _ = attributes.insert("coordinates".to_string(), json!([x, y])); + Geometry { + r#type: "Point".to_string(), + attributes, + } + } +} + +#[cfg(feature = "geo")] +impl TryFrom for geo::Geometry { + type Error = Error; + fn try_from(geometry: Geometry) -> Result { + serde_json::from_value::(serde_json::to_value(geometry)?)? + .try_into() + .map_err(Error::from) + } +} + +#[cfg(test)] +mod tests { + #[test] + #[cfg(feature = "geo")] + fn point() { + let point = super::Geometry::point(-108.0, 42.0); + let geometry: geo::Geometry = point.try_into().unwrap(); + assert_eq!( + geometry, + geo::Geometry::Point(geo::Point::new(-108.0, 42.0)) + ); + } +} diff --git a/stac/src/item.rs b/stac/src/item.rs index 15371fe6..9027a182 100644 --- a/stac/src/item.rs +++ b/stac/src/item.rs @@ -1,4 +1,4 @@ -use crate::{Asset, Assets, Error, Extensions, Href, Link, Links, Result, STAC_VERSION}; +use crate::{Asset, Assets, Error, Extensions, Geometry, Href, Link, Links, Result, STAC_VERSION}; use chrono::{DateTime, FixedOffset, Utc}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; @@ -81,21 +81,6 @@ pub struct Item { href: Option, } -/// Additional metadata fields can be added to the GeoJSON Object Properties. -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct Geometry { - /// The geometry type. - pub r#type: String, - - /// The other geometry attributes. - /// - /// `GeometryCollection` doesn't have a `coordinates` member, so we must - /// capture everything in a flat, generic array. - #[serde(flatten)] - pub attributes: Map, -} - /// Additional metadata fields can be added to the GeoJSON Object Properties. #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] pub struct Properties { @@ -441,16 +426,6 @@ impl TryFrom> for Item { } } -#[cfg(feature = "geo")] -impl TryFrom for geo::Geometry { - type Error = Error; - fn try_from(geometry: Geometry) -> Result { - serde_json::from_value::(serde_json::to_value(geometry)?)? - .try_into() - .map_err(Error::from) - } -} - fn deserialize_type<'de, D>(deserializer: D) -> std::result::Result where D: serde::de::Deserializer<'de>, diff --git a/stac/src/lib.rs b/stac/src/lib.rs index 1e6703bf..7b001892 100644 --- a/stac/src/lib.rs +++ b/stac/src/lib.rs @@ -118,6 +118,7 @@ mod error; mod extensions; #[cfg(feature = "geo")] pub mod geo; +mod geometry; mod href; mod io; mod item; @@ -132,9 +133,10 @@ pub use { collection::{Collection, Extent, Provider, SpatialExtent, TemporalExtent, COLLECTION_TYPE}, error::Error, extensions::Extensions, + geometry::Geometry, href::{href_to_url, Href}, io::{read, read_json}, - item::{Geometry, Item, Properties, ITEM_TYPE}, + item::{Item, Properties, ITEM_TYPE}, item_collection::{ItemCollection, ITEM_COLLECTION_TYPE}, link::{Link, Links}, value::Value, From 2767c636337287241ad5205d247b42747a9ace80 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 06:41:56 -0600 Subject: [PATCH 06/25] feat: add Search.validate --- stac-api/src/error.rs | 5 +++++ stac-api/src/search.rs | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/stac-api/src/error.rs b/stac-api/src/error.rs index 41ba43df..a47a6619 100644 --- a/stac-api/src/error.rs +++ b/stac-api/src/error.rs @@ -1,3 +1,4 @@ +use crate::Search; use serde_json::{Map, Value}; use thiserror::Error; @@ -23,6 +24,10 @@ pub enum Error { #[error(transparent)] ParseFloatError(#[from] std::num::ParseFloatError), + /// A search has both bbox and intersects. + #[error("search has bbox and intersects")] + SearchHasBboxAndIntersects(Search), + /// [serde_json::Error] #[error(transparent)] SerdeJson(#[from] serde_json::Error), diff --git a/stac-api/src/search.rs b/stac-api/src/search.rs index aadbd585..63d1d703 100644 --- a/stac-api/src/search.rs +++ b/stac-api/src/search.rs @@ -123,6 +123,29 @@ pub struct GetSearch { pub additional_fields: HashMap, } +impl Search { + /// Validates this search. + /// + /// E.g. the search is invalid if both bbox and intersects are specified. + /// + /// # Examples + /// + /// ``` + /// use stac_api::Search; + /// let mut search = Search { bbox: Some(vec![-180.0, -90.0, 180.0, 80.0]), ..Default::default() }; + /// search.validate().unwrap(); + /// search.intersects = Some(stac::Geometry::point(0., 0.)); + /// let _ = search.validate().unwrap_err(); + /// ``` + pub fn validate(&self) -> Result<()> { + if self.bbox.is_some() & self.intersects.is_some() { + Err(Error::SearchHasBboxAndIntersects(self.clone())) + } else { + Ok(()) + } + } +} + impl TryFrom for GetSearch { type Error = Error; From a0536660bd220ab3b678f4259c5919570a69ed84 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 06:45:52 -0600 Subject: [PATCH 07/25] chore: update changelog --- stac-api/CHANGELOG.md | 4 ++++ stac/CHANGELOG.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/stac-api/CHANGELOG.md b/stac-api/CHANGELOG.md index 2203d253..c663b6df 100644 --- a/stac-api/CHANGELOG.md +++ b/stac-api/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- `Search.validate` ([#206](https://github.com/stac-utils/stac-rs/pull/206)) + ## [0.3.2] - 2023-10-11 ### Added diff --git a/stac/CHANGELOG.md b/stac/CHANGELOG.md index 849d8586..6d0ab4a2 100644 --- a/stac/CHANGELOG.md +++ b/stac/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- `Geometry::point` ([#206](https://github.com/stac-utils/stac-rs/pull/206)) + ## [0.5.2] - 2023-10-18 ### Added From c5cc7b47710f44e6c0b0ab5f2f56fcfcd6e8be32 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 08:22:50 -0600 Subject: [PATCH 08/25] feat: validate collections endpoint --- stac-cli/CHANGELOG.md | 4 +++ stac-cli/src/command.rs | 10 ++++-- stac-cli/src/commands/validate.rs | 59 ++++++++++++++++++++++++++----- stac-cli/src/error.rs | 3 ++ 4 files changed, 66 insertions(+), 10 deletions(-) diff --git a/stac-cli/CHANGELOG.md b/stac-cli/CHANGELOG.md index e2fe80f8..74ac8103 100644 --- a/stac-cli/CHANGELOG.md +++ b/stac-cli/CHANGELOG.md @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- Validation for the collections endpoint ([#208](https://github.com/stac-utils/stac-rs/pull/208)) + ## [0.0.5] - 2023-10-11 ### Added diff --git a/stac-cli/src/command.rs b/stac-cli/src/command.rs index eb5eb79a..56680a0d 100644 --- a/stac-cli/src/command.rs +++ b/stac-cli/src/command.rs @@ -83,9 +83,15 @@ pub enum Command { compact: bool, }, - /// Validates a STAC object using json-schema validation. + /// Validates a STAC object or API endpoint using json-schema validation. Validate { - /// The href of the STAC object. + /// The href of the STAC object or endpoint. + /// + /// The validator will make some decisions depending on what type of + /// data is returned from the href. If it's a STAC Catalog, Collection, + /// or Item, that object will be validated. If its a collections + /// endpoint from a STAC API, all collections will be validated. + /// Additional behavior TBD. href: String, }, } diff --git a/stac-cli/src/commands/validate.rs b/stac-cli/src/commands/validate.rs index 2ed8b65e..c25d1b3b 100644 --- a/stac-cli/src/commands/validate.rs +++ b/stac-cli/src/commands/validate.rs @@ -1,12 +1,55 @@ -use crate::Result; -use stac_validate::Validate; +use crate::{Error, Result}; +use stac_validate::{Validate, Validator}; pub async fn validate(href: &str) -> Result<()> { let value: serde_json::Value = stac_async::read_json(href).await?; - let result = { - let value = value.clone(); - tokio::task::spawn_blocking(move || value.validate()).await? - }; + if let Some(map) = value.as_object() { + if map.contains_key("type") { + let value = value.clone(); + let result = tokio::task::spawn_blocking(move || value.validate()).await?; + print_result(result).map_err(Error::from) + } else if let Some(collections) = map + .get("collections") + .and_then(|collections| collections.as_array()) + { + let collections = collections.clone(); + let result = tokio::task::spawn_blocking(move || { + let mut errors = Vec::new(); + let mut validator = Validator::new(); + let num_collections = collections.len(); + let mut valid_collections = 0; + for collection in collections { + if let Some(id) = collection.get("id").and_then(|id| id.as_str()) { + println!("== Validating {}", id); + } + let result = validator.validate(collection); + match print_result(result) { + Ok(()) => valid_collections += 1, + Err(err) => errors.push(err), + } + println!("") + } + println!( + "{}/{} collections are valid", + valid_collections, num_collections + ); + if errors.is_empty() { + Ok(()) + } else { + Err(Error::ValidationGroup(errors)) + } + }) + .await?; + result + } else { + todo!() + } + } else { + todo!() + } +} + +pub fn print_result(result: stac_validate::Result<()>) -> stac_validate::Result<()> { match result { Ok(()) => { println!("OK!"); @@ -16,11 +59,11 @@ pub async fn validate(href: &str) -> Result<()> { for err in &errors { println!("Validation error at {}: {}", err.instance_path, err) } - Err(stac_validate::Error::Validation(errors).into()) + Err(stac_validate::Error::Validation(errors)) } Err(err) => { println!("Error while validating: {}", err); - Err(err.into()) + Err(err) } } } diff --git a/stac-cli/src/error.rs b/stac-cli/src/error.rs index a9c5518d..6ccb2022 100644 --- a/stac-cli/src/error.rs +++ b/stac-cli/src/error.rs @@ -24,6 +24,9 @@ pub enum Error { #[error(transparent)] TokioJoinError(#[from] tokio::task::JoinError), + + #[error("many validation errors")] + ValidationGroup(Vec), } impl Error { From a1299604811dd3fc1b6fda9dc78608a43a76274d Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Wed, 18 Oct 2023 08:34:51 -0600 Subject: [PATCH 09/25] release: stac-cli v0.0.6 --- stac-cli/CHANGELOG.md | 5 ++++- stac-cli/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/stac-cli/CHANGELOG.md b/stac-cli/CHANGELOG.md index 74ac8103..f0732cf2 100644 --- a/stac-cli/CHANGELOG.md +++ b/stac-cli/CHANGELOG.md @@ -7,6 +7,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [0.0.6] - 2023-10-18 + ### Added - Validation for the collections endpoint ([#208](https://github.com/stac-utils/stac-rs/pull/208)) @@ -37,7 +39,8 @@ Moved over from [stac-incubator-rs](https://github.com/gadomski/stac-incubator-r - Downloading ([#142](https://github.com/stac-utils/stac-rs/pull/142), [#152](https://github.com/stac-utils/stac-rs/pull/152)) - Validation ([#155](https://github.com/stac-utils/stac-rs/pull/155)) -[Unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-cli-v0.0.5..main +[Unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-cli-v0.0.6..main +[0.0.6]: https://github.com/stac-utils/stac-rs/compare/stac-cli-v0.0.5..stac-cli-v0.0.6 [0.0.5]: https://github.com/stac-utils/stac-rs/compare/stac-cli-v0.0.4..stac-cli-v0.0.5 [0.0.4]: https://github.com/stac-utils/stac-rs/compare/stac-cli-v0.0.3..stac-cli-v0.0.4 [0.0.3]: https://github.com/stac-utils/stac-rs/tree/stac-cli-v0.0.3 diff --git a/stac-cli/Cargo.toml b/stac-cli/Cargo.toml index c56f7116..29fbd557 100644 --- a/stac-cli/Cargo.toml +++ b/stac-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stac-cli" -version = "0.0.5" +version = "0.0.6" edition = "2021" description = "Command line interface for stac-rs" documentation = "https://docs.rs/stac-cli" From 9f4342d39c9e1f4fee3f2d254b367c8144c88661 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 19 Oct 2023 07:08:36 -0600 Subject: [PATCH 10/25] feat: add search matching --- stac-api/CHANGELOG.md | 3 +- stac-api/Cargo.toml | 5 + stac-api/README.md | 10 +- stac-api/src/error.rs | 8 ++ stac-api/src/filter.rs | 6 ++ stac-api/src/lib.rs | 3 + stac-api/src/search.rs | 239 ++++++++++++++++++++++++++++++++++++++++- stac/CHANGELOG.md | 5 +- stac/src/item.rs | 16 +++ 9 files changed, 290 insertions(+), 5 deletions(-) diff --git a/stac-api/CHANGELOG.md b/stac-api/CHANGELOG.md index c663b6df..39c9f3ca 100644 --- a/stac-api/CHANGELOG.md +++ b/stac-api/CHANGELOG.md @@ -8,7 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added -- `Search.validate` ([#206](https://github.com/stac-utils/stac-rs/pull/206)) +- `Search::validate` ([#206](https://github.com/stac-utils/stac-rs/pull/206)) +- `geo` feature, `Search::matches` and sub-methods, `Search::new`, `Search::ids`, `Default` for `Filter`, `Error::Stac`, and `Error::Unimplemented` ([#209](https://github.com/stac-utils/stac-rs/pull/209)) ## [0.3.2] - 2023-10-11 diff --git a/stac-api/Cargo.toml b/stac-api/Cargo.toml index 32d0e11c..a4f1523f 100644 --- a/stac-api/Cargo.toml +++ b/stac-api/Cargo.toml @@ -11,9 +11,11 @@ keywords = ["geospatial", "stac", "metadata", "geo", "raster"] categories = ["science", "data-structures", "web-programming"] [features] +geo = ["dep:geo", "stac/geo"] schemars = ["dep:schemars", "stac/schemars"] [dependencies] +geo = { version = "0.26", optional = true } schemars = { version = "0.8", optional = true } serde = "1" serde_json = "1" @@ -21,3 +23,6 @@ serde_urlencoded = "0.7" stac = { version = "0.5", path = "../stac" } thiserror = "1" url = "2.3" + +[dev-dependencies] +geojson = "0.24" diff --git a/stac-api/README.md b/stac-api/README.md index e27ba884..ccd703d6 100644 --- a/stac-api/README.md +++ b/stac-api/README.md @@ -19,7 +19,15 @@ To use the library in your project: stac-api = "0.3" ``` -**stac-api** has one optional feature, `schemars`, which can be used to generate [jsonschema](https://json-schema.org/) documents for the API structures. +**stac-api** has two optional features. +`geo` enables `Search::match`: + +```toml +[dependencies] +stac-api = { version = "0.3", features = ["geo"] } +``` + +`schemars`, can be used to generate [jsonschema](https://json-schema.org/) documents for the API structures. This is useful for auto-generating OpenAPI documentation: ```toml diff --git a/stac-api/src/error.rs b/stac-api/src/error.rs index a47a6619..017bb488 100644 --- a/stac-api/src/error.rs +++ b/stac-api/src/error.rs @@ -36,6 +36,10 @@ pub enum Error { #[error(transparent)] SerdeUrlencodedSer(#[from] serde_urlencoded::ser::Error), + /// [stac::Error] + #[error(transparent)] + Stac(#[from] stac::Error), + /// [std::num::TryFromIntError] #[error(transparent)] TryFromInt(#[from] std::num::TryFromIntError), @@ -43,4 +47,8 @@ pub enum Error { /// [url::ParseError] #[error(transparent)] UrlParse(#[from] url::ParseError), + + /// This functionality is not yet implemented. + #[error("this functionality is not yet implemented: {0}")] + Unimplemented(&'static str), } diff --git a/stac-api/src/filter.rs b/stac-api/src/filter.rs index aabb71c6..04de30d0 100644 --- a/stac-api/src/filter.rs +++ b/stac-api/src/filter.rs @@ -15,6 +15,12 @@ pub enum Filter { Cql2Json(Map), } +impl Default for Filter { + fn default() -> Self { + Filter::Cql2Json(Default::default()) + } +} + #[cfg(test)] mod tests { use super::Filter; diff --git a/stac-api/src/lib.rs b/stac-api/src/lib.rs index 4e19d96c..05140349 100644 --- a/stac-api/src/lib.rs +++ b/stac-api/src/lib.rs @@ -104,6 +104,9 @@ pub type Result = std::result::Result; /// servers to explicitly include or exclude certain fields. pub type Item = serde_json::Map; +#[cfg(test)] +use geojson as _; + // From https://github.com/rust-lang/cargo/issues/383#issuecomment-720873790, // may they be forever blessed. #[cfg(doctest)] diff --git a/stac-api/src/search.rs b/stac-api/src/search.rs index 63d1d703..bc738e2e 100644 --- a/stac-api/src/search.rs +++ b/stac-api/src/search.rs @@ -1,7 +1,7 @@ use crate::{Error, Fields, Filter, GetItems, Items, Result, Sortby}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; -use stac::Geometry; +use stac::{Geometry, Item}; use std::collections::HashMap; /// The core parameters for STAC search are defined by OAFeat, and STAC adds a few parameters for convenience. @@ -124,6 +124,32 @@ pub struct GetSearch { } impl Search { + /// Creates a new, empty search. + /// + /// # Examples + /// + /// ``` + /// use stac_api::Search; + /// + /// let search = Search::new(); + /// ``` + pub fn new() -> Search { + Search::default() + } + + /// Sets the ids field of this search. + /// + /// # Examples + /// + /// ``` + /// use stac_api::Search; + /// let search = Search::new().ids(vec!["an-id".to_string()]); + /// ``` + pub fn ids(mut self, ids: impl Into>>) -> Search { + self.ids = ids.into(); + self + } + /// Validates this search. /// /// E.g. the search is invalid if both bbox and intersects are specified. @@ -144,6 +170,217 @@ impl Search { Ok(()) } } + + /// Returns true if this item matches this search. + /// + /// # Examples + /// + /// ``` + /// use stac::Item; + /// use stac_api::Search; + /// + /// let item = Item::new("an-id"); + /// assert!(Search::new().matches(&item).unwrap()); + /// assert!(!Search::new().ids(vec!["not-the-id".to_string()]).matches(&item).unwrap()); + /// ``` + #[cfg(feature = "geo")] + pub fn matches(&self, item: &Item) -> Result { + Ok(self.collection_matches(item) + & self.id_matches(item) + & self.bbox_matches(item)? + & self.intersects_matches(item)? + & self.datetime_matches(item)? + & self.query_matches(item)? + & self.filter_matches(item)?) + } + + /// Returns true if this item's collection matches this search. + /// + /// # Examples + /// + /// ``` + /// use stac_api::Search; + /// use stac::Item; + /// + /// let mut search = Search::new(); + /// let mut item = Item::new("item-id"); + /// assert!(search.collection_matches(&item)); + /// search.collections = Some(vec!["collection-id".to_string()]); + /// assert!(!search.collection_matches(&item)); + /// item.collection = Some("collection-id".to_string()); + /// assert!(search.collection_matches(&item)); + /// item.collection = Some("another-collection-id".to_string()); + /// assert!(!search.collection_matches(&item)); + /// ``` + pub fn collection_matches(&self, item: &Item) -> bool { + if let Some(collections) = self.collections.as_ref() { + if let Some(collection) = item.collection.as_ref() { + collections.contains(collection) + } else { + false + } + } else { + true + } + } + + /// Returns true if this item's id matches this search. + /// + /// # Examples + /// + /// ``` + /// use stac_api::Search; + /// use stac::Item; + /// + /// let mut search = Search::new(); + /// let mut item = Item::new("item-id"); + /// assert!(search.id_matches(&item)); + /// search.ids = Some(vec!["item-id".to_string()]); + /// assert!(search.id_matches(&item)); + /// search.ids = Some(vec!["another-id".to_string()]); + /// assert!(!search.id_matches(&item)); + /// ``` + pub fn id_matches(&self, item: &Item) -> bool { + if let Some(ids) = self.ids.as_ref() { + ids.contains(&item.id) + } else { + true + } + } + + /// Returns true if this item's geometry matches this search's bbox. + /// + /// # Examples + /// + /// ``` + /// # #[cfg(feature = "geo")] + /// # { + /// use stac_api::Search; + /// use stac::Item; + /// use geojson::{Geometry, Value}; + /// + /// let mut search = Search::new(); + /// let mut item = Item::new("item-id"); + /// assert!(search.bbox_matches(&item).unwrap()); + /// search.bbox = Some(vec![-110.0, 40.0, -100.0, 50.0]); + /// assert!(!search.bbox_matches(&item).unwrap()); + /// item.set_geometry(Geometry::new(Value::Point(vec![-105.1, 41.1]))); + /// assert!(search.bbox_matches(&item).unwrap()); + /// # } + /// ``` + #[cfg(feature = "geo")] + pub fn bbox_matches(&self, item: &Item) -> Result { + if let Some(bbox) = self.bbox.as_ref() { + let bbox = stac::geo::bbox(bbox)?; + item.intersects(&bbox).map_err(Error::from) + } else { + Ok(true) + } + } + + /// Returns true if this item's geometry matches this search's intersects. + /// + /// # Examples + /// + /// ``` + /// # #[cfg(feature = "geo")] + /// # { + /// use stac_api::Search; + /// use stac::Item; + /// use geojson::{Geometry, Value}; + /// + /// let mut search = Search::new(); + /// let mut item = Item::new("item-id"); + /// assert!(search.intersects_matches(&item).unwrap()); + /// search.intersects = Some(stac::Geometry::point(-105.1, 41.1)); + /// assert!(!search.intersects_matches(&item).unwrap()); + /// item.set_geometry(Geometry::new(Value::Point(vec![-105.1, 41.1]))); + /// assert!(search.intersects_matches(&item).unwrap()); + /// # } + /// ``` + #[cfg(feature = "geo")] + pub fn intersects_matches(&self, item: &Item) -> Result { + if let Some(intersects) = self.intersects.clone() { + let intersects: geo::Geometry = intersects.try_into()?; + item.intersects(&intersects).map_err(Error::from) + } else { + Ok(true) + } + } + + /// Returns true if this item's datetime matches this search. + /// + /// # Examples + /// + /// ``` + /// use stac_api::Search; + /// use stac::Item; + /// + /// let mut search = Search::new(); + /// let mut item = Item::new("item-id"); // default datetime is now + /// assert!(search.datetime_matches(&item).unwrap()); + /// search.datetime = Some("../2023-10-09T00:00:00Z".to_string()); + /// assert!(!search.datetime_matches(&item).unwrap()); + /// item.properties.datetime = Some("2023-10-08T00:00:00Z".to_string()); + /// assert!(search.datetime_matches(&item).unwrap()); + /// ``` + pub fn datetime_matches(&self, item: &Item) -> Result { + if let Some(datetime) = self.datetime.as_ref() { + item.intersects_datetime_str(datetime).map_err(Error::from) + } else { + Ok(true) + } + } + + /// Returns true if this item's matches this search query. + /// + /// Currently unsupported, always raises an error if query is set. + /// + /// # Examples + /// + /// ``` + /// use stac_api::Search; + /// use stac::Item; + /// + /// let mut search = Search::new(); + /// let mut item = Item::new("item-id"); + /// assert!(search.query_matches(&item).unwrap()); + /// search.query = Some(Default::default()); + /// assert!(search.query_matches(&item).is_err()); + /// ``` + pub fn query_matches(&self, _: &Item) -> Result { + if let Some(_) = self.query.as_ref() { + // TODO implement + Err(Error::Unimplemented("query")) + } else { + Ok(true) + } + } + + /// Returns true if this item matches this search's filter. + /// + /// Currently unsupported, always raises an error if filter is set. + /// + /// # Examples + /// + /// ``` + /// use stac_api::Search; + /// use stac::Item; + /// + /// let mut search = Search::new(); + /// let mut item = Item::new("item-id"); + /// assert!(search.filter_matches(&item).unwrap()); + /// search.filter = Some(Default::default()); + /// assert!(search.filter_matches(&item).is_err()); + /// ``` + pub fn filter_matches(&self, _: &Item) -> Result { + if let Some(_) = self.filter.as_ref() { + // TODO implement + Err(Error::Unimplemented("filter")) + } else { + Ok(true) + } + } } impl TryFrom for GetSearch { diff --git a/stac/CHANGELOG.md b/stac/CHANGELOG.md index 6d0ab4a2..b6af2c7d 100644 --- a/stac/CHANGELOG.md +++ b/stac/CHANGELOG.md @@ -9,17 +9,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - `Geometry::point` ([#206](https://github.com/stac-utils/stac-rs/pull/206)) +- `Item::intersects_datetime_str` ([#209](https://github.com/stac-utils/stac-rs/pull/209)) ## [0.5.2] - 2023-10-18 ### Added -- `Item.intersects` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) +- `Item::intersects` ([#202](https://github.com/stac-utils/stac-rs/pull/202)) - Common metadata fields ([#203](https://github.com/stac-utils/stac-rs/pull/203)) ### Deprecated -- `Item.intersects_bbox` ([#204](https://github.com/stac-utils/stac-rs/pull/204)) +- `Item::intersects_bbox` ([#204](https://github.com/stac-utils/stac-rs/pull/204)) ## [0.5.1] - 2023-09-14 diff --git a/stac/src/item.rs b/stac/src/item.rs index 9027a182..421e47c9 100644 --- a/stac/src/item.rs +++ b/stac/src/item.rs @@ -316,6 +316,22 @@ impl Item { } } + /// Returns true if this item's datetime (or start and end datetime) + /// intersects the provided datetime string. + /// + /// # Examples + /// + /// ``` + /// use stac::Item; + /// let mut item = Item::new("an-id"); + /// item.properties.datetime = Some("2023-07-11T12:00:00Z".to_string()); + /// assert!(item.intersects_datetime_str("2023-07-11T00:00:00Z/2023-07-12T00:00:00Z").unwrap()); + /// ``` + pub fn intersects_datetime_str(&self, datetime: &str) -> Result { + let (start, end) = crate::datetime::parse(datetime)?; + self.intersects_datetimes(start, end) + } + /// Returns true if this item's datetime (or start and end datetimes) /// intersects the provided datetime. /// From 24e78e6cd073f03a676ac59686a1f5f62572719e Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Mon, 23 Oct 2023 14:26:35 -0400 Subject: [PATCH 11/25] feat: add geometry::rect --- stac/src/geometry.rs | 51 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/stac/src/geometry.rs b/stac/src/geometry.rs index d597e8b8..c981f83f 100644 --- a/stac/src/geometry.rs +++ b/stac/src/geometry.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use serde_json::{Map, Value}; +use serde_json::{json, Map, Value}; #[cfg(feature = "geo")] use crate::{Error, Result}; @@ -31,8 +31,6 @@ impl Geometry { /// let geometry = Geometry::point(-108.0, 42.0); /// ``` pub fn point(x: f64, y: f64) -> Geometry { - use serde_json::json; - let mut attributes = Map::new(); let _ = attributes.insert("coordinates".to_string(), json!([x, y])); Geometry { @@ -40,6 +38,32 @@ impl Geometry { attributes, } } + + /// Creates a geometry. + /// + /// # Examples + /// + /// ``` + /// use stac::Geometry; + /// let geometry = Geometry::rect(-108.0, 42.0, -107.0, 43.0); + /// ``` + pub fn rect(xmin: f64, ymin: f64, xmax: f64, ymax: f64) -> Geometry { + let mut attributes = Map::new(); + let _ = attributes.insert( + "coordinates".to_string(), + json!([[ + [xmin, ymin], + [xmax, ymin], + [xmax, ymax], + [xmin, ymax], + [xmin, ymin] + ]]), + ); + Geometry { + r#type: "Polygon".to_string(), + attributes, + } + } } #[cfg(feature = "geo")] @@ -52,16 +76,29 @@ impl TryFrom for geo::Geometry { } } -#[cfg(test)] +#[cfg(all(test, feature = "geo"))] mod tests { + use geo::{algorithm::orient::Direction, Orient, Point, Polygon, Rect}; + #[test] - #[cfg(feature = "geo")] fn point() { let point = super::Geometry::point(-108.0, 42.0); let geometry: geo::Geometry = point.try_into().unwrap(); + assert_eq!(geometry, geo::Geometry::Point(Point::new(-108.0, 42.0))); + } + + #[test] + fn rect() { + let rect = super::Geometry::rect(-108.0, 42.0, -107.0, 43.0); + let geometry: geo::Geometry = rect.try_into().unwrap(); assert_eq!( - geometry, - geo::Geometry::Point(geo::Point::new(-108.0, 42.0)) + Polygon::try_from(geometry).unwrap(), + Rect::new( + geo::coord! { x: -108.0, y: 42.0 }, + geo::coord! { x: -107.0, y: 43.0 }, + ) + .to_polygon() + .orient(Direction::Default) ); } } From 550ff74a033f0daa1baa2726e6a4ad1d7c14da20 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 9 Nov 2023 05:53:48 -0700 Subject: [PATCH 12/25] fix: remove unused code set-query is gone but we left some cruft. --- stac/src/error.rs | 5 ----- stac/src/link.rs | 10 ---------- 2 files changed, 15 deletions(-) diff --git a/stac/src/error.rs b/stac/src/error.rs index 4e64b6bd..2fe0ecc8 100644 --- a/stac/src/error.rs +++ b/stac/src/error.rs @@ -73,11 +73,6 @@ pub enum Error { #[error(transparent)] SerdeJson(#[from] serde_json::Error), - /// [serde_urlencoded::ser::Error] - #[cfg(feature = "set_query")] - #[error(transparent)] - SerdeUrlencodedSer(#[from] serde_urlencoded::ser::Error), - /// Returned when the `type` field of a STAC object does not equal `"Feature"`, `"Catalog"`, or `"Collection"`. #[error("unknown \"type\": {0}")] UnknownType(String), diff --git a/stac/src/link.rs b/stac/src/link.rs index 84b0db53..28025ed2 100644 --- a/stac/src/link.rs +++ b/stac/src/link.rs @@ -677,16 +677,6 @@ mod tests { assert!(value.get("title").is_none()); } - #[test] - #[cfg(feature = "set_query")] - fn set_query_pair() { - let mut link = Link::new("http://stac-rs.test/an-href", "a-rel"); - link.set_query([("foo", "bar")]).unwrap(); - assert_eq!(link.href, "http://stac-rs.test/an-href?foo=bar"); - link.set_query([("baz", "boz")]).unwrap(); - assert_eq!(link.href, "http://stac-rs.test/an-href?baz=boz"); - } - mod links { use crate::{Catalog, Item, Link, Links}; From dd79af18d1b111ac35f3b0c80e6689f9f4f1cd06 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 9 Nov 2023 06:48:25 -0700 Subject: [PATCH 13/25] feat: add make_absolute_links_relative --- stac/src/link.rs | 129 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/stac/src/link.rs b/stac/src/link.rs index 28025ed2..605d8956 100644 --- a/stac/src/link.rs +++ b/stac/src/link.rs @@ -237,6 +237,31 @@ pub trait Links { Ok(()) } + /// Makes all absolute links relative with respect to an href. + /// + /// If they do not share a root, the link will be made absolute. + /// + /// # Examples + /// + /// ``` + /// use stac::{Links, Catalog, Error, Href}; + /// + /// let mut catalog: stac::Catalog = stac::read("data/catalog.json").unwrap(); + /// assert!(!catalog.root_link().unwrap().is_absolute()); + /// catalog.make_relative_links_absolute("data/catalog.json").unwrap(); + /// assert!(catalog.root_link().unwrap().is_absolute()); + /// catalog.make_absolute_links_relative("data/catalog.json").unwrap(); + /// assert!(catalog.root_link().unwrap().is_relative()); + /// ``` + fn make_absolute_links_relative(&mut self, href: impl ToString) -> Result<()> { + let href = make_absolute(href.to_string(), None)?; + for link in self.links_mut() { + let absolute_link_href = make_absolute(std::mem::take(&mut link.href), Some(&href))?; + link.href = make_relative(&absolute_link_href, &href); + } + Ok(()) + } + /// Removes all relative links. /// /// This can be useful e.g. if you're relocating a STAC object, but it @@ -608,6 +633,21 @@ impl Link { pub fn is_absolute(&self) -> bool { is_absolute(&self.href) } + + /// Returns true if this link's href is a relative path. + /// + /// # Examples + /// + /// ``` + /// use stac::Link; + /// + /// assert!(!Link::new("/a/local/path/item.json", "rel").is_relative()); + /// assert!(!Link::new("http://stac-rs.test/item.json", "rel").is_relative()); + /// assert!(Link::new("./not/an/absolute/path", "rel").is_relative()); + /// ``` + pub fn is_relative(&self) -> bool { + !is_absolute(&self.href) + } } fn is_absolute(href: &str) -> bool { @@ -638,6 +678,69 @@ fn make_absolute(href: String, base: Option<&str>) -> Result { } } +fn make_relative(href: &str, base: &str) -> String { + // Cribbed from `Url::make_relative` + let mut relative = String::new(); + + fn extract_path_filename(s: &str) -> (&str, &str) { + let last_slash_idx = s.rfind('/').unwrap_or(0); + let (path, filename) = s.split_at(last_slash_idx); + if filename.is_empty() { + (path, "") + } else { + (path, &filename[1..]) + } + } + + let (base_path, base_filename) = extract_path_filename(base); + let (href_path, href_filename) = extract_path_filename(href); + + let mut base_path = base_path.split('/').peekable(); + let mut href_path = href_path.split('/').peekable(); + + while base_path.peek().is_some() && base_path.peek() == href_path.peek() { + let _ = base_path.next(); + let _ = href_path.next(); + } + + for base_path_segment in base_path { + if base_path_segment.is_empty() { + break; + } + + if !relative.is_empty() { + relative.push('/'); + } + + relative.push_str(".."); + } + + for href_path_segment in href_path { + if relative.is_empty() { + relative.push_str("./"); + } else { + relative.push('/'); + } + + relative.push_str(href_path_segment); + } + + if !relative.is_empty() || base_filename != href_filename { + if href_filename.is_empty() { + relative.push('/'); + } else { + if relative.is_empty() { + relative.push_str("./"); + } else { + relative.push('/'); + } + relative.push_str(href_filename); + } + } + + relative +} + fn normalize_path(path: &str) -> String { let mut parts = if path.starts_with('/') { Vec::new() @@ -730,6 +833,32 @@ mod tests { ); } + #[test] + fn make_absolute_links_relative_path() { + let mut catalog: Catalog = crate::read("data/catalog.json").unwrap(); + catalog + .make_relative_links_absolute("data/catalog.json") + .unwrap(); + catalog.make_absolute_links_relative("data/").unwrap(); + for link in catalog.links() { + if !link.is_self() { + assert!(link.is_relative(), "{}", link.href); + } + } + } + + #[test] + fn make_absolute_links_relative_url() { + let mut catalog: Catalog = crate::read("data/catalog.json").unwrap(); + catalog + .make_relative_links_absolute("http://stac-rs.test/catalog.json") + .unwrap(); + catalog + .make_absolute_links_relative("http://stac-rs.test/") + .unwrap(); + assert_eq!(catalog.root_link().unwrap().href, "./catalog.json"); + } + #[test] fn remove_relative_links() { let mut catalog = Catalog::new("an-id", "a description"); From e534de28e175a609b815ceaacae3819454c23bda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 02:58:41 +0000 Subject: [PATCH 14/25] chore(deps): update geo requirement from 0.26 to 0.27 in /stac Updates the requirements on [geo](https://github.com/georust/geo) to permit the latest version. - [Changelog](https://github.com/georust/geo/blob/main/CHANGES.md) - [Commits](https://github.com/georust/geo/compare/geo-0.26.0...geo-0.27.0) --- updated-dependencies: - dependency-name: geo dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- stac/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stac/Cargo.toml b/stac/Cargo.toml index e2007a50..19e7ff91 100644 --- a/stac/Cargo.toml +++ b/stac/Cargo.toml @@ -17,7 +17,7 @@ schemars = ["dep:schemars"] [dependencies] chrono = "0.4" -geo = { version = "0.26", optional = true } +geo = { version = "0.27", optional = true } geojson = { version = "0.24", optional = true } reqwest = { version = "0.11", optional = true, features = ["json", "blocking"] } schemars = { version = "0.8", optional = true } From 3eb7e0bb6ad1115094f5d294ad80ae6d0fd73a26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 02:18:39 +0000 Subject: [PATCH 15/25] chore(deps): update geo requirement from 0.26 to 0.27 in /stac-api Updates the requirements on [geo](https://github.com/georust/geo) to permit the latest version. - [Changelog](https://github.com/georust/geo/blob/main/CHANGES.md) - [Commits](https://github.com/georust/geo/compare/geo-0.26.0...geo-0.27.0) --- updated-dependencies: - dependency-name: geo dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- stac-api/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stac-api/Cargo.toml b/stac-api/Cargo.toml index a4f1523f..fdd3825f 100644 --- a/stac-api/Cargo.toml +++ b/stac-api/Cargo.toml @@ -15,7 +15,7 @@ geo = ["dep:geo", "stac/geo"] schemars = ["dep:schemars", "stac/schemars"] [dependencies] -geo = { version = "0.26", optional = true } +geo = { version = "0.27", optional = true } schemars = { version = "0.8", optional = true } serde = "1" serde_json = "1" From c869f43d29cdad28f4c6b33e7ee02357d79e4189 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 02:48:05 +0000 Subject: [PATCH 16/25] chore(deps): bump actions/upload-artifact from 3 to 4 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1528fec0..619aa731 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: tar czvf $ARCHIVE_FILE -C target/release stac shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 - name: Upload - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: binaries path: | From 2ea9d74029d1e6efbce902eeeea867840f9f0d4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 02:48:02 +0000 Subject: [PATCH 17/25] chore(deps): bump actions/download-artifact from 3 to 4 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 619aa731..8b85de3f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: - x84_64-apple-darwin if: "startsWith(github.ref, 'refs/tags/stac-cli')" steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: binaries path: binaries From 1d715605b0397cd01b2225c0654d5d3d5974fa41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 02:47:23 +0000 Subject: [PATCH 18/25] chore(deps): update geo requirement from 0.27 to 0.28 in /stac Updates the requirements on [geo](https://github.com/georust/geo) to permit the latest version. - [Changelog](https://github.com/georust/geo/blob/main/CHANGES.md) - [Commits](https://github.com/georust/geo/compare/geo-0.27.0...geo-0.28.0) --- updated-dependencies: - dependency-name: geo dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- stac/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stac/Cargo.toml b/stac/Cargo.toml index 19e7ff91..3dcd76b7 100644 --- a/stac/Cargo.toml +++ b/stac/Cargo.toml @@ -17,7 +17,7 @@ schemars = ["dep:schemars"] [dependencies] chrono = "0.4" -geo = { version = "0.27", optional = true } +geo = { version = "0.28", optional = true } geojson = { version = "0.24", optional = true } reqwest = { version = "0.11", optional = true, features = ["json", "blocking"] } schemars = { version = "0.8", optional = true } From 6908e45f0ddde89f7f30a10a06ec01d489b89505 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 02:39:11 +0000 Subject: [PATCH 19/25] chore(deps): update geo requirement from 0.27 to 0.28 in /stac-api Updates the requirements on [geo](https://github.com/georust/geo) to permit the latest version. - [Changelog](https://github.com/georust/geo/blob/main/CHANGES.md) - [Commits](https://github.com/georust/geo/compare/geo-0.27.0...geo-0.28.0) --- updated-dependencies: - dependency-name: geo dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- stac-api/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stac-api/Cargo.toml b/stac-api/Cargo.toml index fdd3825f..b4f565d7 100644 --- a/stac-api/Cargo.toml +++ b/stac-api/Cargo.toml @@ -15,7 +15,7 @@ geo = ["dep:geo", "stac/geo"] schemars = ["dep:schemars", "stac/schemars"] [dependencies] -geo = { version = "0.27", optional = true } +geo = { version = "0.28", optional = true } schemars = { version = "0.8", optional = true } serde = "1" serde_json = "1" From be01a49d0b38348cc6ee9b6f578d19d105a36c68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 02:18:00 +0000 Subject: [PATCH 20/25] chore(deps): bump softprops/action-gh-release from 1 to 2 Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b85de3f..b8007447 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,6 +43,6 @@ jobs: name: binaries path: binaries - name: Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: binaries/* From 7bbf4b03e7d89cc30888815fb83cbbc0a57322b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 02:31:19 +0000 Subject: [PATCH 21/25] chore(deps): update reqwest requirement from 0.11 to 0.12 in /stac-cli Updates the requirements on [reqwest](https://github.com/seanmonstar/reqwest) to permit the latest version. - [Release notes](https://github.com/seanmonstar/reqwest/releases) - [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md) - [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.0...v0.12.1) --- updated-dependencies: - dependency-name: reqwest dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- stac-cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stac-cli/Cargo.toml b/stac-cli/Cargo.toml index 29fbd557..4b304c2f 100644 --- a/stac-cli/Cargo.toml +++ b/stac-cli/Cargo.toml @@ -14,7 +14,7 @@ categories = ["science", "data-structures"] clap = { version = "4", features = ["derive"] } console = "0.15" indicatif = "0.17" -reqwest = "0.11" +reqwest = "0.12" serde = "1" serde_json = "1" stac = { version = "0.5", path = "../stac" } From 5c808747f77a7cc64b0035378620e01e9aa37474 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 02:23:57 +0000 Subject: [PATCH 22/25] chore(deps): update reqwest requirement from 0.11 to 0.12 in /stac Updates the requirements on [reqwest](https://github.com/seanmonstar/reqwest) to permit the latest version. - [Release notes](https://github.com/seanmonstar/reqwest/releases) - [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md) - [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.0...v0.12.1) --- updated-dependencies: - dependency-name: reqwest dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- stac/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stac/Cargo.toml b/stac/Cargo.toml index 3dcd76b7..10b1a05f 100644 --- a/stac/Cargo.toml +++ b/stac/Cargo.toml @@ -19,7 +19,7 @@ schemars = ["dep:schemars"] chrono = "0.4" geo = { version = "0.28", optional = true } geojson = { version = "0.24", optional = true } -reqwest = { version = "0.11", optional = true, features = ["json", "blocking"] } +reqwest = { version = "0.12", optional = true, features = ["json", "blocking"] } schemars = { version = "0.8", optional = true } serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = ["preserve_order"] } From acf16c722d7b473dbab49185e2e6f5ccb990baba Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Sat, 6 Apr 2024 10:10:44 -0600 Subject: [PATCH 23/25] feat: add ApiClient::with_client --- stac-async/CHANGELOG.md | 4 ++++ stac-async/src/api_client.rs | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/stac-async/CHANGELOG.md b/stac-async/CHANGELOG.md index 3bcc1d01..87ea1668 100644 --- a/stac-async/CHANGELOG.md +++ b/stac-async/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- `ApiClient::with_client` ([#227](https://github.com/stac-utils/stac-rs/pull/227)) + ### Removed - Downloading (use [stac-asset](https://github.com/stac-utils/stac-asset) instead) ([#194](https://github.com/stac-utils/stac-rs/pull/194)) diff --git a/stac-async/src/api_client.rs b/stac-async/src/api_client.rs index f951a468..2e628ee4 100644 --- a/stac-async/src/api_client.rs +++ b/stac-async/src/api_client.rs @@ -28,8 +28,24 @@ impl ApiClient { /// ``` pub fn new(url: &str) -> Result { // TODO support HATEOS (aka look up the urls from the root catalog) + ApiClient::with_client(Client::new(), url) + } + + /// Creates a new API client with the given [Client]. + /// + /// Useful if you want to customize the behavior of the underlying `Client`, + /// as documented in [Client::new]. + /// + /// # Examples + /// + /// ``` + /// use stac_async::{Client, ApiClient}; + /// let client = Client::new(); + /// let api_client = ApiClient::with_client(client, "https://earth-search.aws.element84.com/v1/").unwrap(); + /// ``` + pub fn with_client(client: Client, url: &str) -> Result { Ok(ApiClient { - client: Client::new(), + client, channel_buffer: DEFAULT_CHANNEL_BUFFER, url_builder: UrlBuilder::new(url)?, }) From 4bf5f20d34fbd10ec0802f48da07c5c4001ef582 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Sat, 6 Apr 2024 10:16:32 -0600 Subject: [PATCH 24/25] deps: update reqwest and http for stac-async --- stac-async/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stac-async/Cargo.toml b/stac-async/Cargo.toml index 81ad7100..2a1d148b 100644 --- a/stac-async/Cargo.toml +++ b/stac-async/Cargo.toml @@ -14,8 +14,8 @@ categories = ["science", "data-structures"] async-stream = "0.3" futures-core = "0.3" futures-util = "0.3" -http = "0.2" -reqwest = { version = "0.11", features = ["json"] } +http = "1" +reqwest = { version = "0.12", features = ["json"] } serde = "1" serde_json = "1" stac = { version = "0.5", path = "../stac" } From c5507e542bd6a9153783dc99df4baeaaf7d9b4ee Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Sun, 7 Apr 2024 06:33:23 -0600 Subject: [PATCH 25/25] release: stac v0.5.3, stac-api v0.3.3, stac-async v0.5.0 --- stac-api/CHANGELOG.md | 5 ++++- stac-api/Cargo.toml | 2 +- stac-async/CHANGELOG.md | 5 ++++- stac-async/Cargo.toml | 2 +- stac-cli/Cargo.toml | 2 +- stac/CHANGELOG.md | 5 ++++- stac/Cargo.toml | 2 +- 7 files changed, 16 insertions(+), 7 deletions(-) diff --git a/stac-api/CHANGELOG.md b/stac-api/CHANGELOG.md index 39c9f3ca..8c3d6939 100644 --- a/stac-api/CHANGELOG.md +++ b/stac-api/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [0.3.3] - 2024-04-07 + ### Added - `Search::validate` ([#206](https://github.com/stac-utils/stac-rs/pull/206)) @@ -76,7 +78,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), Initial release -[unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-api-v0.3.2...main +[unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-api-v0.3.3...main +[0.3.3]: https://github.com/stac-utils/stac-rs/compare/stac-api-v0.3.2...stac-api-v0.3.3 [0.3.2]: https://github.com/stac-utils/stac-rs/compare/stac-api-v0.3.1...stac-api-v0.3.2 [0.3.1]: https://github.com/stac-utils/stac-rs/compare/stac-api-v0.3.0...stac-api-v0.3.1 [0.3.0]: https://github.com/stac-utils/stac-rs/compare/stac-api-v0.2.0...stac-api-v0.3.0 diff --git a/stac-api/Cargo.toml b/stac-api/Cargo.toml index b4f565d7..beac6676 100644 --- a/stac-api/Cargo.toml +++ b/stac-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stac-api" -version = "0.3.2" +version = "0.3.3" authors = ["Pete Gadomski "] edition = "2021" description = "Rust library for the SpatioTemporal Asset Catalog (STAC) API specification" diff --git a/stac-async/CHANGELOG.md b/stac-async/CHANGELOG.md index 87ea1668..9d547a4d 100644 --- a/stac-async/CHANGELOG.md +++ b/stac-async/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [0.5.0] + ### Added - `ApiClient::with_client` ([#227](https://github.com/stac-utils/stac-rs/pull/227)) @@ -48,7 +50,8 @@ No changes. Initial release of **stac-async**. -[Unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-async-v0.4.1...main +[Unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-async-v0.5.0...main +[0.5.0]: https://github.com/stac-utils/stac-rs/compare/stac-async-v0.4.1...stac-async-v0.5.0 [0.4.1]: https://github.com/stac-utils/stac-rs/compare/stac-async-v0.4.0...stac-async-v0.4.1 [0.4.0]: https://github.com/stac-utils/stac-rs/compare/stac-async-v0.3.0...stac-async-v0.4.0 [0.3.0]: https://github.com/stac-utils/stac-rs/compare/stac-async-v0.2.0...stac-async-v0.3.0 diff --git a/stac-async/Cargo.toml b/stac-async/Cargo.toml index 2a1d148b..4827f31e 100644 --- a/stac-async/Cargo.toml +++ b/stac-async/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stac-async" -version = "0.4.1" +version = "0.5.0" edition = "2021" description = "Asynchronous I/O for stac-rs" documentation = "https://docs.rs/stac-async" diff --git a/stac-cli/Cargo.toml b/stac-cli/Cargo.toml index 4b304c2f..d9670b7a 100644 --- a/stac-cli/Cargo.toml +++ b/stac-cli/Cargo.toml @@ -19,7 +19,7 @@ serde = "1" serde_json = "1" stac = { version = "0.5", path = "../stac" } stac-api = { version = "0.3", path = "../stac-api" } -stac-async = { version = "0.4", path = "../stac-async" } +stac-async = { version = "0.5", path = "../stac-async" } stac-validate = { version = "0.1", path = "../stac-validate" } thiserror = "1" tokio = { version = "1.23", features = ["macros", "rt-multi-thread"] } diff --git a/stac/CHANGELOG.md b/stac/CHANGELOG.md index b6af2c7d..7ab9449d 100644 --- a/stac/CHANGELOG.md +++ b/stac/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [0.5.3] - 2024-04-07 + ### Added - `Geometry::point` ([#206](https://github.com/stac-utils/stac-rs/pull/206)) @@ -255,7 +257,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), Initial release. -[Unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.2...main +[Unreleased]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.3...main +[0.5.3]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.2...stac-v0.5.3 [0.5.2]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.1...stac-v0.5.2 [0.5.1]: https://github.com/stac-utils/stac-rs/compare/stac-v0.5.0...stac-v0.5.1 [0.5.0]: https://github.com/stac-utils/stac-rs/compare/stac-v0.4.0...stac-v0.5.0 diff --git a/stac/Cargo.toml b/stac/Cargo.toml index 10b1a05f..b6c3abdd 100644 --- a/stac/Cargo.toml +++ b/stac/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stac" -version = "0.5.2" +version = "0.5.3" authors = ["Pete Gadomski "] edition = "2021" description = "Rust library for the SpatioTemporal Asset Catalog (STAC) specification"