wkt/
lib.rs

1#![doc(html_logo_url = "https://raw.githubusercontent.com/georust/meta/master/logo/logo.png")]
2// Copyright 2014-2015 The GeoRust Developers
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16// The unstable `doc_auto_cfg` feature annotates documentation with any required cfg/features
17// needed for optional items. We set the `docsrs` config when building for docs.rs. To use it
18// in a local docs build, run: `cargo +nightly rustdoc --all-features -- --cfg docsrs`
19#![cfg_attr(docsrs, feature(doc_auto_cfg))]
20
21//! The `wkt` crate provides conversions to and from the [WKT (Well Known Text)](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry)
22//! geometry format.
23//!
24//! Conversions are available via the [`TryFromWkt`] and [`ToWkt`] traits, with implementations for
25//! [`geo_types`] and [`geo_traits`] primitives enabled by default.
26//!
27//! For advanced usage, see the [`types`](crate::types) module for a list of internally used types.
28//!
29//! This crate has optional `serde` integration for deserializing fields containing WKT. See
30//! [`deserialize`] for an example.
31//!
32//! # Examples
33//!
34//! ## Read `geo_types` from a WKT string
35#![cfg_attr(feature = "geo-types", doc = "```")]
36#![cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
37//! // This example requires the geo-types feature (on by default).
38//! use wkt::TryFromWkt;
39//! use geo_types::Point;
40//!
41//! let point: Point<f64> = Point::try_from_wkt_str("POINT(10 20)").unwrap();
42//! assert_eq!(point.y(), 20.0);
43//! ```
44//!
45//! ## Write `geo_types` to a WKT string
46#![cfg_attr(feature = "geo-types", doc = "```")]
47#![cfg_attr(not(feature = "geo-types"), doc = "```ignore")]
48//! // This example requires the geo-types feature (on by default).
49//! use wkt::ToWkt;
50//! use geo_types::Point;
51//!
52//! let point: Point<f64> = Point::new(1.0, 2.0);
53//! assert_eq!(point.wkt_string(), "POINT(1 2)");
54//! ```
55//!
56//! ## Read or write your own geometry types
57//!
58//! Not using `geo-types` for your geometries? No problem!
59//!
60//! As of `wkt` version 0.12, this crate provides read and write integration with [`geo_traits`],
61//! a collection of geometry access traits, to provide zero-copy integration with geometry
62//! representations other than `geo-types`.
63//!
64//! This integration allows you to transparently read data from this crate's intermediate geometry
65//! structure, and it allows you to write WKT strings directly from your geometry without any
66//! intermediate representation.
67//!
68//! ### Reading
69//!
70//! You can use [`Wkt::from_str`] to parse a WKT string into this crate's intermediate geometry
71//! structure. `Wkt` (and all structs defined in [types]) implement traits from [geo_traits]. You
72//! can write functions in terms of those traits and you'll be able to work with the parsed WKT
73//! without any further overhead.
74//!
75//! ```
76//! use std::str::FromStr;
77//! use wkt::Wkt;
78//! use geo_traits::{GeometryTrait, GeometryType};
79//!
80//! fn is_line_string(geom: &impl GeometryTrait<T = f64>) {
81//!     assert!(matches!(geom.as_type(), GeometryType::LineString(_)))
82//! }
83//!
84//! let wktls: Wkt<f64> = Wkt::from_str("LINESTRING(10 20, 20 30)").unwrap();
85//! is_line_string(&wktls);
86//! ```
87//!
88//! Working with the trait definition is preferable to working with `wkt::Wkt` directly, as the
89//! geometry trait will work with many different geometry representations; not just the one from
90//! this crate.
91//!
92//! ### Writing
93//!
94//! Consult the functions provided in [`to_wkt`]. Those functions will write any `geo_traits` object to WKT without any intermediate overhead.
95//!
96//! Implement [`geo_traits`] on your own geometry representation and those functions will work out
97//! of the box on your data.
98use std::default::Default;
99use std::fmt;
100use std::str::FromStr;
101
102use geo_traits::{
103    GeometryCollectionTrait, GeometryTrait, LineStringTrait, MultiLineStringTrait, MultiPointTrait,
104    MultiPolygonTrait, PointTrait, PolygonTrait,
105};
106use num_traits::{Float, Num, NumCast};
107
108use crate::to_wkt::write_geometry;
109use crate::tokenizer::{PeekableTokens, Token, Tokens};
110use crate::types::{
111    Dimension, GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon, Point,
112    Polygon,
113};
114
115pub mod to_wkt;
116mod tokenizer;
117
118/// Error variant for this crate
119pub mod error;
120/// `WKT` primitive types and collections
121pub mod types;
122
123mod infer_type;
124
125pub use infer_type::infer_type;
126
127#[cfg(feature = "geo-types")]
128extern crate geo_types;
129
130pub use crate::to_wkt::ToWkt;
131
132#[cfg(feature = "geo-types")]
133#[deprecated(note = "renamed module to `wkt::geo_types_from_wkt`")]
134pub mod conversion;
135#[cfg(feature = "geo-types")]
136pub mod geo_types_from_wkt;
137#[cfg(feature = "geo-types")]
138mod geo_types_to_wkt;
139
140#[cfg(feature = "serde")]
141extern crate serde;
142#[cfg(feature = "serde")]
143pub mod deserialize;
144#[cfg(feature = "serde")]
145pub use deserialize::deserialize_wkt;
146
147mod from_wkt;
148pub use from_wkt::TryFromWkt;
149
150#[cfg(all(feature = "serde", feature = "geo-types"))]
151#[allow(deprecated)]
152pub use deserialize::geo_types::deserialize_geometry;
153
154#[cfg(all(feature = "serde", feature = "geo-types"))]
155#[deprecated(
156    since = "0.10.2",
157    note = "instead: use wkt::deserialize::geo_types::deserialize_point"
158)]
159pub use deserialize::geo_types::deserialize_point;
160
161pub trait WktNum: Num + NumCast + PartialOrd + PartialEq + Copy + fmt::Debug {}
162impl<T> WktNum for T where T: Num + NumCast + PartialOrd + PartialEq + Copy + fmt::Debug {}
163
164pub trait WktFloat: WktNum + Float {}
165impl<T> WktFloat for T where T: WktNum + Float {}
166
167#[derive(Clone, Debug, PartialEq)]
168/// All supported WKT geometry [`types`]
169pub enum Wkt<T>
170where
171    T: WktNum,
172{
173    Point(Point<T>),
174    LineString(LineString<T>),
175    Polygon(Polygon<T>),
176    MultiPoint(MultiPoint<T>),
177    MultiLineString(MultiLineString<T>),
178    MultiPolygon(MultiPolygon<T>),
179    GeometryCollection(GeometryCollection<T>),
180}
181
182impl<T> Wkt<T>
183where
184    T: WktNum + FromStr + Default,
185{
186    fn from_word_and_tokens(
187        word: &str,
188        tokens: &mut PeekableTokens<T>,
189    ) -> Result<Self, &'static str> {
190        // Normally Z/M/ZM is separated by a space from the primary WKT word. E.g. `POINT Z`
191        // instead of `POINTZ`. However we wish to support both types (in reading). When written
192        // without a space, `POINTZ` is considered a single word, which means we need to include
193        // matches here.
194        match word {
195            w if w.eq_ignore_ascii_case("POINT") => {
196                let x = <Point<T> as FromTokens<T>>::from_tokens_with_header(tokens, None);
197                x.map(|y| y.into())
198            }
199            w if w.eq_ignore_ascii_case("POINTZ") => {
200                let x = <Point<T> as FromTokens<T>>::from_tokens_with_header(
201                    tokens,
202                    Some(Dimension::XYZ),
203                );
204                x.map(|y| y.into())
205            }
206            w if w.eq_ignore_ascii_case("POINTM") => {
207                let x = <Point<T> as FromTokens<T>>::from_tokens_with_header(
208                    tokens,
209                    Some(Dimension::XYM),
210                );
211                x.map(|y| y.into())
212            }
213            w if w.eq_ignore_ascii_case("POINTZM") => {
214                let x = <Point<T> as FromTokens<T>>::from_tokens_with_header(
215                    tokens,
216                    Some(Dimension::XYZM),
217                );
218                x.map(|y| y.into())
219            }
220            w if w.eq_ignore_ascii_case("LINESTRING") || w.eq_ignore_ascii_case("LINEARRING") => {
221                let x = <LineString<T> as FromTokens<T>>::from_tokens_with_header(tokens, None);
222                x.map(|y| y.into())
223            }
224            w if w.eq_ignore_ascii_case("LINESTRINGZ") => {
225                let x = <LineString<T> as FromTokens<T>>::from_tokens_with_header(
226                    tokens,
227                    Some(Dimension::XYZ),
228                );
229                x.map(|y| y.into())
230            }
231            w if w.eq_ignore_ascii_case("LINESTRINGM") => {
232                let x = <LineString<T> as FromTokens<T>>::from_tokens_with_header(
233                    tokens,
234                    Some(Dimension::XYM),
235                );
236                x.map(|y| y.into())
237            }
238            w if w.eq_ignore_ascii_case("LINESTRINGZM") => {
239                let x = <LineString<T> as FromTokens<T>>::from_tokens_with_header(
240                    tokens,
241                    Some(Dimension::XYZM),
242                );
243                x.map(|y| y.into())
244            }
245            w if w.eq_ignore_ascii_case("POLYGON") => {
246                let x = <Polygon<T> as FromTokens<T>>::from_tokens_with_header(tokens, None);
247                x.map(|y| y.into())
248            }
249            w if w.eq_ignore_ascii_case("POLYGONZ") => {
250                let x = <Polygon<T> as FromTokens<T>>::from_tokens_with_header(
251                    tokens,
252                    Some(Dimension::XYZ),
253                );
254                x.map(|y| y.into())
255            }
256            w if w.eq_ignore_ascii_case("POLYGONM") => {
257                let x = <Polygon<T> as FromTokens<T>>::from_tokens_with_header(
258                    tokens,
259                    Some(Dimension::XYM),
260                );
261                x.map(|y| y.into())
262            }
263            w if w.eq_ignore_ascii_case("POLYGONZM") => {
264                let x = <Polygon<T> as FromTokens<T>>::from_tokens_with_header(
265                    tokens,
266                    Some(Dimension::XYZM),
267                );
268                x.map(|y| y.into())
269            }
270            w if w.eq_ignore_ascii_case("MULTIPOINT") => {
271                let x = <MultiPoint<T> as FromTokens<T>>::from_tokens_with_header(tokens, None);
272                x.map(|y| y.into())
273            }
274            w if w.eq_ignore_ascii_case("MULTIPOINTZ") => {
275                let x = <MultiPoint<T> as FromTokens<T>>::from_tokens_with_header(
276                    tokens,
277                    Some(Dimension::XYZ),
278                );
279                x.map(|y| y.into())
280            }
281            w if w.eq_ignore_ascii_case("MULTIPOINTM") => {
282                let x = <MultiPoint<T> as FromTokens<T>>::from_tokens_with_header(
283                    tokens,
284                    Some(Dimension::XYM),
285                );
286                x.map(|y| y.into())
287            }
288            w if w.eq_ignore_ascii_case("MULTIPOINTZM") => {
289                let x = <MultiPoint<T> as FromTokens<T>>::from_tokens_with_header(
290                    tokens,
291                    Some(Dimension::XYZM),
292                );
293                x.map(|y| y.into())
294            }
295            w if w.eq_ignore_ascii_case("MULTILINESTRING") => {
296                let x =
297                    <MultiLineString<T> as FromTokens<T>>::from_tokens_with_header(tokens, None);
298                x.map(|y| y.into())
299            }
300            w if w.eq_ignore_ascii_case("MULTILINESTRINGZ") => {
301                let x = <MultiLineString<T> as FromTokens<T>>::from_tokens_with_header(
302                    tokens,
303                    Some(Dimension::XYZ),
304                );
305                x.map(|y| y.into())
306            }
307            w if w.eq_ignore_ascii_case("MULTILINESTRINGM") => {
308                let x = <MultiLineString<T> as FromTokens<T>>::from_tokens_with_header(
309                    tokens,
310                    Some(Dimension::XYM),
311                );
312                x.map(|y| y.into())
313            }
314            w if w.eq_ignore_ascii_case("MULTILINESTRINGZM") => {
315                let x = <MultiLineString<T> as FromTokens<T>>::from_tokens_with_header(
316                    tokens,
317                    Some(Dimension::XYZM),
318                );
319                x.map(|y| y.into())
320            }
321            w if w.eq_ignore_ascii_case("MULTIPOLYGON") => {
322                let x = <MultiPolygon<T> as FromTokens<T>>::from_tokens_with_header(tokens, None);
323                x.map(|y| y.into())
324            }
325            w if w.eq_ignore_ascii_case("MULTIPOLYGONZ") => {
326                let x = <MultiPolygon<T> as FromTokens<T>>::from_tokens_with_header(
327                    tokens,
328                    Some(Dimension::XYZ),
329                );
330                x.map(|y| y.into())
331            }
332            w if w.eq_ignore_ascii_case("MULTIPOLYGONM") => {
333                let x = <MultiPolygon<T> as FromTokens<T>>::from_tokens_with_header(
334                    tokens,
335                    Some(Dimension::XYM),
336                );
337                x.map(|y| y.into())
338            }
339            w if w.eq_ignore_ascii_case("MULTIPOLYGONZM") => {
340                let x = <MultiPolygon<T> as FromTokens<T>>::from_tokens_with_header(
341                    tokens,
342                    Some(Dimension::XYZM),
343                );
344                x.map(|y| y.into())
345            }
346            w if w.eq_ignore_ascii_case("GEOMETRYCOLLECTION") => {
347                let x =
348                    <GeometryCollection<T> as FromTokens<T>>::from_tokens_with_header(tokens, None);
349                x.map(|y| y.into())
350            }
351            w if w.eq_ignore_ascii_case("GEOMETRYCOLLECTIONZ") => {
352                let x = <GeometryCollection<T> as FromTokens<T>>::from_tokens_with_header(
353                    tokens,
354                    Some(Dimension::XYZ),
355                );
356                x.map(|y| y.into())
357            }
358            w if w.eq_ignore_ascii_case("GEOMETRYCOLLECTIONM") => {
359                let x = <GeometryCollection<T> as FromTokens<T>>::from_tokens_with_header(
360                    tokens,
361                    Some(Dimension::XYM),
362                );
363                x.map(|y| y.into())
364            }
365            w if w.eq_ignore_ascii_case("GEOMETRYCOLLECTIONZM") => {
366                let x = <GeometryCollection<T> as FromTokens<T>>::from_tokens_with_header(
367                    tokens,
368                    Some(Dimension::XYZM),
369                );
370                x.map(|y| y.into())
371            }
372            _ => Err("Invalid type encountered"),
373        }
374    }
375}
376
377impl<T> fmt::Display for Wkt<T>
378where
379    T: WktNum + fmt::Display,
380{
381    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
382        Ok(write_geometry(f, self)?)
383    }
384}
385
386impl<T> Wkt<T>
387where
388    T: WktNum + FromStr + Default,
389{
390    fn from_tokens(tokens: Tokens<T>) -> Result<Self, &'static str> {
391        let mut tokens = tokens.peekable();
392        let word = match tokens.next().transpose()? {
393            Some(Token::Word(word)) => {
394                if !word.is_ascii() {
395                    return Err("Encountered non-ascii word");
396                }
397                word
398            }
399            _ => return Err("Invalid WKT format"),
400        };
401        Wkt::from_word_and_tokens(&word, &mut tokens)
402    }
403}
404
405impl<T> FromStr for Wkt<T>
406where
407    T: WktNum + FromStr + Default,
408{
409    type Err = &'static str;
410
411    fn from_str(wkt_str: &str) -> Result<Self, Self::Err> {
412        Wkt::from_tokens(Tokens::from_str(wkt_str))
413    }
414}
415
416impl<T: WktNum> GeometryTrait for Wkt<T> {
417    type T = T;
418    type PointType<'b> = Point<T> where Self: 'b;
419    type LineStringType<'b> = LineString<T> where Self: 'b;
420    type PolygonType<'b> = Polygon<T> where Self: 'b;
421    type MultiPointType<'b> = MultiPoint<T> where Self: 'b;
422    type MultiLineStringType<'b> = MultiLineString<T> where Self: 'b;
423    type MultiPolygonType<'b> = MultiPolygon<T> where Self: 'b;
424    type GeometryCollectionType<'b> = GeometryCollection<T> where Self: 'b;
425    type RectType<'b> = geo_traits::UnimplementedRect<T> where Self: 'b;
426    type LineType<'b> = geo_traits::UnimplementedLine<T> where Self: 'b;
427    type TriangleType<'b> = geo_traits::UnimplementedTriangle<T> where Self: 'b;
428
429    fn dim(&self) -> geo_traits::Dimensions {
430        match self {
431            Wkt::Point(geom) => PointTrait::dim(geom),
432            Wkt::LineString(geom) => LineStringTrait::dim(geom),
433            Wkt::Polygon(geom) => PolygonTrait::dim(geom),
434            Wkt::MultiPoint(geom) => MultiPointTrait::dim(geom),
435            Wkt::MultiLineString(geom) => MultiLineStringTrait::dim(geom),
436            Wkt::MultiPolygon(geom) => MultiPolygonTrait::dim(geom),
437            Wkt::GeometryCollection(geom) => GeometryCollectionTrait::dim(geom),
438        }
439    }
440
441    fn as_type(
442        &self,
443    ) -> geo_traits::GeometryType<
444        '_,
445        Point<T>,
446        LineString<T>,
447        Polygon<T>,
448        MultiPoint<T>,
449        MultiLineString<T>,
450        MultiPolygon<T>,
451        GeometryCollection<T>,
452        Self::RectType<'_>,
453        Self::TriangleType<'_>,
454        Self::LineType<'_>,
455    > {
456        match self {
457            Wkt::Point(geom) => geo_traits::GeometryType::Point(geom),
458            Wkt::LineString(geom) => geo_traits::GeometryType::LineString(geom),
459            Wkt::Polygon(geom) => geo_traits::GeometryType::Polygon(geom),
460            Wkt::MultiPoint(geom) => geo_traits::GeometryType::MultiPoint(geom),
461            Wkt::MultiLineString(geom) => geo_traits::GeometryType::MultiLineString(geom),
462            Wkt::MultiPolygon(geom) => geo_traits::GeometryType::MultiPolygon(geom),
463            Wkt::GeometryCollection(geom) => geo_traits::GeometryType::GeometryCollection(geom),
464        }
465    }
466}
467
468impl<T: WktNum> GeometryTrait for &Wkt<T> {
469    type T = T;
470    type PointType<'b> = Point<T> where Self: 'b;
471    type LineStringType<'b> = LineString<T> where Self: 'b;
472    type PolygonType<'b> = Polygon<T> where Self: 'b;
473    type MultiPointType<'b> = MultiPoint<T> where Self: 'b;
474    type MultiLineStringType<'b> = MultiLineString<T> where Self: 'b;
475    type MultiPolygonType<'b> = MultiPolygon<T> where Self: 'b;
476    type GeometryCollectionType<'b> = GeometryCollection<T> where Self: 'b;
477    type RectType<'b> = geo_traits::UnimplementedRect<T> where Self: 'b;
478    type LineType<'b> = geo_traits::UnimplementedLine<T> where Self: 'b;
479    type TriangleType<'b> = geo_traits::UnimplementedTriangle<T> where Self: 'b;
480
481    fn dim(&self) -> geo_traits::Dimensions {
482        match self {
483            Wkt::Point(geom) => PointTrait::dim(geom),
484            Wkt::LineString(geom) => LineStringTrait::dim(geom),
485            Wkt::Polygon(geom) => PolygonTrait::dim(geom),
486            Wkt::MultiPoint(geom) => MultiPointTrait::dim(geom),
487            Wkt::MultiLineString(geom) => MultiLineStringTrait::dim(geom),
488            Wkt::MultiPolygon(geom) => MultiPolygonTrait::dim(geom),
489            Wkt::GeometryCollection(geom) => GeometryCollectionTrait::dim(geom),
490        }
491    }
492
493    fn as_type(
494        &self,
495    ) -> geo_traits::GeometryType<
496        '_,
497        Point<T>,
498        LineString<T>,
499        Polygon<T>,
500        MultiPoint<T>,
501        MultiLineString<T>,
502        MultiPolygon<T>,
503        GeometryCollection<T>,
504        Self::RectType<'_>,
505        Self::TriangleType<'_>,
506        Self::LineType<'_>,
507    > {
508        match self {
509            Wkt::Point(geom) => geo_traits::GeometryType::Point(geom),
510            Wkt::LineString(geom) => geo_traits::GeometryType::LineString(geom),
511            Wkt::Polygon(geom) => geo_traits::GeometryType::Polygon(geom),
512            Wkt::MultiPoint(geom) => geo_traits::GeometryType::MultiPoint(geom),
513            Wkt::MultiLineString(geom) => geo_traits::GeometryType::MultiLineString(geom),
514            Wkt::MultiPolygon(geom) => geo_traits::GeometryType::MultiPolygon(geom),
515            Wkt::GeometryCollection(geom) => geo_traits::GeometryType::GeometryCollection(geom),
516        }
517    }
518}
519
520// Specialized implementations on each WKT concrete type.
521
522macro_rules! impl_specialization {
523    ($geometry_type:ident) => {
524        impl<T: WktNum> GeometryTrait for $geometry_type<T> {
525            type T = T;
526            type PointType<'b> = Point<Self::T> where Self: 'b;
527            type LineStringType<'b> = LineString<Self::T> where Self: 'b;
528            type PolygonType<'b> = Polygon<Self::T> where Self: 'b;
529            type MultiPointType<'b> = MultiPoint<Self::T> where Self: 'b;
530            type MultiLineStringType<'b> = MultiLineString<Self::T> where Self: 'b;
531            type MultiPolygonType<'b> = MultiPolygon<Self::T> where Self: 'b;
532            type GeometryCollectionType<'b> = GeometryCollection<Self::T> where Self: 'b;
533            type RectType<'b> = geo_traits::UnimplementedRect<T> where Self: 'b;
534            type LineType<'b> = geo_traits::UnimplementedLine<T> where Self: 'b;
535            type TriangleType<'b> = geo_traits::UnimplementedTriangle<T> where Self: 'b;
536
537            fn dim(&self) -> geo_traits::Dimensions {
538                geo_traits::Dimensions::Xy
539            }
540
541            fn as_type(
542                &self,
543            ) -> geo_traits::GeometryType<
544                '_,
545                Point<T>,
546                LineString<T>,
547                Polygon<T>,
548                MultiPoint<T>,
549                MultiLineString<T>,
550                MultiPolygon<T>,
551                GeometryCollection<T>,
552                Self::RectType<'_>,
553                Self::TriangleType<'_>,
554                Self::LineType<'_>,
555            > {
556                geo_traits::GeometryType::$geometry_type(self)
557            }
558        }
559
560        impl<'a, T: WktNum + 'a> GeometryTrait for &'a $geometry_type<T> {
561            type T = T;
562            type PointType<'b> = Point<Self::T> where Self: 'b;
563            type LineStringType<'b> = LineString<Self::T> where Self: 'b;
564            type PolygonType<'b> = Polygon<Self::T> where Self: 'b;
565            type MultiPointType<'b> = MultiPoint<Self::T> where Self: 'b;
566            type MultiLineStringType<'b> = MultiLineString<Self::T> where Self: 'b;
567            type MultiPolygonType<'b> = MultiPolygon<Self::T> where Self: 'b;
568            type GeometryCollectionType<'b> = GeometryCollection<Self::T> where Self: 'b;
569            type RectType<'b> = geo_traits::UnimplementedRect<T> where Self: 'b;
570            type LineType<'b> = geo_traits::UnimplementedLine<T> where Self: 'b;
571            type TriangleType<'b> = geo_traits::UnimplementedTriangle<T> where Self: 'b;
572
573            fn dim(&self) -> geo_traits::Dimensions {
574                geo_traits::Dimensions::Xy
575            }
576
577            fn as_type(
578                &self,
579            ) -> geo_traits::GeometryType<
580                '_,
581                Point<T>,
582                LineString<T>,
583                Polygon<T>,
584                MultiPoint<T>,
585                MultiLineString<T>,
586                MultiPolygon<T>,
587                GeometryCollection<T>,
588                Self::RectType<'_>,
589                Self::TriangleType<'_>,
590                Self::LineType<'_>,
591            > {
592                geo_traits::GeometryType::$geometry_type(self)
593            }
594        }
595    };
596}
597
598impl_specialization!(Point);
599impl_specialization!(LineString);
600impl_specialization!(Polygon);
601impl_specialization!(MultiPoint);
602impl_specialization!(MultiLineString);
603impl_specialization!(MultiPolygon);
604impl_specialization!(GeometryCollection);
605
606fn infer_geom_dimension<T: WktNum + FromStr + Default>(
607    tokens: &mut PeekableTokens<T>,
608) -> Result<Dimension, &'static str> {
609    if let Some(Ok(c)) = tokens.peek() {
610        match c {
611            // If we match a word check if it's Z/M/ZM and consume the token from the stream
612            Token::Word(w) => match w.as_str() {
613                w if w.eq_ignore_ascii_case("Z") => {
614                    tokens.next().unwrap().unwrap();
615                    Ok(Dimension::XYZ)
616                }
617                w if w.eq_ignore_ascii_case("M") => {
618                    tokens.next().unwrap().unwrap();
619
620                    Ok(Dimension::XYM)
621                }
622                w if w.eq_ignore_ascii_case("ZM") => {
623                    tokens.next().unwrap().unwrap();
624                    Ok(Dimension::XYZM)
625                }
626                w if w.eq_ignore_ascii_case("EMPTY") => Ok(Dimension::XY),
627                _ => Err("Unexpected word before open paren"),
628            },
629            // Not a word, e.g. an open paren
630            _ => Ok(Dimension::XY),
631        }
632    } else {
633        Err("End of stream")
634    }
635}
636
637trait FromTokens<T>: Sized + Default
638where
639    T: WktNum + FromStr + Default,
640{
641    fn from_tokens(tokens: &mut PeekableTokens<T>, dim: Dimension) -> Result<Self, &'static str>;
642
643    /// The preferred top-level FromTokens API, which additionally checks for the presence of Z, M,
644    /// and ZM in the token stream.
645    fn from_tokens_with_header(
646        tokens: &mut PeekableTokens<T>,
647        dim: Option<Dimension>,
648    ) -> Result<Self, &'static str> {
649        let dim = if let Some(dim) = dim {
650            dim
651        } else {
652            infer_geom_dimension(tokens)?
653        };
654        FromTokens::from_tokens_with_parens(tokens, dim)
655    }
656
657    fn from_tokens_with_parens(
658        tokens: &mut PeekableTokens<T>,
659        dim: Dimension,
660    ) -> Result<Self, &'static str> {
661        match tokens.next().transpose()? {
662            Some(Token::ParenOpen) => (),
663            Some(Token::Word(ref s)) if s.eq_ignore_ascii_case("EMPTY") => {
664                // TODO: expand this to support Z EMPTY
665                // Maybe create a DefaultXY, DefaultXYZ trait etc for each geometry type, and then
666                // here match on the dim to decide which default trait to use.
667                return Ok(Default::default());
668            }
669            _ => return Err("Missing open parenthesis for type"),
670        };
671        let result = FromTokens::from_tokens(tokens, dim);
672        match tokens.next().transpose()? {
673            Some(Token::ParenClose) => (),
674            _ => return Err("Missing closing parenthesis for type"),
675        };
676        result
677    }
678
679    fn from_tokens_with_optional_parens(
680        tokens: &mut PeekableTokens<T>,
681        dim: Dimension,
682    ) -> Result<Self, &'static str> {
683        match tokens.peek() {
684            Some(Ok(Token::ParenOpen)) => Self::from_tokens_with_parens(tokens, dim),
685            _ => Self::from_tokens(tokens, dim),
686        }
687    }
688
689    fn comma_many<F>(
690        f: F,
691        tokens: &mut PeekableTokens<T>,
692        dim: Dimension,
693    ) -> Result<Vec<Self>, &'static str>
694    where
695        F: Fn(&mut PeekableTokens<T>, Dimension) -> Result<Self, &'static str>,
696    {
697        let mut items = Vec::new();
698
699        let item = f(tokens, dim)?;
700        items.push(item);
701
702        while let Some(&Ok(Token::Comma)) = tokens.peek() {
703            tokens.next(); // throw away comma
704
705            let item = f(tokens, dim)?;
706            items.push(item);
707        }
708
709        Ok(items)
710    }
711}
712
713#[cfg(test)]
714mod tests {
715    use crate::types::{Coord, MultiPolygon, Point};
716    use crate::Wkt;
717    use std::str::FromStr;
718
719    #[test]
720    fn empty_string() {
721        let res: Result<Wkt<f64>, _> = Wkt::from_str("");
722        assert!(res.is_err());
723    }
724
725    #[test]
726    fn empty_items() {
727        let wkt: Wkt<f64> = Wkt::from_str("POINT EMPTY").ok().unwrap();
728        match wkt {
729            Wkt::Point(Point(None)) => (),
730            _ => unreachable!(),
731        };
732
733        let wkt: Wkt<f64> = Wkt::from_str("MULTIPOLYGON EMPTY").ok().unwrap();
734        match wkt {
735            Wkt::MultiPolygon(MultiPolygon(polygons)) => assert_eq!(polygons.len(), 0),
736            _ => unreachable!(),
737        };
738    }
739
740    #[test]
741    fn lowercase_point() {
742        let wkt: Wkt<f64> = Wkt::from_str("point EMPTY").ok().unwrap();
743        match wkt {
744            Wkt::Point(Point(None)) => (),
745            _ => unreachable!(),
746        };
747    }
748
749    #[test]
750    fn invalid_number() {
751        let msg = <Wkt<f64>>::from_str("POINT (10 20.1A)").unwrap_err();
752        assert_eq!(
753            "Unable to parse input number as the desired output type",
754            msg
755        );
756    }
757
758    #[test]
759    fn test_points() {
760        // point(x, y)
761        let wkt = <Wkt<f64>>::from_str("POINT (10 20.1)").ok().unwrap();
762        match wkt {
763            Wkt::Point(Point(Some(coord))) => {
764                assert_eq!(coord.x, 10.0);
765                assert_eq!(coord.y, 20.1);
766                assert_eq!(coord.z, None);
767                assert_eq!(coord.m, None);
768            }
769            _ => panic!("excepted to be parsed as a POINT"),
770        }
771
772        // point(x, y, z)
773        let wkt = <Wkt<f64>>::from_str("POINT Z (10 20.1 5)").ok().unwrap();
774        match wkt {
775            Wkt::Point(Point(Some(coord))) => {
776                assert_eq!(coord.x, 10.0);
777                assert_eq!(coord.y, 20.1);
778                assert_eq!(coord.z, Some(5.0));
779                assert_eq!(coord.m, None);
780            }
781            _ => panic!("excepted to be parsed as a POINT"),
782        }
783
784        // point(x, y, m)
785        let wkt = <Wkt<f64>>::from_str("POINT M (10 20.1 80)").ok().unwrap();
786        match wkt {
787            Wkt::Point(Point(Some(coord))) => {
788                assert_eq!(coord.x, 10.0);
789                assert_eq!(coord.y, 20.1);
790                assert_eq!(coord.z, None);
791                assert_eq!(coord.m, Some(80.0));
792            }
793            _ => panic!("excepted to be parsed as a POINT"),
794        }
795
796        // point(x, y, z, m)
797        let wkt = <Wkt<f64>>::from_str("POINT ZM (10 20.1 5 80)")
798            .ok()
799            .unwrap();
800        match wkt {
801            Wkt::Point(Point(Some(coord))) => {
802                assert_eq!(coord.x, 10.0);
803                assert_eq!(coord.y, 20.1);
804                assert_eq!(coord.z, Some(5.0));
805                assert_eq!(coord.m, Some(80.0));
806            }
807            _ => panic!("excepted to be parsed as a POINT"),
808        }
809    }
810
811    #[test]
812    fn support_jts_linearring() {
813        let wkt: Wkt<f64> = Wkt::from_str("linearring (10 20, 30 40)").ok().unwrap();
814        match wkt {
815            Wkt::LineString(_ls) => (),
816            _ => panic!("expected to be parsed as a LINESTRING"),
817        };
818    }
819
820    #[test]
821    fn test_debug() {
822        let g = Wkt::Point(Point(Some(Coord {
823            x: 1.0,
824            y: 2.0,
825            m: None,
826            z: None,
827        })));
828        assert_eq!(
829            format!("{:?}", g),
830            "Point(Point(Some(Coord { x: 1.0, y: 2.0, z: None, m: None })))"
831        );
832    }
833
834    #[test]
835    fn test_display_on_wkt() {
836        let wktls: Wkt<f64> = Wkt::from_str("LINESTRING(10 20, 20 30)").unwrap();
837
838        assert_eq!(wktls.to_string(), "LINESTRING(10 20,20 30)");
839    }
840}