Skip to content

Commit

Permalink
feat: add gdal
Browse files Browse the repository at this point in the history
  • Loading branch information
gadomski committed Apr 11, 2024
1 parent cda0b00 commit e345ef4
Show file tree
Hide file tree
Showing 19 changed files with 605 additions and 14 deletions.
25 changes: 23 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,46 @@ jobs:
- "-p stac-async"
- "-p stac-cli"
- "-p stac-validate"
steps:
- uses: actions/checkout@v4
- name: Set up Rust cache
uses: Swatinem/rust-cache@v2
- name: Test
run: cargo test ${{ matrix.args }}
ubuntu-with-gdal:
runs-on: ubuntu-latest
strategy:
matrix:
args:
- "-p stac -p stac-cli -F gdal"
- "--all-features"
steps:
- uses: actions/checkout@v4
- name: Set up Rust cache
uses: Swatinem/rust-cache@v2
- name: Install GDAL
run: |
sudo apt-get update
sudo apt-get install libgdal-dev
- name: Test
run: cargo test ${{ matrix.args }}

windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Set up Rust cache
uses: Swatinem/rust-cache@v2
- name: Test
run: cargo test --all-features
run: cargo test
macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Set up Rust cache
uses: Swatinem/rust-cache@v2
- name: Test
run: cargo test --all-features
run: cargo test
fmt:
runs-on: ubuntu-latest
steps:
Expand All @@ -68,5 +85,9 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Rust cache
uses: Swatinem/rust-cache@v2
- name: Install GDAL
run: |
sudo apt-get update
sudo apt-get install libgdal-dev
- name: Doc
run: cargo doc --all-features
2 changes: 2 additions & 0 deletions stac-cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Added

- `stac validate` can take from stdin ([#236](https://github.com/stac-utils/stac-rs/pull/236))
- `stac item` to create items ([#237](https://github.com/stac-utils/stac-rs/pull/237))
- The `gdal` feature ([#232](https://github.com/stac-utils/stac-rs/pull/232))

## [0.0.6] - 2023-10-18

Expand Down
3 changes: 3 additions & 0 deletions stac-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ license = "MIT OR Apache-2.0"
keywords = ["geospatial", "stac", "metadata", "geo", "raster"]
categories = ["science", "data-structures"]

[features]
gdal = ["stac/gdal"]

[dependencies]
clap = { version = "4", features = ["derive"] }
console = "0.15"
Expand Down
10 changes: 10 additions & 0 deletions stac-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ Once you do:
cargo install stac-cli
```

### Features

There is one opt-in feature, `gdal`:

```shell
cargo install stac-cli -F gdal
```

This will enable the projection and raster extensions for created items.

## Usage

Use the cli `--help` flag to see all available options:
Expand Down
20 changes: 19 additions & 1 deletion stac-cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ pub enum Command {
/// Use compact representation for the output.
#[arg(short, long)]
compact: bool,

/// Don't use GDAL for item creation.
///
/// Automatically set to true if this crate is compiled without GDAL.
#[arg(long)]
disable_gdal: bool,
},

/// Searches a STAC API.
Expand Down Expand Up @@ -143,9 +149,21 @@ impl Command {
role,
allow_relative_paths,
compact,
mut disable_gdal,
} => {
let id = id.unwrap_or_else(|| infer_id(&href));
crate::commands::item(id, href, key, role, allow_relative_paths, compact)
if !cfg!(feature = "gdal") {
disable_gdal = true;
}
crate::commands::item(
id,
href,
key,
role,
allow_relative_paths,
compact,
disable_gdal,
)
}
Search {
href,
Expand Down
2 changes: 2 additions & 0 deletions stac-cli/src/commands/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ pub fn item(
roles: Vec<String>,
allow_relative_paths: bool,
compact: bool,
disable_gdal: bool,
) -> Result<()> {
let mut asset = Asset::new(href);
asset.roles = roles;
let item = Builder::new(id)
.asset(key, asset)
.canonicalize_paths(!allow_relative_paths)
.enable_gdal(!disable_gdal)
.into_item()?;
if compact {
println!("{}", serde_json::to_string(&item)?);
Expand Down
2 changes: 2 additions & 0 deletions stac/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- The projection and raster extensions, the `Extension` trait, and the `Fields` trait ([#234](https://github.com/stac-utils/stac-rs/pull/234))
- `stac::item::Builder` ([#237](https://github.com/stac-utils/stac-rs/pull/237))
- The `gdal` feature ([#232](https://github.com/stac-utils/stac-rs/pull/232))
- `Bounds` ([#232](https://github.com/stac-utils/stac-rs/pull/232))

### Changed

Expand Down
8 changes: 8 additions & 0 deletions stac/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,22 @@ keywords = ["geospatial", "stac", "metadata", "geo", "raster"]
categories = ["science", "data-structures"]

[features]
gdal = ["dep:gdal", "dep:gdal-sys", "dep:log"]
geo = ["dep:geo", "dep:geojson"]
reqwest = ["dep:reqwest"]
schemars = ["dep:schemars"]

[dependencies]
chrono = "0.4"
gdal = { version = "0.16", optional = true, features = [
"bindgen",
] } # we use bindgen b/c the gdal crate doesn't support GDAL 3.8 as of this writing
gdal-sys = { version = "0.9", optional = true, features = [
"bindgen",
] } # we use bindgen b/c the gdal crate doesn't support GDAL 3.8 as of this writing, and we depend on gdal-sys for build script usage
geo = { version = "0.28", optional = true }
geojson = { version = "0.24", optional = true }
log = { version = "0.4", optional = true }
reqwest = { version = "0.12", optional = true, features = ["json", "blocking"] }
schemars = { version = "0.8", optional = true }
serde = { version = "1", features = ["derive"] }
Expand Down
23 changes: 22 additions & 1 deletion stac/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Please see the [documentation](https://docs.rs/stac) for more usage examples.

## Features

There are three opt-in features.
There are four opt-in features.

### reqwest

Expand Down Expand Up @@ -62,6 +62,27 @@ let err = stac::read::<stac::Item>(href).unwrap_err();

For non-blocking IO, use the [**stac-async**](https://crates.io/crates/stac-async) crate.

### gdal

To use [GDAL](https://gdal.org) to create items with projection and raster band information, you'll need GDAL installed on your system:

```toml
[dependencies]
stac = { version = "0.5", features = ["gdal"] }
```

Then, items created from rasters will include the projection and raster extensions:

```rust
#[cfg(feature = "gdal")]
{
use stac::{extensions::{Raster, Projection}, Extensions, item::Builder};
let item = Builder::new("an-id").asset("data", "assets/dataset_geo.tif").into_item().unwrap();
assert!(item.has_extension::<Projection>());
assert!(item.has_extension::<Raster>());
}
```

### geo

To use [geojson](https://docs.rs/geojson) and [geo](https://docs.rs/geo) to add some extra geo-enabled methods:
Expand Down
Binary file added stac/assets/dataset_geo.tif
Binary file not shown.
23 changes: 23 additions & 0 deletions stac/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
fn main() {
#[cfg(feature = "gdal")]
{
use std::str::FromStr;
let gdal_version_string = std::env::var("DEP_GDAL_VERSION_NUMBER").unwrap();
let gdal_version = i64::from_str(&gdal_version_string)
.expect("Could not convert gdal version string into number.");
let major = gdal_version / 1000000;
let minor = (gdal_version - major * 1000000) / 10000;
let patch = (gdal_version - major * 1000000 - minor * 10000) / 100;

if major != 3 {
panic!("This crate requires a GDAL version = 3. Found {major}.{minor}.{patch}");
}
if minor >= 5 {
println!("cargo:rustc-cfg=gdal_has_int64");
println!("cargo:rustc-cfg=gdal_has_uint64");
}
if minor >= 7 {
println!("cargo:rustc-cfg=gdal_has_int8");
}
}
}
76 changes: 76 additions & 0 deletions stac/src/bounds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/// Two-dimensional bounds.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Bounds {
/// Minimum x value.
pub xmin: f64,
/// Minimum y value.
pub ymin: f64,
/// Maximum x value.
pub xmax: f64,
/// Maximum y value.
pub ymax: f64,
}

impl Bounds {
/// Creates a new bounds object.
///
/// # Examples
///
/// ```
/// use stac::Bounds;
/// let bounds = Bounds::new(1., 2., 3., 4.);
/// ```
pub fn new(xmin: f64, ymin: f64, xmax: f64, ymax: f64) -> Bounds {
Bounds {
xmin,
ymin,
xmax,
ymax,
}
}

/// Returns true if the minimum bound values are smaller than the maximum.
///
/// This doesn't currently handle antimeridian-crossing bounds.
///
/// # Examples
///
/// ```
/// use stac::Bounds;
/// let bounds = Bounds::default();
/// assert!(!bounds.is_valid());
/// let bounds = Bounds::new(1., 2., 3., 4.);
/// assert!(bounds.is_valid());
/// ```
pub fn is_valid(&self) -> bool {
self.xmin < self.xmax && self.ymin < self.ymax
}

/// Updates these bounds with another bounds' values.
///
/// # Examples
///
/// ```
/// use stac::Bounds;
/// let mut bounds = Bounds::new(1., 1., 2., 2.);
/// bounds.update(Bounds::new(0., 0., 1.5, 1.5));
/// assert_eq!(bounds, Bounds::new(0., 0., 2., 2.));
/// ```
pub fn update(&mut self, other: Bounds) {
self.xmin = self.xmin.min(other.xmin);
self.ymin = self.ymin.min(other.ymin);
self.xmax = self.xmax.max(other.xmax);
self.ymax = self.ymax.max(other.ymax);
}
}

impl Default for Bounds {
fn default() -> Self {
Bounds {
xmin: f64::MAX,
ymin: f64::MAX,
xmax: f64::MIN,
ymax: f64::MIN,
}
}
}
13 changes: 13 additions & 0 deletions stac/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ pub enum Error {
#[error(transparent)]
ChronoParse(#[from] chrono::ParseError),

/// [gdal::errors::GdalError]
#[cfg(feature = "gdal")]
#[error(transparent)]
GdalError(#[from] gdal::errors::GdalError),

/// GDAL is not enabled.
#[error("gdal is not enabled")]
GdalNotEnabled,

/// [geojson::Error]
#[cfg(feature = "geo")]
#[error(transparent)]
Expand Down Expand Up @@ -77,6 +86,10 @@ pub enum Error {
#[error(transparent)]
SerdeJson(#[from] serde_json::Error),

/// [std::num::TryFromIntError]
#[error(transparent)]
TryFromInt(#[from] std::num::TryFromIntError),

/// Returned when the `type` field of a STAC object does not equal `"Feature"`, `"Catalog"`, or `"Collection"`.
#[error("unknown \"type\": {0}")]
UnknownType(String),
Expand Down
29 changes: 25 additions & 4 deletions stac/src/extensions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
//! assert!(item.has_extension::<Projection>());
//!
//! // Get extension information
//! let mut projection: Projection = item.extension().unwrap();
//! let mut projection: Projection = item.extension().unwrap().unwrap();
//! println!("epsg: {}", projection.epsg.unwrap());
//!
//! // Set extension information
Expand Down Expand Up @@ -121,16 +121,36 @@ pub trait Extensions: Fields {

/// Gets an extension's data.
///
/// Returns `Ok(None)` if the object doesn't have the given extension.
///
/// # Examples
///
/// ```
/// use stac::{Item, extensions::{Projection, Extensions}};
/// let item: Item = stac::read("data/extensions-collection/proj-example/proj-example.json").unwrap();
/// let projection: Projection = item.extension().unwrap();
/// let projection: Projection = item.extension().unwrap().unwrap();
/// assert_eq!(projection.epsg.unwrap(), 32614);
/// ```
fn extension<E: Extension>(&self) -> Result<E> {
self.fields_with_prefix(E::PREFIX)
fn extension<E: Extension>(&self) -> Result<Option<E>> {
if self.has_extension::<E>() {
self.fields_with_prefix(E::PREFIX).map(|v| Some(v))
} else {
Ok(None)
}
}

/// Adds an extension's identifer to this object.
///
/// # Examples
///
/// ```
/// use stac::{Item, extensions::{Projection, Extensions}};
/// let mut item = Item::new("an-id");
/// item.add_extension::<Projection>();
/// ```
fn add_extension<E: Extension>(&mut self) {
self.extensions_mut().push(E::IDENTIFIER.to_string());
self.extensions_mut().dedup();
}

/// Sets an extension's data and adds its schema to this object's `extensions`.
Expand All @@ -148,6 +168,7 @@ pub trait Extensions: Fields {
fn set_extension<E: Extension>(&mut self, extension: E) -> Result<()> {
self.remove_extension::<E>();
self.extensions_mut().push(E::IDENTIFIER.to_string());
self.extensions_mut().dedup();
self.set_fields_with_prefix(E::PREFIX, extension)
}

Expand Down
Loading

0 comments on commit e345ef4

Please sign in to comment.