Skip to content

Commit 37445fd

Browse files
committed
Merge #180
180: Add PostGIS and GeoJSON integration/conversions r=frewsxcv a=eeeeeta This PR adds conversions from PostGIS types to `geo` primitives, and conversions from `geo` primitives to GeoJSON and PostGIS types. This integration is gated behind two new features, `postgis-integration` and `geojson-integration`.
2 parents 5d2a779 + 5b877e3 commit 37445fd

File tree

5 files changed

+195
-0
lines changed

5 files changed

+195
-0
lines changed

Cargo.toml

+8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ num-traits = "0.1"
1717
serde = "1.0"
1818
serde_derive = "1.0"
1919
spade = "1.3.0"
20+
postgis = { version = "0.5", optional = true }
21+
22+
[features]
23+
default = []
24+
postgis-integration = ["postgis"]
2025

2126
[dev-dependencies]
2227
approx = "0.1.1"
28+
29+
[package.metadata.docs.rs]
30+
all-features = true

src/algorithm/from_postgis.rs

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use types::{MultiPoint, Point, LineString, MultiLineString, MultiPolygon, Polygon, Geometry, GeometryCollection};
2+
use postgis;
3+
use postgis::ewkb::{GeometryT, GeometryCollectionT};
4+
5+
/// Creates geometry from a PostGIS type.
6+
///
7+
/// Note that PostGIS databases can store data under any spatial
8+
/// reference system - not just WGS84. No attempt is made to convert
9+
/// data between reference systems.
10+
pub trait FromPostgis<T> {
11+
fn from_postgis(T) -> Self;
12+
}
13+
14+
impl<'a, T> FromPostgis<&'a T> for Point<f64> where T: postgis::Point {
15+
fn from_postgis(pt: &'a T) -> Self {
16+
Point::new(pt.x(), pt.y())
17+
}
18+
}
19+
impl<'a, T> FromPostgis<&'a T> for LineString<f64> where T: postgis::LineString<'a> {
20+
fn from_postgis(ls: &'a T) -> Self {
21+
let ret = ls.points()
22+
.map(|x| Point::from_postgis(x))
23+
.collect();
24+
LineString(ret)
25+
}
26+
}
27+
impl<'a, T> FromPostgis<&'a T> for Option<Polygon<f64>> where T: postgis::Polygon<'a> {
28+
/// This returns an `Option`, because it's possible for a PostGIS `Polygon`
29+
/// to contain zero rings, which makes for an invalid `geo::Polygon`.
30+
fn from_postgis(poly: &'a T) -> Self {
31+
let mut rings = poly.rings()
32+
.map(|x| LineString::from_postgis(x))
33+
.collect::<Vec<_>>();
34+
if rings.len() == 0 {
35+
return None;
36+
}
37+
let exterior = rings.remove(0);
38+
Some(Polygon {
39+
exterior,
40+
interiors: rings
41+
})
42+
}
43+
}
44+
impl<'a, T> FromPostgis<&'a T> for MultiPoint<f64> where T: postgis::MultiPoint<'a> {
45+
fn from_postgis(mp: &'a T) -> Self {
46+
let ret = mp.points()
47+
.map(|x| Point::from_postgis(x))
48+
.collect();
49+
MultiPoint(ret)
50+
}
51+
}
52+
impl<'a, T> FromPostgis<&'a T> for MultiLineString<f64> where T: postgis::MultiLineString<'a> {
53+
fn from_postgis(mp: &'a T) -> Self {
54+
let ret = mp.lines()
55+
.map(|x| LineString::from_postgis(x))
56+
.collect();
57+
MultiLineString(ret)
58+
}
59+
}
60+
impl<'a, T> FromPostgis<&'a T> for MultiPolygon<f64> where T: postgis::MultiPolygon<'a> {
61+
/// This implementation discards PostGIS polygons that don't convert
62+
/// (return `None` when `from_postgis()` is called on them).
63+
fn from_postgis(mp: &'a T) -> Self {
64+
let ret = mp.polygons()
65+
.filter_map(|x| Option::from_postgis(x))
66+
.collect();
67+
MultiPolygon(ret)
68+
}
69+
}
70+
impl<'a, T> FromPostgis<&'a GeometryCollectionT<T>> for GeometryCollection<f64> where T: postgis::Point + postgis::ewkb::EwkbRead {
71+
/// This implementation discards geometries that don't convert
72+
/// (return `None` when `from_postgis()` is called on them).
73+
fn from_postgis(gc: &'a GeometryCollectionT<T>) -> Self {
74+
let geoms = gc.geometries.iter()
75+
.filter_map(|x| Option::from_postgis(x))
76+
.collect();
77+
GeometryCollection(geoms)
78+
}
79+
}
80+
impl<'a, T> FromPostgis<&'a GeometryT<T>> for Option<Geometry<f64>> where T: postgis::Point + postgis::ewkb::EwkbRead {
81+
/// This returns an `Option`, because the supplied geometry
82+
/// could be an invalid `Polygon`.
83+
fn from_postgis(geo: &'a GeometryT<T>) -> Self {
84+
Some(match *geo {
85+
GeometryT::Point(ref p) => Geometry::Point(Point::from_postgis(p)),
86+
GeometryT::LineString(ref ls) => Geometry::LineString(LineString::from_postgis(ls)),
87+
GeometryT::Polygon(ref p) => match Option::from_postgis(p) {
88+
Some(p) => Geometry::Polygon(p),
89+
None => return None
90+
},
91+
GeometryT::MultiPoint(ref p) => Geometry::MultiPoint(MultiPoint::from_postgis(p)),
92+
GeometryT::MultiLineString(ref p) => Geometry::MultiLineString(MultiLineString::from_postgis(p)),
93+
GeometryT::MultiPolygon(ref p) => Geometry::MultiPolygon(MultiPolygon::from_postgis(p)),
94+
GeometryT::GeometryCollection(ref p) => Geometry::GeometryCollection(GeometryCollection::from_postgis(p))
95+
})
96+
}
97+
}

src/algorithm/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,9 @@ pub mod translate;
3434
pub mod map_coords;
3535
/// Determine the closest point between two objects.
3636
pub mod closest_point;
37+
/// Produces geometry from PostGIS.
38+
#[cfg(feature = "postgis-integration")]
39+
pub mod from_postgis;
40+
/// Converts geometry into PostGIS types.
41+
#[cfg(feature = "postgis-integration")]
42+
pub mod to_postgis;

src/algorithm/to_postgis.rs

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use types::{MultiPoint, Line, Point, LineString, MultiLineString, MultiPolygon, Polygon, Geometry, GeometryCollection};
2+
use postgis::ewkb;
3+
4+
/// Converts geometry to a PostGIS type.
5+
///
6+
/// Note that PostGIS databases can include a SRID (spatial reference
7+
/// system identifier) for geometry stored in them. You should specify
8+
/// the SRID of your geometry when converting, using `to_postgis_with_srid()`,
9+
/// or use `to_postgis_wgs84()` if your data is standard WGS84.
10+
pub trait ToPostgis<T> {
11+
/// Converts this geometry to a PostGIS type, using the supplied SRID.
12+
fn to_postgis_with_srid(&self, srid: Option<i32>) -> T;
13+
/// Converts this WGS84 geometry to a PostGIS type.
14+
fn to_postgis_wgs84(&self) -> T {
15+
self.to_postgis_with_srid(Some(4326))
16+
}
17+
}
18+
19+
impl ToPostgis<ewkb::Point> for Point<f64> {
20+
fn to_postgis_with_srid(&self, srid: Option<i32>) -> ewkb::Point {
21+
ewkb::Point::new(self.x(), self.y(), srid)
22+
}
23+
}
24+
impl ToPostgis<ewkb::LineString> for Line<f64> {
25+
fn to_postgis_with_srid(&self, srid: Option<i32>) -> ewkb::LineString {
26+
let points = vec![
27+
self.start.to_postgis_with_srid(srid),
28+
self.end.to_postgis_with_srid(srid)
29+
];
30+
ewkb::LineString { points, srid }
31+
}
32+
}
33+
impl ToPostgis<ewkb::Polygon> for Polygon<f64> {
34+
fn to_postgis_with_srid(&self, srid: Option<i32>) -> ewkb::Polygon {
35+
let rings = ::std::iter::once(&self.exterior)
36+
.chain(self.interiors.iter())
37+
.map(|x| x.to_postgis_with_srid(srid))
38+
.collect();
39+
ewkb::Polygon { rings, srid }
40+
}
41+
}
42+
macro_rules! to_postgis_impl {
43+
($from:ident, $to:path, $name:ident, srid) => {
44+
impl ToPostgis<$to> for $from<f64> {
45+
fn to_postgis_with_srid(&self, srid: Option<i32>) -> $to {
46+
let $name = self.0.iter()
47+
.map(|x| x.to_postgis_with_srid(srid))
48+
.collect();
49+
$to { $name, srid }
50+
}
51+
}
52+
};
53+
($from:ident, $to:path, $name:ident) => {
54+
impl ToPostgis<$to> for $from<f64> {
55+
fn to_postgis_with_srid(&self, srid: Option<i32>) -> $to {
56+
let $name = self.0.iter()
57+
.map(|x| x.to_postgis_with_srid(srid))
58+
.collect();
59+
$to { $name }
60+
}
61+
}
62+
};
63+
}
64+
to_postgis_impl!(GeometryCollection, ewkb::GeometryCollection, geometries);
65+
to_postgis_impl!(MultiPolygon, ewkb::MultiPolygon, polygons, srid);
66+
to_postgis_impl!(MultiLineString, ewkb::MultiLineString, lines, srid);
67+
to_postgis_impl!(MultiPoint, ewkb::MultiPoint, points, srid);
68+
to_postgis_impl!(LineString, ewkb::LineString, points, srid);
69+
impl ToPostgis<ewkb::Geometry> for Geometry<f64> {
70+
fn to_postgis_with_srid(&self, srid: Option<i32>) -> ewkb::Geometry {
71+
match *self {
72+
Geometry::Point(ref p) => ewkb::GeometryT::Point(p.to_postgis_with_srid(srid)),
73+
Geometry::Line(ref p) => ewkb::GeometryT::LineString(p.to_postgis_with_srid(srid)),
74+
Geometry::LineString(ref p) => ewkb::GeometryT::LineString(p.to_postgis_with_srid(srid)),
75+
Geometry::Polygon(ref p) => ewkb::GeometryT::Polygon(p.to_postgis_with_srid(srid)),
76+
Geometry::MultiPoint(ref p) => ewkb::GeometryT::MultiPoint(p.to_postgis_with_srid(srid)),
77+
Geometry::MultiLineString(ref p) => ewkb::GeometryT::MultiLineString(p.to_postgis_with_srid(srid)),
78+
Geometry::MultiPolygon(ref p) => ewkb::GeometryT::MultiPolygon(p.to_postgis_with_srid(srid)),
79+
Geometry::GeometryCollection(ref p) => ewkb::GeometryT::GeometryCollection(p.to_postgis_with_srid(srid)),
80+
}
81+
}
82+
}

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ extern crate num_traits;
22
#[macro_use]
33
extern crate serde_derive;
44
extern crate spade;
5+
#[cfg(feature = "postgis-integration")]
6+
extern crate postgis;
57

68
pub use traits::ToGeo;
79
pub use types::*;

0 commit comments

Comments
 (0)