Skip to content

Commit

Permalink
Merge #43
Browse files Browse the repository at this point in the history
43: Use generic type for precision in conversion r=frewsxcv a=pjsier

Attempt to close #33. I based this off of the conversion implementation in [geojson](https://github.com/georust/geojson/blob/master/src/conversion.rs) and only used generic types for converting to `geo_types` objects

Initially I tried to update the `From` trait implementation, but I used `Into` instead because updating `From` gave me the following error:

```
type parameter `T` must be used as the type parameter for some local type
```

If I missed something that would make `From` work instead I can make that change. Let me know if I should change that or anything else here, thanks!

Co-authored-by: pjsier <pjsier@gmail.com>
  • Loading branch information
bors[bot] and pjsier committed Oct 26, 2019
2 parents 30c53e6 + 5aa1fc4 commit 1faf2b3
Show file tree
Hide file tree
Showing 13 changed files with 399 additions and 174 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -11,6 +11,7 @@ keywords = ["geo", "geospatial", "wkt"]

[dependencies]
geo-types = {version = "0.4", optional = true}
num-traits = "0.2"

[dev-dependencies]
criterion = { version = "0.2" }
Expand Down
96 changes: 67 additions & 29 deletions src/conversion.rs
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

extern crate geo_types;
extern crate num_traits;

use std::fmt;
use types::*;
Expand All @@ -33,17 +34,33 @@ impl fmt::Display for Error {
}
}

fn try_into_point(point: &Point) -> Result<geo_types::Geometry<f64>, Error> {
fn create_geo_coordinate<T>(coord: &Coord<T>) -> geo_types::Coordinate<T>
where
T: num_traits::Float,
{
geo_types::Coordinate {
x: T::from(coord.x).unwrap(),
y: T::from(coord.y).unwrap(),
}
}

fn try_into_point<T>(point: &Point<T>) -> Result<geo_types::Geometry<T>, Error>
where
T: num_traits::Float,
{
match point.0 {
Some(ref c) => {
let geo_point: geo_types::Point<f64> = (c.x, c.y).into();
let geo_point: geo_types::Point<T> = (c.x, c.y).into();
Ok(geo_point.into())
}
None => Err(Error::PointConversionError),
}
}

pub fn try_into_geometry(geometry: &Geometry) -> Result<geo_types::Geometry<f64>, Error> {
pub fn try_into_geometry<T>(geometry: &Geometry<T>) -> Result<geo_types::Geometry<T>, Error>
where
T: num_traits::Float,
{
match geometry {
Geometry::Point(point) => try_into_point(point),
Geometry::LineString(linestring) => Ok(linestring.into()),
Expand All @@ -57,61 +74,78 @@ pub fn try_into_geometry(geometry: &Geometry) -> Result<geo_types::Geometry<f64>
}
}

impl<'a> From<&'a LineString> for geo_types::Geometry<f64> {
fn from(linestring: &LineString) -> Self {
let geo_linestring: geo_types::LineString<f64> =
linestring.0.iter().map(|c| (c.x, c.y)).collect();
impl<'a, T> Into<geo_types::Geometry<T>> for &'a LineString<T>
where
T: num_traits::Float,
{
fn into(self) -> geo_types::Geometry<T> {
let geo_linestring: geo_types::LineString<T> =
self.0.iter().map(|c| create_geo_coordinate(&c)).collect();

geo_linestring.into()
}
}

impl<'a> From<&'a MultiLineString> for geo_types::Geometry<f64> {
fn from(multilinestring: &MultiLineString) -> Self {
let geo_multilinestring: geo_types::MultiLineString<f64> = multilinestring
impl<'a, T> Into<geo_types::Geometry<T>> for &'a MultiLineString<T>
where
T: num_traits::Float,
{
fn into(self) -> geo_types::Geometry<T> {
let geo_multilinestring: geo_types::MultiLineString<T> = self
.0
.iter()
.map(|l| l.0.iter().map(|c| (c.x, c.y)).collect::<Vec<_>>())
.map(|l| l.0.iter().map(|c| create_geo_coordinate(&c)).collect::<Vec<_>>())
.collect();

geo_multilinestring.into()
}
}

fn w_polygon_to_g_polygon(polygon: &Polygon) -> geo_types::Polygon<f64> {
fn w_polygon_to_g_polygon<T>(polygon: &Polygon<T>) -> geo_types::Polygon<T>
where
T: num_traits::Float,
{
let mut iter = polygon
.0
.iter()
.map(|l| l.0.iter().map(|c| (c.x, c.y)).collect::<Vec<_>>().into());
.iter().map(|l| l.0.iter().map(|c| create_geo_coordinate(c)).collect::<Vec<_>>().into());

match iter.next() {
Some(interior) => geo_types::Polygon::new(interior, iter.collect()),
None => geo_types::Polygon::new(geo_types::LineString(vec![]), vec![]),
}
}

impl<'a> From<&'a Polygon> for geo_types::Geometry<f64> {
fn from(polygon: &Polygon) -> Self {
w_polygon_to_g_polygon(polygon).into()
impl<'a, T> Into<geo_types::Geometry<T>> for &'a Polygon<T>
where
T: num_traits::Float,
{
fn into(self) -> geo_types::Geometry<T> {
w_polygon_to_g_polygon(self).into()
}
}

impl<'a> From<&'a MultiPoint> for geo_types::Geometry<f64> {
fn from(multipoint: &MultiPoint) -> Self {
let geo_multipoint: geo_types::MultiPoint<f64> = multipoint
impl<'a, T> Into<geo_types::Geometry<T>> for &'a MultiPoint<T>
where
T: num_traits::Float,
{
fn into(self) -> geo_types::Geometry<T> {
let geo_multipoint: geo_types::MultiPoint<T> = self
.0
.iter()
.filter_map(|p| p.0.as_ref())
.map(|c| (c.x, c.y))
.map(|c| create_geo_coordinate(c))
.collect();

geo_multipoint.into()
}
}

impl<'a> From<&'a MultiPolygon> for geo_types::Geometry<f64> {
fn from(multipolygon: &MultiPolygon) -> Self {
let geo_multipolygon: geo_types::MultiPolygon<f64> = multipolygon
impl<'a, T> Into<geo_types::Geometry<T>> for &'a MultiPolygon<T>
where
T: num_traits::Float,
{
fn into(self) -> geo_types::Geometry<T> {
let geo_multipolygon: geo_types::MultiPolygon<T> = self
.0
.iter()
.map(|p| w_polygon_to_g_polygon(p))
Expand All @@ -121,10 +155,13 @@ impl<'a> From<&'a MultiPolygon> for geo_types::Geometry<f64> {
}
}

pub fn try_into_geometry_collection(
geometrycollection: &GeometryCollection,
) -> Result<geo_types::Geometry<f64>, Error> {
let geo_geometrycollection: geo_types::GeometryCollection<f64> = geometrycollection
pub fn try_into_geometry_collection<T>(
geometrycollection: &GeometryCollection<T>,
) -> Result<geo_types::Geometry<T>, Error>
where
T: num_traits::Float,
{
let geo_geometrycollection: geo_types::GeometryCollection<T> = geometrycollection
.0
.iter()
.map(|g| try_into_geometry(g))
Expand All @@ -142,7 +179,8 @@ mod tests {
#[test]
fn convert_empty_point() {
let point = Point(None).as_item();
assert!(try_into_geometry(&point).is_err());
let res: Result<geo_types::Geometry<f64>, Error> = try_into_geometry(&point);
assert!(res.is_err());
}

#[test]
Expand Down
87 changes: 54 additions & 33 deletions src/lib.rs
Expand Up @@ -12,8 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.

extern crate num_traits;

use std::default::Default;
use std::fmt;
use std::str::FromStr;

use tokenizer::{PeekableTokens, Token, Tokens};
use types::GeometryCollection;
Expand All @@ -37,53 +40,62 @@ pub use towkt::ToWkt;
#[cfg(feature = "geo-types")]
pub mod conversion;

pub enum Geometry {
Point(Point),
LineString(LineString),
Polygon(Polygon),
MultiPoint(MultiPoint),
MultiLineString(MultiLineString),
MultiPolygon(MultiPolygon),
GeometryCollection(GeometryCollection),
pub enum Geometry<T>
where
T: num_traits::Float
{
Point(Point<T>),
LineString(LineString<T>),
Polygon(Polygon<T>),
MultiPoint(MultiPoint<T>),
MultiLineString(MultiLineString<T>),
MultiPolygon(MultiPolygon<T>),
GeometryCollection(GeometryCollection<T>),
}

impl Geometry {
fn from_word_and_tokens(word: &str, tokens: &mut PeekableTokens) -> Result<Self, &'static str> {
impl<T> Geometry<T>
where
T: num_traits::Float + FromStr + Default
{
fn from_word_and_tokens(word: &str, tokens: &mut PeekableTokens<T>) -> Result<Self, &'static str> {
match word {
w if w.eq_ignore_ascii_case("POINT") => {
let x = <Point as FromTokens>::from_tokens_with_parens(tokens);
let x = <Point<T> as FromTokens<T>>::from_tokens_with_parens(tokens);
x.map(|y| y.as_item())
}
w if w.eq_ignore_ascii_case("LINESTRING") => {
let x = <LineString as FromTokens>::from_tokens_with_parens(tokens);
let x = <LineString<T> as FromTokens<T>>::from_tokens_with_parens(tokens);
x.map(|y| y.as_item())
}
w if w.eq_ignore_ascii_case("POLYGON") => {
let x = <Polygon as FromTokens>::from_tokens_with_parens(tokens);
let x = <Polygon<T> as FromTokens<T>>::from_tokens_with_parens(tokens);
x.map(|y| y.as_item())
}
w if w.eq_ignore_ascii_case("MULTIPOINT") => {
let x = <MultiPoint as FromTokens>::from_tokens_with_parens(tokens);
let x = <MultiPoint<T> as FromTokens<T>>::from_tokens_with_parens(tokens);
x.map(|y| y.as_item())
}
w if w.eq_ignore_ascii_case("MULTILINESTRING") => {
let x = <MultiLineString as FromTokens>::from_tokens_with_parens(tokens);
let x = <MultiLineString<T> as FromTokens<T>>::from_tokens_with_parens(tokens);
x.map(|y| y.as_item())
}
w if w.eq_ignore_ascii_case("MULTIPOLYGON") => {
let x = <MultiPolygon as FromTokens>::from_tokens_with_parens(tokens);
let x = <MultiPolygon<T> as FromTokens<T>>::from_tokens_with_parens(tokens);
x.map(|y| y.as_item())
}
w if w.eq_ignore_ascii_case("GEOMETRYCOLLECTION") => {
let x = <GeometryCollection as FromTokens>::from_tokens_with_parens(tokens);
let x = <GeometryCollection<T> as FromTokens<T>>::from_tokens_with_parens(tokens);
x.map(|y| y.as_item())
}
_ => Err("Invalid type encountered"),
}
}
}

impl fmt::Display for Geometry {
impl<T> fmt::Display for Geometry<T>
where
T: num_traits::Float + fmt::Display
{
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
Geometry::Point(point) => point.fmt(f),
Expand All @@ -97,16 +109,22 @@ impl fmt::Display for Geometry {
}
}

pub struct Wkt {
pub items: Vec<Geometry>,
pub struct Wkt<T>
where
T: num_traits::Float
{
pub items: Vec<Geometry<T>>,
}

impl Wkt {
impl<T> Wkt<T>
where
T: num_traits::Float + FromStr + Default
{
pub fn new() -> Self {
Wkt { items: vec![] }
}

pub fn add_item(&mut self, item: Geometry) {
pub fn add_item(&mut self, item: Geometry<T>) {
self.items.push(item);
}

Expand All @@ -115,7 +133,7 @@ impl Wkt {
Wkt::from_tokens(tokens)
}

fn from_tokens(tokens: Tokens) -> Result<Self, &'static str> {
fn from_tokens(tokens: Tokens<T>) -> Result<Self, &'static str> {
let mut wkt = Wkt::new();
let mut tokens = tokens.peekable();
let word = match tokens.next() {
Expand All @@ -136,10 +154,13 @@ impl Wkt {
}
}

trait FromTokens: Sized + Default {
fn from_tokens(tokens: &mut PeekableTokens) -> Result<Self, &'static str>;
trait FromTokens<T>: Sized + Default
where
T: num_traits::Float + FromStr + Default
{
fn from_tokens(tokens: &mut PeekableTokens<T>) -> Result<Self, &'static str>;

fn from_tokens_with_parens(tokens: &mut PeekableTokens) -> Result<Self, &'static str> {
fn from_tokens_with_parens(tokens: &mut PeekableTokens<T>) -> Result<Self, &'static str> {
match tokens.next() {
Some(Token::ParenOpen) => (),
Some(Token::Word(ref s)) if s.eq_ignore_ascii_case("EMPTY") => {
Expand All @@ -155,9 +176,9 @@ trait FromTokens: Sized + Default {
result
}

fn comma_many<F>(f: F, tokens: &mut PeekableTokens) -> Result<Vec<Self>, &'static str>
fn comma_many<F>(f: F, tokens: &mut PeekableTokens<T>) -> Result<Vec<Self>, &'static str>
where
F: Fn(&mut PeekableTokens) -> Result<Self, &'static str>,
F: Fn(&mut PeekableTokens<T>) -> Result<Self, &'static str>,
{
let mut items = Vec::new();

Expand All @@ -182,20 +203,20 @@ mod tests {

#[test]
fn empty_string() {
let wkt = Wkt::from_str("").ok().unwrap();
let wkt: Wkt<f64> = Wkt::from_str("").ok().unwrap();
assert_eq!(0, wkt.items.len());
}

#[test]
fn empty_items() {
let mut wkt = Wkt::from_str("POINT EMPTY").ok().unwrap();
let mut wkt: Wkt<f64> = Wkt::from_str("POINT EMPTY").ok().unwrap();
assert_eq!(1, wkt.items.len());
match wkt.items.pop().unwrap() {
Geometry::Point(Point(None)) => (),
_ => unreachable!(),
};

let mut wkt = Wkt::from_str("MULTIPOLYGON EMPTY").ok().unwrap();
let mut wkt: Wkt<f64> = Wkt::from_str("MULTIPOLYGON EMPTY").ok().unwrap();
assert_eq!(1, wkt.items.len());
match wkt.items.pop().unwrap() {
Geometry::MultiPolygon(MultiPolygon(polygons)) => assert_eq!(polygons.len(), 0),
Expand All @@ -205,7 +226,7 @@ mod tests {

#[test]
fn lowercase_point() {
let mut wkt = Wkt::from_str("point EMPTY").ok().unwrap();
let mut wkt: Wkt<f64> = Wkt::from_str("point EMPTY").ok().unwrap();
assert_eq!(1, wkt.items.len());
match wkt.items.pop().unwrap() {
Geometry::Point(Point(None)) => (),
Expand All @@ -215,7 +236,7 @@ mod tests {

#[test]
fn invalid_number() {
if let Err(msg) = Wkt::from_str("POINT (10 20.1A)") {
if let Err(msg) = <Wkt<f64>>::from_str("POINT (10 20.1A)") {
assert_eq!("Expected a number for the Y coordinate", msg);
} else {
panic!("Should not have parsed");
Expand Down

0 comments on commit 1faf2b3

Please sign in to comment.