Skip to content

Commit 05440b0

Browse files
committed
add geoarrow GeoTable conversion. Derive default in most cases
1 parent 197a159 commit 05440b0

12 files changed

+81
-70
lines changed

.github/workflows/cargo-doc.yml

-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ jobs:
3333
uses: actions/checkout@v4
3434

3535
- uses: dtolnay/rust-toolchain@nightly
36-
if: matrix.config.rust == 'nightly'
3736

3837
- name: Docs
3938
run: cargo doc --no-deps --all-features

.zed/settings.json

-6
This file was deleted.

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "serde_esri"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2021"
55
license = "MIT OR Apache-2.0"
66
repository = "https://github.com/josiahparry/serde_esri"

README.md

+50-45
Original file line numberDiff line numberDiff line change
@@ -7,48 +7,73 @@ This crate provides representations of Esri JSON objects with [`serde::Deseriali
77
`serde_esri` has two additional features `geo` and `geoarrow`.
88

99
- `geo` implements `From` for the Esri JSON objects.
10-
- `geoarrow` provides compatibility with arrow and geoarrow by implementing geoarrow geometry traits as well as providing a utility function `featureset_to_arrow()` which converts a `FeatureSet` to an arrow `RecordBatch`.
10+
- `geoarrow` provides compatibility with arrow and geoarrow by implementing geoarrow geometry traits as well as providing a utility function `featureset_to_geoarrow()` which converts a `FeatureSet` to an arrow `GeoTable`.
11+
1112

1213
## Example usage:
1314

14-
In this example, we query a feature service and convert the response to an Arrow `RecordBatch`. This requires the `geoarrow` feature to be enabled.
15+
This example reads a few features from a feature service and returns a `FeatureSet` struct. It illustrates the use of the geo and geoarrow features.
16+
17+
```toml
18+
[dependencies]
19+
geo = "0.28.0"
20+
geoarrow = "0.2.0"
21+
reqwest = { version = "0.12.3", features = ["blocking"] }
22+
serde_esri = { version = "0.2.0", features = ["geo", "geoarrow"] }
23+
serde_json = "1.0.115"
24+
```
1525

1626
```rust
27+
use geo::{GeodesicArea, Polygon};
28+
use serde_esri::arrow_compat::featureset_to_geoarrow;
1729
use serde_esri::features::FeatureSet;
18-
use serde_esri::arrow_compat::featureset_to_arrow;
19-
20-
#[tokio::main]
21-
async fn main() {
22-
23-
// query url
24-
let furl = "https://services.arcgis.com/P3ePLMYs2RVChkJx/ArcGIS/rest/services/USA_Counties_Generalized_Boundaries/FeatureServer/0/query?where=1=1&outFields=*&f=json&resultRecordCount=10";
25-
26-
// make the request
27-
let resp = reqwest::get(furl)
28-
.await.unwrap()
29-
.text()
30-
.await.unwrap();
31-
32-
// parse the response into a FeatureSet
33-
let fset: FeatureSet<2> = serde_json::from_str(&resp).unwrap();
34-
35-
// convert the FeatureSet to an Arrow RecordBatch
36-
let rb = featureset_to_arrow(fset).unwrap();
37-
38-
println!("{:#?}", rb.schema());
30+
use std::io::Read;
31+
32+
fn main() {
33+
let flayer_url = "https://services.arcgis.com/P3ePLMYs2RVChkJx/ArcGIS/rest/services/USA_Counties_Generalized_Boundaries/FeatureServer/0/query?where=1%3D1&outFields=*&returnGeometry=true&resultRecordCount=5&f=json";
34+
let mut res = reqwest::blocking::get(flayer_url).unwrap();
35+
let mut body = String::new();
36+
37+
// Read the request into a String
38+
res.read_to_string(&mut body).unwrap();
39+
40+
// Parse into a 2D FeatureSet
41+
let fset: FeatureSet<2> = serde_json::from_str(&body).unwrap();
42+
43+
// Utilize the `geo` feature by converting to Polygon
44+
// and calculate geodesic area
45+
// iterate through the features
46+
let area = fset
47+
.features
48+
.clone()
49+
.into_iter()
50+
.map(|feat| {
51+
// convert to a geo_types::Polygon
52+
let poly = Polygon::from(feat.geometry.unwrap().as_polygon().unwrap());
53+
// calculate geodesic area
54+
poly.geodesic_area_unsigned()
55+
})
56+
.collect::<Vec<_>>();
57+
58+
// print areas
59+
println!("{:?}", area);
60+
61+
// convert to a geoarrow GeoTable
62+
println!("{:?}", featureset_to_geoarrow(fset).unwrap());
3963
}
4064
```
4165

4266
## Supported Esri JSON objects:
4367

4468
### Geometry
4569

46-
[Esri Geometries Objects](https://developers.arcgis.com/documentation/common-data-types/geometry-objects.htm#CURVE) are encoded by the following structs:
70+
[Esri Geometries Objects](https://developers.arcgis.com/documentation/common-data-types/geometry-objects.htm) are encoded by the following structs:
4771

4872
- `EsriPoint`
4973
- `EsriMultiPoint<N>`
5074
- `EsriPolyline<N>`
5175
- `EsriPolygon<N>`
76+
- `EsriEnvelope`
5277

5378
They are encapsulated by the `EsriGeometry` enum:
5479

@@ -58,6 +83,7 @@ enum EsriGeometry<const N: usize> {
5883
MultiPoint(EsriMultiPoint<N>),
5984
Polygon(EsriPolygon<N>),
6085
Polyline(EsriPolyline<N>),
86+
Envelope(EsriEnvelope),
6187
}
6288
```
6389
The parameter `N` is used to specify the dimension of the geometries. Use `<2>` for 2 dimensional data, `<3>` for Z values and `<4>` when `M` and `Z` are present.
@@ -99,24 +125,3 @@ struct SpatialReference {
99125
wkt: Option<String>,
100126
}
101127
```
102-
103-
## Example usage:
104-
105-
This example reads a single feature from a feature service and returns a `FeatureSet` struct.
106-
107-
```rust
108-
use serde_esri::features::FeatureSet;
109-
use reqwest::Error;
110-
use std::io::Read;
111-
112-
fn main() -> Result<(), Error> {
113-
let flayer_url = "https://services.arcgis.com/P3ePLMYs2RVChkJx/ArcGIS/rest/services/USA_Counties_Generalized_Boundaries/FeatureServer/0/query?where=1%3D1&outFields=*&returnGeometry=true&resultRecordCount=1&f=json";
114-
let mut res = reqwest::blocking::get(flayer_url)?;
115-
let mut body = String::new();
116-
res.read_to_string(&mut body).unwrap();
117-
118-
let fset: FeatureSet<2> = serde_json::from_str(&body).unwrap();
119-
println!("{:#?}", fset);
120-
Ok(())
121-
}
122-
```

src/arrow_compat.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::{
1818
geometry::EsriGeometry,
1919
};
2020

21-
use std::sync::Arc;
21+
use std::{result::Result, sync::Arc};
2222

2323
use geoarrow::GeometryArrayTrait;
2424
use serde_json::Value;
@@ -71,6 +71,20 @@ pub fn featureset_to_arrow<const N: usize>(
7171
}
7272
}
7373

74+
use geoarrow::table::GeoTable;
75+
76+
// TODO create my own error types
77+
/// Given a `FeatureSet`, create a geoarrow `GeoTable`
78+
pub fn featureset_to_geoarrow<const N: usize>(
79+
x: FeatureSet<N>,
80+
) -> Result<GeoTable, geoarrow::error::GeoArrowError> {
81+
let arrow_res = featureset_to_arrow(x)?;
82+
let schema_ref = arrow_res.schema_ref().clone();
83+
let geometry_index = arrow_res.schema().fields.len();
84+
85+
GeoTable::try_new(schema_ref, vec![arrow_res], geometry_index)
86+
}
87+
7488
// convert an esri field to a new arrow field
7589
impl From<Field> for AField {
7690
fn from(value: Field) -> Self {

src/de_array.rs

-2
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,3 @@ pub mod arrays {
5656
deserializer.deserialize_tuple(N, ArrayVisitor::<T, N>(PhantomData))
5757
}
5858
}
59-
60-
// pub struct ArrayVisitor<T, const N: usize>(PhantomData<T>);

src/features.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ use serde_with::{serde_as, DisplayFromStr};
2020
/// Note that both geometry and attributes are optional. This is because
2121
/// we can anticipate receiving _only_ geometries, or _only_ attributes
2222
/// or both together.
23-
#[derive(Debug, Deserialize, Serialize)]
23+
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
2424
pub struct Feature<const N: usize> {
2525
pub geometry: Option<EsriGeometry<N>>,
2626
pub attributes: Option<Map<String, Value>>,
2727
}
2828

2929
/// A set of geometries and their attributes
3030
#[allow(non_snake_case)]
31-
#[derive(Debug, Deserialize, Serialize)]
31+
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
3232
pub struct FeatureSet<const N: usize> {
3333
pub objectIdFieldName: Option<String>,
3434
pub globalIdFieldName: Option<String>,
@@ -44,7 +44,7 @@ pub struct FeatureSet<const N: usize> {
4444
// TODO sqlType, field_type need to be Enums
4545
#[serde_as]
4646
#[allow(non_snake_case)]
47-
#[derive(Clone, Debug, Deserialize, Serialize)]
47+
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
4848
pub struct Field {
4949
pub name: String,
5050
#[serde(rename = "type")]

src/field_type.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
//! Enumeration of valid esri field types
2-
use serde::{Deserialize, Serialize};
32
4-
#[derive(Clone, Debug, Deserialize, Serialize)]
3+
use serde::{Deserialize, Serialize};
4+
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
55
pub enum FieldType {
66
EsriFieldTypeSmallInteger = 0,
77
EsriFieldTypeInteger = 1,
88
EsriFieldTypeSingle = 2,
99
EsriFieldTypeDouble = 3,
10+
#[default]
1011
EsriFieldTypeString = 4,
1112
EsriFieldTypeDate = 5,
1213
EsriFieldTypeOid = 6,

src/geometry.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub struct EsriCoord<const N: usize>(#[serde(with = "arrays")] pub [f64; N]);
2222
/// An `esriGeometryPoint` with fields x, y, z, and m. x and y are both required.
2323
#[skip_serializing_none]
2424
#[allow(non_snake_case)]
25-
#[derive(Clone, Deserialize, Serialize, Debug)]
25+
#[derive(Clone, Deserialize, Serialize, Debug, Default)]
2626
pub struct EsriPoint {
2727
pub x: f64,
2828
pub y: f64,
@@ -38,7 +38,7 @@ pub struct EsriPoint {
3838
/// a `panic!`.
3939
#[skip_serializing_none]
4040
#[allow(non_snake_case)]
41-
#[derive(Clone, Deserialize, Serialize, Debug)]
41+
#[derive(Clone, Deserialize, Serialize, Debug, Default)]
4242
pub struct EsriMultiPoint<const N: usize> {
4343
pub hasZ: Option<bool>,
4444
pub hasM: Option<bool>,
@@ -91,7 +91,7 @@ impl<'a, const N: usize> ExactSizeIterator for EsriMultiPointIterator<'a, N> {
9191
/// This struct is used strictly for representing the internal LineStrings
9292
/// for the `EsriPolygon` and `EsriPolyline` structs. They do not represent
9393
/// any Esri JSON geometry objects.
94-
#[derive(Clone, Deserialize, Serialize, Debug)]
94+
#[derive(Clone, Deserialize, Serialize, Debug, Default)]
9595
pub struct EsriLineString<const N: usize>(pub Vec<EsriCoord<N>>);
9696

9797
pub struct EsriLineStringIterator<'a, const N: usize> {
@@ -137,7 +137,7 @@ impl<const N: usize> EsriLineString<N> {
137137
/// a `panic!`.
138138
#[skip_serializing_none]
139139
#[allow(non_snake_case)]
140-
#[derive(Clone, Deserialize, Serialize, Debug)]
140+
#[derive(Clone, Deserialize, Serialize, Debug, Default)]
141141
pub struct EsriPolyline<const N: usize> {
142142
pub hasZ: Option<bool>,
143143
pub hasM: Option<bool>,
@@ -174,7 +174,7 @@ impl<'a, const N: usize> ExactSizeIterator for EsriPolylineIterator<'a, N> {
174174
/// a `panic!`.
175175
#[skip_serializing_none]
176176
#[allow(non_snake_case)]
177-
#[derive(Clone, Deserialize, Serialize, Debug)]
177+
#[derive(Clone, Deserialize, Serialize, Debug, Default)]
178178
pub struct EsriPolygon<const N: usize> {
179179
pub hasZ: Option<bool>,
180180
pub hasM: Option<bool>,
@@ -249,7 +249,7 @@ impl<const N: usize> EsriGeometry<N> {
249249
// TODO: esriGeometryEnvelope.
250250

251251
#[allow(non_snake_case)]
252-
#[derive(Clone, Deserialize, Serialize, Debug)]
252+
#[derive(Clone, Deserialize, Serialize, Debug, Default)]
253253
#[skip_serializing_none]
254254
pub struct EsriEnvelope {
255255
xmin: f64,

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//!
1+
#![doc = include_str!("../README.md")]
22
//!
33
//! Example usage:
44
//!

src/spatial_reference.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use serde_with::skip_serializing_none;
44

55
/// Read more on [Esri docs site](https://developers.arcgis.com/documentation/common-data-types/geometry-objects.htm#GUID-DFF0E738-5A42-40BC-A811-ACCB5814BABC)
66
#[skip_serializing_none]
7-
#[derive(Serialize, Deserialize, Debug, Clone)]
7+
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
88
pub struct SpatialReference {
99
pub wkid: Option<u32>,
1010
pub latest_wkid: Option<u32>,

0 commit comments

Comments
 (0)