Skip to content

Commit 199a518

Browse files
authored
feat: update geoarrow crates (#761)
Closes #759.
1 parent 2bf1da5 commit 199a518

File tree

24 files changed

+139
-145
lines changed

24 files changed

+139
-145
lines changed

Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ futures-util = "0.3.31"
5656
geo = "0.30.0"
5757
geo-traits = "0.3.0"
5858
geo-types = "0.7.16"
59-
geoarrow-array = { git = "https://github.com/geoarrow/geoarrow-rs/", rev = "1d73a8c8f739ac2a9f4cbac928283c19b74db463" }
60-
geoparquet = { git = "https://github.com/geoarrow/geoarrow-rs/", rev = "1d73a8c8f739ac2a9f4cbac928283c19b74db463" }
61-
geoarrow-schema = { git = "https://github.com/geoarrow/geoarrow-rs/", rev = "1d73a8c8f739ac2a9f4cbac928283c19b74db463" }
59+
geoarrow-array = "0.4.0"
60+
geoparquet = "0.4.0"
61+
geoarrow-schema = "0.4.0"
6262
geojson = "0.24.1"
6363
getrandom = { version = "0.3.3", features = ["wasm_js"] }
6464
http = "1.1"
@@ -69,7 +69,7 @@ log = "0.4.25"
6969
mime = "0.3.17"
7070
mockito = "1.5"
7171
object_store = "0.12.0"
72-
parquet = { version = "55.0.0", default-features = false }
72+
parquet = { version = "55.0.0" }
7373
pgstac = { version = "0.3.0", path = "crates/pgstac" }
7474
quote = "1.0"
7575
reqwest = { version = "0.12.8", default-features = false, features = [

crates/api/src/client.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ mod tests {
439439
let mut page_1_body: ItemCollection =
440440
serde_json::from_str(include_str!("../mocks/search-page-1.json")).unwrap();
441441
let mut next_link = page_1_body.link("next").unwrap().clone();
442-
next_link.href = format!("{}/search", server.url()).into();
442+
next_link.href = format!("{}/search", server.url());
443443
page_1_body.set_link(next_link);
444444
let page_1 = server
445445
.mock("POST", "/search")
@@ -495,8 +495,7 @@ mod tests {
495495
"{}/collections/sentinel-2-l2a/items?{}",
496496
server.url(),
497497
query
498-
)
499-
.into();
498+
);
500499
page_1_body.set_link(next_link);
501500
let page_1 = server
502501
.mock("GET", "/collections/sentinel-2-l2a/items?limit=1")
@@ -542,8 +541,7 @@ mod tests {
542541
"{}/collections/sentinel-2-l2a/items?{}",
543542
server.url(),
544543
query
545-
)
546-
.into();
544+
);
547545
page_body.set_link(next_link);
548546
page_body.items = vec![];
549547
let page = server

crates/api/src/fields.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl Display for Fields {
5050
fields.push(include.to_string());
5151
}
5252
for exclude in &self.exclude {
53-
fields.push(format!("-{}", exclude));
53+
fields.push(format!("-{exclude}"));
5454
}
5555
write!(f, "{}", fields.join(","))
5656
}

crates/api/src/url_builder.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ impl UrlBuilder {
3636
let root: Url = if url.ends_with('/') {
3737
url.parse()?
3838
} else {
39-
format!("{}/", url).parse()?
39+
format!("{url}/").parse()?
4040
};
4141
Ok(UrlBuilder {
4242
collections: root.join("collections")?,
@@ -108,7 +108,7 @@ impl UrlBuilder {
108108
/// );
109109
/// ```
110110
pub fn items(&self, id: &str) -> Result<Url, ParseError> {
111-
self.collections_with_slash.join(&format!("{}/items", id))
111+
self.collections_with_slash.join(&format!("{id}/items"))
112112
}
113113

114114
/// Returns a item url.
@@ -125,7 +125,7 @@ impl UrlBuilder {
125125
/// ```
126126
pub fn item(&self, collection_id: &str, id: &str) -> Result<Url, ParseError> {
127127
self.collections_with_slash
128-
.join(&format!("{}/items/{}", collection_id, id))
128+
.join(&format!("{collection_id}/items/{id}"))
129129
}
130130

131131
/// Returns the conformance url.

crates/cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ stac-duckdb.workspace = true
3030
stac-io = { workspace = true, features = [
3131
"store-all",
3232
"reqwest",
33-
"geoparquet-compression",
33+
"geoparquet",
3434
] }
3535
stac-server = { workspace = true, features = ["axum", "duckdb"] }
3636
stac-validate.workspace = true

crates/cli/src/lib.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ use async_stream::try_stream;
77
use clap::{Parser, Subcommand};
88
use futures_core::TryStream;
99
use futures_util::{TryStreamExt, pin_mut};
10-
use stac::{Assets, Collection, Item, Links, Migrate, SelfHref, geoparquet::Compression};
10+
use stac::{
11+
Assets, Collection, Item, Links, Migrate, SelfHref,
12+
geoparquet::{Compression, DEFAULT_COMPRESSION},
13+
};
1114
use stac_api::{GetItems, GetSearch, Search};
1215
use stac_io::{Format, StacStore};
1316
use stac_server::Backend;
@@ -511,7 +514,7 @@ impl Rustac {
511514
}
512515
} else {
513516
for error in errors {
514-
println!("{}", error);
517+
println!("{error}");
515518
}
516519
}
517520
}
@@ -590,7 +593,7 @@ impl Rustac {
590593
Format::Json(true)
591594
};
592595
if matches!(format, Format::Geoparquet(_)) {
593-
Format::Geoparquet(self.parquet_compression.or(Some(Compression::SNAPPY)))
596+
Format::Geoparquet(self.parquet_compression.or(Some(DEFAULT_COMPRESSION)))
594597
} else if let Format::Json(pretty) = format {
595598
Format::Json(self.compact_json.map(|c| !c).unwrap_or(pretty))
596599
} else {
@@ -690,11 +693,11 @@ async fn load_and_serve(
690693
"items don't have a collection and `create_collections` is false"
691694
));
692695
}
693-
let root = format!("http://{}", addr);
696+
let root = format!("http://{addr}");
694697
let api = stac_server::Api::new(backend, &root)?;
695698
let router = stac_server::routes::from_api(api);
696699
let listener = TcpListener::bind(&addr).await?;
697-
eprintln!("Serving a STAC API at {}", root);
700+
eprintln!("Serving a STAC API at {root}");
698701
axum::serve(listener, router).await.map_err(Error::from)
699702
}
700703

crates/cli/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ async fn main() {
77
std::process::exit(match args.run(true).await {
88
Ok(()) => 0,
99
Err(err) => {
10-
eprintln!("ERROR: {}", err);
10+
eprintln!("ERROR: {err}");
1111
1 // TODO make this more meaningful
1212
}
1313
})

crates/core/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ geoarrow = [
2424
"dep:geo-types",
2525
]
2626
geoparquet = ["geoarrow", "dep:geoparquet", "dep:parquet"]
27-
geoparquet-compression = ["geoparquet", "geoparquet/compression"]
2827

2928
[dependencies]
3029
arrow-array = { workspace = true, optional = true, features = ["chrono-tz"] }

crates/core/src/fields.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ pub trait Fields {
8383
/// ```
8484
fn fields_with_prefix<D: DeserializeOwned>(&self, prefix: &str) -> Result<D> {
8585
let mut map = Map::new();
86-
let prefix = format!("{}:", prefix);
86+
let prefix = format!("{prefix}:");
8787
for (key, value) in self.fields().iter() {
8888
if key.starts_with(&prefix) && key.len() > prefix.len() {
8989
let _ = map.insert(key[prefix.len()..].to_string(), value.clone());
@@ -108,7 +108,7 @@ pub trait Fields {
108108
let value = serde_json::to_value(value)?;
109109
if let Value::Object(object) = value {
110110
for (key, value) in object.into_iter() {
111-
let _ = self.set_field(format!("{}:{}", prefix, key), value);
111+
let _ = self.set_field(format!("{prefix}:{key}"), value);
112112
}
113113
Ok(())
114114
} else {
@@ -127,7 +127,7 @@ pub trait Fields {
127127
/// item.remove_fields_with_prefix("proj");
128128
/// ```
129129
fn remove_fields_with_prefix(&mut self, prefix: &str) {
130-
let prefix = format!("{}:", prefix);
130+
let prefix = format!("{prefix}:");
131131
self.fields_mut()
132132
.retain(|key, _| !(key.starts_with(&prefix) && key.len() > prefix.len()));
133133
}

crates/core/src/geoparquet.rs

Lines changed: 54 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,20 @@ use crate::{
77
use bytes::Bytes;
88
use geoparquet::{
99
reader::{GeoParquetReaderBuilder, GeoParquetRecordBatchReader},
10-
writer::GeoParquetWriterOptions,
10+
writer::{GeoParquetRecordBatchEncoder, GeoParquetWriterOptionsBuilder},
1111
};
1212
use parquet::{
13-
arrow::arrow_reader::ParquetRecordBatchReaderBuilder,
13+
arrow::{ArrowWriter, arrow_reader::ParquetRecordBatchReaderBuilder},
1414
file::{properties::WriterProperties, reader::ChunkReader},
1515
format::KeyValue,
1616
};
1717
use std::io::Write;
1818

1919
pub use parquet::basic::Compression;
2020

21+
/// Default stac-geoparquet compression
22+
pub const DEFAULT_COMPRESSION: Compression = Compression::SNAPPY;
23+
2124
/// Reads a [ItemCollection] from a [ChunkReader] as
2225
/// [stac-geoparquet](https://github.com/stac-utils/stac-geoparquet).
2326
///
@@ -65,7 +68,7 @@ pub fn into_writer<W>(writer: W, item_collection: impl Into<ItemCollection>) ->
6568
where
6669
W: Write + Send,
6770
{
68-
into_writer_with_options(writer, item_collection, Default::default())
71+
WriterBuilder::new(writer, item_collection).write()
6972
}
7073

7174
/// Writes a [ItemCollection] to a [std::io::Write] as
@@ -77,12 +80,9 @@ where
7780
/// use std::io::Cursor;
7881
/// use stac::{Item, geoparquet::Compression};
7982
///
80-
/// # #[cfg(feature = "geoparquet-compression")]
81-
/// # {
8283
/// let item: Item = stac::read("examples/simple-item.json").unwrap();
8384
/// let mut cursor = Cursor::new(Vec::new());
8485
/// stac::geoparquet::into_writer_with_compression(&mut cursor, vec![item], Compression::SNAPPY).unwrap();
85-
/// # }
8686
/// ```
8787
pub fn into_writer_with_compression<W>(
8888
writer: W,
@@ -92,50 +92,59 @@ pub fn into_writer_with_compression<W>(
9292
where
9393
W: Write + Send,
9494
{
95-
let mut options = GeoParquetWriterOptions::default();
96-
let writer_properties = WriterProperties::builder()
97-
.set_compression(compression)
98-
.set_key_value_metadata(Some(vec![KeyValue {
99-
key: VERSION_KEY.to_string(),
100-
value: Some(VERSION.to_string()),
101-
}]))
102-
.build();
103-
options.writer_properties = Some(writer_properties);
104-
into_writer_with_options(writer, item_collection, options)
95+
WriterBuilder::new(writer, item_collection)
96+
.compression(compression)
97+
.write()
10598
}
10699

107-
/// Writes a [ItemCollection] to a [std::io::Write] as
108-
/// [stac-geoparquet](https://github.com/stac-utils/stac-geoparquet) with the provided options.
109-
///
110-
/// # Examples
111-
///
112-
/// ```
113-
/// use std::io::Cursor;
114-
/// use stac::{Item, geoparquet::Compression};
115-
///
116-
/// let item: Item = stac::read("examples/simple-item.json").unwrap();
117-
/// let mut cursor = Cursor::new(Vec::new());
118-
/// stac::geoparquet::into_writer_with_options(&mut cursor, vec![item], Default::default()).unwrap();
119-
/// ```
120-
pub fn into_writer_with_options<W>(
100+
struct WriterBuilder<W: Write + Send> {
121101
writer: W,
122-
item_collection: impl Into<ItemCollection>,
123-
mut options: GeoParquetWriterOptions,
124-
) -> Result<()>
125-
where
126-
W: Write + Send,
127-
{
128-
if let Some(primary_column) = options.primary_column.as_deref() {
129-
if primary_column != "geometry" {
130-
log::warn!("primary column not set to 'geometry'");
102+
item_collection: ItemCollection,
103+
compression: Option<Compression>,
104+
}
105+
106+
impl<W: Write + Send> WriterBuilder<W> {
107+
fn new(writer: W, item_collection: impl Into<ItemCollection>) -> WriterBuilder<W> {
108+
WriterBuilder {
109+
writer,
110+
item_collection: item_collection.into(),
111+
compression: Some(DEFAULT_COMPRESSION),
131112
}
132-
} else {
133-
options.primary_column = Some("geometry".to_string());
134113
}
135-
let table = Table::from_item_collection(item_collection)?;
136-
geoparquet::writer::write_geoparquet(Box::new(table.into_reader()), writer, &options)?;
137-
Ok(())
114+
115+
fn compression(mut self, compression: impl Into<Option<Compression>>) -> WriterBuilder<W> {
116+
self.compression = compression.into();
117+
self
118+
}
119+
120+
fn write(self) -> Result<()> {
121+
let (record_batches, schema) =
122+
Table::from_item_collection(self.item_collection)?.into_inner();
123+
let options = GeoParquetWriterOptionsBuilder::default()
124+
.set_primary_column("geometry".to_string())
125+
.build();
126+
let mut encoder = GeoParquetRecordBatchEncoder::try_new(&schema, &options)?;
127+
let mut builder = WriterProperties::builder();
128+
if let Some(compression) = self.compression {
129+
builder = builder.set_compression(compression);
130+
}
131+
let properties = builder.build();
132+
let mut writer =
133+
ArrowWriter::try_new(self.writer, encoder.target_schema(), Some(properties))?;
134+
for record_batch in record_batches {
135+
let record_batch = encoder.encode_record_batch(&record_batch)?;
136+
writer.write(&record_batch)?;
137+
}
138+
writer.append_key_value_metadata(encoder.into_keyvalue()?);
139+
writer.append_key_value_metadata(KeyValue::new(
140+
VERSION_KEY.to_string(),
141+
Some(VERSION.to_string()),
142+
));
143+
let _ = writer.finish()?;
144+
Ok(())
145+
}
138146
}
147+
139148
/// Create a STAC object from geoparquet data.
140149
pub trait FromGeoparquet: Sized {
141150
/// Creates a STAC object from geoparquet bytes.
@@ -329,7 +338,7 @@ mod tests {
329338
.file_metadata()
330339
.key_value_metadata()
331340
.unwrap()
332-
.into_iter()
341+
.iter()
333342
.find(|key_value| key_value.key == "geo")
334343
.unwrap();
335344
let value: serde_json::Value =

0 commit comments

Comments
 (0)