Skip to content

Commit

Permalink
Merge branch 'main' into mkirk/add-geojson-tostring
Browse files Browse the repository at this point in the history
  • Loading branch information
frewsxcv authored Oct 19, 2024
2 parents 71ab833 + f14a9a4 commit 206edb0
Show file tree
Hide file tree
Showing 17 changed files with 601 additions and 239 deletions.
54 changes: 51 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,58 @@
on: [push, pull_request]
name: Run tests
name: Run Geojson tests

on:
push:
branches:
- main
- staging
- trying
- release/**
pull_request:
merge_group:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref_name }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always

jobs:
build_and_test:
name: Build and test all Geojson features
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- run: cargo install cargo-all-features
- run: cargo build-all-features --verbose
- run: cargo test-all-features --verbose
check:
name: Geojson Rustfmt and Clippy check
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- name: Check formatting using Rustfmt
run: cargo fmt --check
- name: Lint using Clippy
run: cargo clippy --tests
all_checks_complete:
needs:
- build_and_test
- check
if: always()
runs-on: ubuntu-latest
steps:
- name: Result
run: |
jq -C <<< "${needs}"
# Check if all needs were successful or skipped.
"$(jq -r 'all(.result as $result | (["success", "skipped"] | contains([$result])))' <<< "${needs}")"
env:
needs: ${{ toJson(needs) }}
11 changes: 10 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@

## Unreleased

* Add support of serializing optional `geo-types` with `serialize_optional_geometry`.
* Add support of deserializing optional `geo-types` with `deserialize_optional_geometry`.
* Add support for foreign members to `FeatureWriter`.
* Added conversion from `Vec<Feature>` to `GeoJson`.
* Added `GeoJson::to_string_pretty` as convenience wrappers around the same `serde_json` methods.
* Changed `Serialize` impls to avoid creating intermediate `JsonObject`s.
* Better CI: lint, all features
* Implement `Default` on `FeatureCollection`.
* Added `GeometryCollection::try_from(&GeoJson)` and deprecated
`quick_collection` for conventional naming and simpler docs.
* <https://github.com/georust/geojson/pulls/214>

## 0.24.1

Expand All @@ -12,7 +21,7 @@

## 0.24.0

* Added `geojson::{ser, de}` helpers to convert your custom struct to and from GeoJSON.
* Added `geojson::{ser, de}` helpers to convert your custom struct to and from GeoJSON.
* For external geometry types like geo-types, use the `serialize_geometry`/`deserialize_geometry` helpers.
* Example:
```
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ default = ["geo-types"]
[dependencies]
serde = { version="~1.0", features = ["derive"] }
serde_json = "~1.0"
geo-types = { version = "0.7", features = ["serde"], optional = true }
geo-types = { version = "0.7.13", features = ["serde"], optional = true }
thiserror = "1.0.20"
log = "0.4.17"

Expand Down
8 changes: 3 additions & 5 deletions benches/to_geo_types.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use std::convert::TryFrom;

fn benchmark_group(c: &mut Criterion) {
let geojson_str = include_str!("../tests/fixtures/countries.geojson");
let geojson = geojson_str.parse::<geojson::GeoJson>().unwrap();

#[cfg(feature = "geo-types")]
c.bench_function("quick_collection", move |b| {
b.iter(|| {
let _: Result<geo_types::GeometryCollection<f64>, _> =
black_box(geojson::quick_collection(&geojson));
});
c.bench_function("Convert to geo-types", move |b| {
b.iter(|| black_box(geo_types::GeometryCollection::<f64>::try_from(&geojson).unwrap()));
});
}

Expand Down
54 changes: 27 additions & 27 deletions src/conversion/from_geo_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ where
T: CoordFloat,
{
line_string
.points_iter()
.points()
.map(|point| create_point_type(&point))
.collect()
}
Expand Down Expand Up @@ -242,7 +242,7 @@ where
{
let mut coords = vec![polygon
.exterior()
.points_iter()
.points()
.map(|point| create_point_type(&point))
.collect()];

Expand Down Expand Up @@ -271,8 +271,8 @@ where
mod tests {
use crate::{GeoJson, Geometry, Value};
use geo_types::{
Coordinate, GeometryCollection, Line, LineString, MultiLineString, MultiPoint,
MultiPolygon, Point, Polygon, Rect, Triangle,
Coord, GeometryCollection, Line, LineString, MultiLineString, MultiPoint, MultiPolygon,
Point, Polygon, Rect, Triangle,
};

#[test]
Expand Down Expand Up @@ -356,50 +356,50 @@ mod tests {

#[test]
fn geo_triangle_conversion_test() {
let c1 = Coordinate { x: 0., y: 0. };
let c2 = Coordinate { x: 10., y: 20. };
let c3 = Coordinate { x: 20., y: -10. };
let c1: Coord<f64> = Coord { x: 0., y: 0. };
let c2: Coord<f64> = Coord { x: 10., y: 20. };
let c3: Coord<f64> = Coord { x: 20., y: -10. };

let triangle = Triangle(c1, c2, c3);

let geojson_polygon = Value::from(&triangle);

// Geo-types Polygon construction introduces an extra vertex: let's check it!
if let Value::Polygon(c) = geojson_polygon {
assert_almost_eq!(c1.x as f64, c[0][0][0], 1e-6);
assert_almost_eq!(c1.y as f64, c[0][0][1], 1e-6);
assert_almost_eq!(c2.x as f64, c[0][1][0], 1e-6);
assert_almost_eq!(c2.y as f64, c[0][1][1], 1e-6);
assert_almost_eq!(c3.x as f64, c[0][2][0], 1e-6);
assert_almost_eq!(c3.y as f64, c[0][2][1], 1e-6);
assert_almost_eq!(c1.x as f64, c[0][3][0], 1e-6);
assert_almost_eq!(c1.y as f64, c[0][3][1], 1e-6);
assert_almost_eq!(c1.x, c[0][0][0], 1e-6);
assert_almost_eq!(c1.y, c[0][0][1], 1e-6);
assert_almost_eq!(c2.x, c[0][1][0], 1e-6);
assert_almost_eq!(c2.y, c[0][1][1], 1e-6);
assert_almost_eq!(c3.x, c[0][2][0], 1e-6);
assert_almost_eq!(c3.y, c[0][2][1], 1e-6);
assert_almost_eq!(c1.x, c[0][3][0], 1e-6);
assert_almost_eq!(c1.y, c[0][3][1], 1e-6);
} else {
panic!("Not valid geometry {:?}", geojson_polygon);
}
}

#[test]
fn geo_rect_conversion_test() {
let c1 = Coordinate { x: 0., y: 0. };
let c2 = Coordinate { x: 10., y: 20. };
let c1: Coord<f64> = Coord { x: 0., y: 0. };
let c2: Coord<f64> = Coord { x: 10., y: 20. };

let rect = Rect::new(c1, c2);

let geojson_polygon = Value::from(&rect);

// Geo-types Polygon construction introduces an extra vertex: let's check it!
if let Value::Polygon(c) = geojson_polygon {
assert_almost_eq!(c1.x as f64, c[0][0][0], 1e-6);
assert_almost_eq!(c1.y as f64, c[0][0][1], 1e-6);
assert_almost_eq!(c1.x as f64, c[0][1][0], 1e-6);
assert_almost_eq!(c2.y as f64, c[0][1][1], 1e-6);
assert_almost_eq!(c2.x as f64, c[0][2][0], 1e-6);
assert_almost_eq!(c2.y as f64, c[0][2][1], 1e-6);
assert_almost_eq!(c2.x as f64, c[0][3][0], 1e-6);
assert_almost_eq!(c1.y as f64, c[0][3][1], 1e-6);
assert_almost_eq!(c1.x as f64, c[0][4][0], 1e-6);
assert_almost_eq!(c1.y as f64, c[0][4][1], 1e-6);
assert_almost_eq!(c1.x, c[0][0][0], 1e-6);
assert_almost_eq!(c1.y, c[0][0][1], 1e-6);
assert_almost_eq!(c1.x, c[0][1][0], 1e-6);
assert_almost_eq!(c2.y, c[0][1][1], 1e-6);
assert_almost_eq!(c2.x, c[0][2][0], 1e-6);
assert_almost_eq!(c2.y, c[0][2][1], 1e-6);
assert_almost_eq!(c2.x, c[0][3][0], 1e-6);
assert_almost_eq!(c1.y, c[0][3][1], 1e-6);
assert_almost_eq!(c1.x, c[0][4][0], 1e-6);
assert_almost_eq!(c1.y, c[0][4][1], 1e-6);
} else {
panic!("Not valid geometry {:?}", geojson_polygon);
}
Expand Down
47 changes: 12 additions & 35 deletions src/conversion/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use geo_types::{self, CoordFloat, GeometryCollection};
use geo_types::CoordFloat;

use crate::geojson::GeoJson;
use crate::geojson::GeoJson::{Feature, FeatureCollection, Geometry};

use crate::Result;
use std::convert::TryInto;
use std::convert::TryFrom;

#[cfg(test)]
macro_rules! assert_almost_eq {
Expand Down Expand Up @@ -79,32 +78,6 @@ macro_rules! try_from_owned_value {
pub(crate) mod from_geo_types;
pub(crate) mod to_geo_types;

// Process top-level `GeoJSON` items, returning a geo_types::GeometryCollection or an Error
fn process_geojson<T>(gj: &GeoJson) -> Result<geo_types::GeometryCollection<T>>
where
T: CoordFloat,
{
match gj {
FeatureCollection(collection) => Ok(GeometryCollection(
collection
.features
.iter()
// Only pass on non-empty geometries
.filter_map(|feature| feature.geometry.as_ref())
.map(|geometry| geometry.clone().try_into())
.collect::<Result<_>>()?,
)),
Feature(feature) => {
if let Some(geometry) = &feature.geometry {
Ok(GeometryCollection(vec![geometry.clone().try_into()?]))
} else {
Ok(GeometryCollection(vec![]))
}
}
Geometry(geometry) => Ok(GeometryCollection(vec![geometry.clone().try_into()?])),
}
}

/// A shortcut for producing `geo_types` [GeometryCollection](../geo_types/struct.GeometryCollection.html) objects
/// from arbitrary valid GeoJSON input.
///
Expand All @@ -113,7 +86,8 @@ where
/// # Example
///
/// ```
/// use geo_types::GeometryCollection;
/// use geo_types::{Geometry, GeometryCollection, Point};
/// #[allow(deprecated)]
/// use geojson::{quick_collection, GeoJson};
///
/// let geojson_str = r#"
Expand All @@ -125,23 +99,26 @@ where
/// "properties": {},
/// "geometry": {
/// "type": "Point",
/// "coordinates": [
/// -0.13583511114120483,
/// 51.5218870403801
/// ]
/// "coordinates": [-1.0, 2.0]
/// }
/// }
/// ]
/// }
/// "#;
/// let geojson = geojson_str.parse::<GeoJson>().unwrap();
/// // Turn the GeoJSON string into a geo_types GeometryCollection
/// #[allow(deprecated)]
/// let mut collection: GeometryCollection<f64> = quick_collection(&geojson).unwrap();
/// assert_eq!(collection[0], Geometry::Point(Point::new(-1.0, 2.0)))
/// ```
#[deprecated(
since = "0.24.1",
note = "use `geo_types::GeometryCollection::try_from(&geojson)` instead"
)]
#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
pub fn quick_collection<T>(gj: &GeoJson) -> Result<geo_types::GeometryCollection<T>>
where
T: CoordFloat,
{
process_geojson(gj)
geo_types::GeometryCollection::try_from(gj)
}
50 changes: 41 additions & 9 deletions src/conversion/to_geo_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ use geo_types::{self, CoordFloat};

use crate::geometry;

use crate::{
quick_collection, Feature, FeatureCollection, GeoJson, LineStringType, PointType, PolygonType,
};
use crate::{Error, Result};
use crate::{Feature, FeatureCollection, GeoJson, LineStringType, PointType, PolygonType};
use std::convert::{TryFrom, TryInto};

#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
Expand Down Expand Up @@ -243,6 +241,40 @@ impl_try_from_geom_value![
GeometryCollection
];

impl<T: CoordFloat> TryFrom<&GeoJson> for geo_types::GeometryCollection<T> {
type Error = Error;

/// Process top-level `GeoJSON` items, returning a geo_types::GeometryCollection or an Error
fn try_from(gj: &GeoJson) -> Result<geo_types::GeometryCollection<T>>
where
T: CoordFloat,
{
match gj {
GeoJson::FeatureCollection(collection) => Ok(geo_types::GeometryCollection(
collection
.features
.iter()
// Only pass on non-empty geometries
.filter_map(|feature| feature.geometry.as_ref())
.map(|geometry| geometry.clone().try_into())
.collect::<Result<_>>()?,
)),
GeoJson::Feature(feature) => {
if let Some(geometry) = &feature.geometry {
Ok(geo_types::GeometryCollection(vec![geometry
.clone()
.try_into()?]))
} else {
Ok(geo_types::GeometryCollection(vec![]))
}
}
GeoJson::Geometry(geometry) => Ok(geo_types::GeometryCollection(vec![geometry
.clone()
.try_into()?])),
}
}
}

#[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))]
impl<T> TryFrom<FeatureCollection> for geo_types::Geometry<T>
where
Expand All @@ -251,9 +283,9 @@ where
type Error = Error;

fn try_from(val: FeatureCollection) -> Result<geo_types::Geometry<T>> {
Ok(geo_types::Geometry::GeometryCollection(quick_collection(
&GeoJson::FeatureCollection(val),
)?))
Ok(geo_types::Geometry::GeometryCollection(
geo_types::GeometryCollection::try_from(&GeoJson::FeatureCollection(val))?,
))
}
}

Expand All @@ -273,11 +305,11 @@ where
}
}

fn create_geo_coordinate<T>(point_type: &PointType) -> geo_types::Coordinate<T>
fn create_geo_coordinate<T>(point_type: &PointType) -> geo_types::Coord<T>
where
T: CoordFloat,
{
geo_types::Coordinate {
geo_types::Coord {
x: T::from(point_type[0]).unwrap(),
y: T::from(point_type[1]).unwrap(),
}
Expand Down Expand Up @@ -324,7 +356,7 @@ where
T: CoordFloat,
{
let exterior = polygon_type
.get(0)
.first()
.map(|e| create_geo_line_string(e))
.unwrap_or_else(|| create_geo_line_string(&vec![]));

Expand Down
Loading

0 comments on commit 206edb0

Please sign in to comment.