Skip to content

Commit

Permalink
feat: add GetSearch
Browse files Browse the repository at this point in the history
  • Loading branch information
gadomski committed Oct 10, 2023
1 parent cb4a8ef commit 60b7455
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 7 deletions.
1 change: 1 addition & 0 deletions stac-api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Added

- Item search conformance URI ([#193](https://github.com/stac-utils/stac-rs/pull/193))
- `GetSearch` ([#198](https://github.com/stac-utils/stac-rs/pull/198))

## [0.3.0] - 2023-09-25

Expand Down
2 changes: 1 addition & 1 deletion stac-api/src/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use stac::{Collection, Link, Links};

/// Object containing an array of Collection objects in the Catalog, and Link relations.
/// Object containing an array of collections and an array of links.
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Collections {
Expand Down
21 changes: 19 additions & 2 deletions stac-api/src/item_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ const ITEM_COLLECTION_TYPE: &str = "FeatureCollection";
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ItemCollection {
/// Always "FeatureCollection" to provide compatibility with GeoJSON.
pub r#type: String,
#[serde(
deserialize_with = "deserialize_type",
serialize_with = "serialize_type"
)]
r#type: String,

/// A possibly-empty array of Item objects.
#[serde(rename = "features")]
Expand Down Expand Up @@ -111,3 +114,17 @@ impl Default for ItemCollection {
}
}
}

fn deserialize_type<'de, D>(deserializer: D) -> std::result::Result<String, D::Error>
where
D: serde::de::Deserializer<'de>,
{
stac::deserialize_type(deserializer, ITEM_COLLECTION_TYPE)
}

fn serialize_type<S>(r#type: &String, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
stac::serialize_type(r#type, serializer, ITEM_COLLECTION_TYPE)
}
6 changes: 3 additions & 3 deletions stac-api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Rust implementation of the STAC API specification.
//! Rust implementation of the [STAC API](https://github.com/radiantearth/stac-api-spec) specification.
//!
//! This crate **is**:
//!
Expand All @@ -8,7 +8,7 @@
//!
//! - A server implementation
//!
//! For a STAC API server written in Rust, based on this crate, see
//! For a STAC API server written in Rust based on this crate, see
//! [stac-server-rs](http://github.com/gadomski/stac-server-rs).
//!
//! # Data structures
Expand Down Expand Up @@ -86,7 +86,7 @@ pub use {
item_collection::{Context, ItemCollection},
items::{GetItems, Items},
root::Root,
search::Search,
search::{GetSearch, Search},
sort::Sortby,
url_builder::UrlBuilder,
};
Expand Down
134 changes: 133 additions & 1 deletion stac-api/src/search.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{Fields, Filter, Sortby};
use crate::{Error, Fields, Filter, GetItems, Items, Result, Sortby};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use stac::Geometry;
use std::collections::HashMap;

/// The core parameters for STAC search are defined by OAFeat, and STAC adds a few parameters for convenience.
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -64,3 +65,134 @@ pub struct Search {
#[serde(flatten)]
pub additional_fields: Map<String, Value>,
}

/// GET parameters for the item search endpoint.
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GetSearch {
/// The maximum number of results to return (page size).
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<String>,

/// Requested bounding box.
pub bbox: Option<String>,

/// Requested bounding box.
/// Use double dots `..` for open date ranges.
#[serde(skip_serializing_if = "Option::is_none")]
pub datetime: Option<String>,

/// Searches items by performing intersection between their geometry and provided GeoJSON geometry.
///
/// All GeoJSON geometry types must be supported.
#[serde(skip_serializing_if = "Option::is_none")]
pub intersects: Option<String>,

/// Array of Item ids to return.
#[serde(skip_serializing_if = "Option::is_none")]
pub ids: Option<Vec<String>>,

/// Array of one or more Collection IDs that each matching Item must be in.
#[serde(skip_serializing_if = "Option::is_none")]
pub collections: Option<Vec<String>>,

/// Include/exclude fields from item collections.
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<String>,

/// Fields by which to sort results.
#[serde(skip_serializing_if = "Option::is_none")]
pub sortby: Option<String>,

/// Recommended to not be passed, but server must only accept
/// <http://www.opengis.net/def/crs/OGC/1.3/CRS84> as a valid value, may
/// reject any others
#[serde(skip_serializing_if = "Option::is_none", rename = "filter-crs")]
pub filter_crs: Option<String>,

/// CQL2 filter expression.
#[serde(skip_serializing_if = "Option::is_none")]
pub filter_lang: Option<String>,

/// CQL2 filter expression.
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<String>,

/// Additional fields.
#[serde(flatten)]
pub additional_fields: HashMap<String, String>,
}

impl TryFrom<Search> for GetSearch {
type Error = Error;

fn try_from(search: Search) -> Result<GetSearch> {
let items = Items {
limit: search.limit,
bbox: search.bbox,
datetime: search.datetime,
fields: search.fields,
sortby: search.sortby,
filter_crs: search.filter_crs,
filter: search.filter,
query: search.query,
additional_fields: search.additional_fields,
};
let get_items: GetItems = items.try_into()?;
let intersects = search
.intersects
.map(|intersects| serde_json::to_string(&intersects))
.transpose()?;
Ok(GetSearch {
limit: get_items.limit,
bbox: get_items.bbox,
datetime: get_items.datetime,
intersects: intersects,
ids: search.ids,
collections: search.collections,
fields: get_items.fields,
sortby: get_items.sortby,
filter_crs: get_items.filter_crs,
filter_lang: get_items.filter_lang,
filter: get_items.filter,
additional_fields: get_items.additional_fields,
})
}
}

impl TryFrom<GetSearch> for Search {
type Error = Error;

fn try_from(get_search: GetSearch) -> Result<Search> {
let get_items = GetItems {
limit: get_search.limit,
bbox: get_search.bbox,
datetime: get_search.datetime,
fields: get_search.fields,
sortby: get_search.sortby,
filter_crs: get_search.filter_crs,
filter: get_search.filter,
filter_lang: get_search.filter_lang,
additional_fields: get_search.additional_fields,
};
let items: Items = get_items.try_into()?;
let intersects = get_search
.intersects
.map(|intersects| serde_json::from_str(&intersects))
.transpose()?;
Ok(Search {
limit: items.limit,
bbox: items.bbox,
datetime: items.datetime,
intersects: intersects,
ids: get_search.ids,
collections: get_search.collections,
fields: items.fields,
sortby: items.sortby,
filter_crs: items.filter_crs,
filter: items.filter,
query: items.query,
additional_fields: items.additional_fields,
})
}
}

0 comments on commit 60b7455

Please sign in to comment.