geojson/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//! # Introduction
17//!
18//! This crate helps you read and write [GeoJSON](https://geojson.org) — a format for encoding
19//! geographic data structures.
20//!
21//! To get started, add `geojson` to your `Cargo.toml`.
22//!
23//! ```sh
24//! cargo add geojson
25//! ```
26//!
27//! # Types and crate structure
28//!
29//! This crate is structured around the GeoJSON spec ([IETF RFC 7946](https://tools.ietf.org/html/rfc7946)),
30//! and users are encouraged to familiarise themselves with it. The elements specified in this spec
31//! have corresponding struct and type definitions in this crate, e.g. [`FeatureCollection`], [`Feature`],
32//! etc.
33//!
34//! There are two primary ways to use this crate.
35//!
36//! The first, most general, approach is to write your code to deal in terms of these structs from
37//! the GeoJSON spec. This allows you to access the full expressive power of GeoJSON with the speed
38//! and safety of Rust.
39//!
40//! Alternatively, and commonly, if you only need geometry and properties (and not, e.g.
41//! [foreign members](https://www.rfc-editor.org/rfc/rfc7946#section-6.1)), you can bring your own
42//! types, and use this crate's [`serde`] integration to serialize and deserialize your custom
43//! types directly to and from a GeoJSON Feature Collection. [See more on using your own types with
44//! serde](#using-your-own-types-with-serde).
45//!
46//! If you want to use GeoJSON as input to or output from a geometry processing crate like
47//! [`geo`](https://docs.rs/geo), see the section on [using geojson with
48//! geo-types](#use-geojson-with-other-crates-by-converting-to-geo-types).
49//!
50//! ## Using structs from the GeoJSON spec
51//!
52//! A GeoJSON object can be one of three top-level objects, reflected in this crate as the
53//! [`GeoJson`] enum members of the same name.
54//!
55//! 1. A [`Geometry`] represents points, curves, and surfaces in coordinate space.
56//! 2. A [`Feature`] usually contains a `Geometry` and some associated data, for example a "name"
57//! field or any other properties you'd like associated with the `Geometry`.
58//! 3. A [`FeatureCollection`] is a list of `Feature`s.
59//!
60//! Because [`Feature`] and [`FeatureCollection`] are more flexible, bare [`Geometry`] GeoJSON
61//! documents are rarely encountered in the wild. As such, conversions from [`Geometry`]
62//! or [Geometry `Value`](Value) to [`Feature`] objects are provided via the [`From`] trait.
63//!
64//! *Beware:* A common point of confusion arises when converting a [GeoJson
65//! `GeometryCollection`](Value::GeometryCollection). Do you want it converted to a single
66//! [`Feature`] whose geometry is a [`GeometryCollection`](Value::GeometryCollection), or do you
67//! want a [`FeatureCollection`] with each *element* of the
68//! [`GeometryCollection`](Value::GeometryCollection) converted to its own [`Feature`], potentially
69//! with their own individual properties. Either is possible, but it's important you understand
70//! which one you want.
71//!
72//! # Examples
73//! ## Reading
74//!
75//! [`GeoJson`] can be deserialized by calling [`str::parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse):
76//!
77//! ```
78//! use geojson::{Feature, GeoJson, Geometry, Value};
79//! use std::convert::TryFrom;
80//!
81//! let geojson_str = r#"
82//! {
83//! "type": "Feature",
84//! "properties": { "food": "donuts" },
85//! "geometry": {
86//! "type": "Point",
87//! "coordinates": [ -118.2836, 34.0956 ]
88//! }
89//! }
90//! "#;
91//!
92//! let geojson: GeoJson = geojson_str.parse::<GeoJson>().unwrap();
93//! let feature: Feature = Feature::try_from(geojson).unwrap();
94//!
95//! // read property data
96//! assert_eq!("donuts", feature.property("food").unwrap());
97//!
98//! // read geometry data
99//! let geometry: Geometry = feature.geometry.unwrap();
100//! if let Value::Point(coords) = geometry.value {
101//! assert_eq!(coords, vec![-118.2836, 34.0956]);
102//! }
103//!
104//! # else {
105//! # unreachable!("should be point");
106//! # }
107//! ```
108//!
109//! ## Writing
110//!
111//! `GeoJson` can be serialized by calling [`to_string`](geojson/enum.GeoJson.html#impl-ToString):
112//!
113//! ```rust
114//! use geojson::{Feature, GeoJson, Geometry, Value};
115//! # fn get_properties() -> ::geojson::JsonObject {
116//! # let mut properties = ::geojson::JsonObject::new();
117//! # properties.insert(
118//! # String::from("name"),
119//! # ::geojson::JsonValue::from("Firestone Grill"),
120//! # );
121//! # properties
122//! # }
123//! # fn main() {
124//!
125//! let geometry = Geometry::new(Value::Point(vec![-120.66029, 35.2812]));
126//!
127//! let geojson = GeoJson::Feature(Feature {
128//! bbox: None,
129//! geometry: Some(geometry),
130//! id: None,
131//! // See the next section about Feature properties
132//! properties: Some(get_properties()),
133//! foreign_members: None,
134//! });
135//!
136//! let geojson_string = geojson.to_string();
137//! # }
138//! ```
139//!
140//! ### Feature properties
141//!
142//! The `geojson` crate is built on top of [`serde_json`](../serde_json/index.html). Consequently,
143//! some fields like [`feature.properties`](Feature#structfield.properties) hold [serde_json
144//! values](../serde_json/value/index.html).
145//!
146//! ```
147//! use geojson::{JsonObject, JsonValue};
148//!
149//! let mut properties = JsonObject::new();
150//! let key = "name".to_string();
151//! properties.insert(key, JsonValue::from("Firestone Grill"));
152//! ```
153//!
154//! ## Parsing
155//!
156//! GeoJSON's [spec](https://tools.ietf.org/html/rfc7946) is quite simple, but
157//! it has several subtleties that must be taken into account when parsing it:
158//!
159//! - The `geometry` field of a [`Feature`] is an [`Option`] — it can be blank.
160//! - [`GeometryCollection`](Value::GeometryCollection)s contain other [`Geometry`] objects, and can nest.
161//! - We strive to produce strictly valid output, but we are more permissive about what we accept
162//! as input.
163//!
164//! Here's a minimal example which will parse and process a GeoJSON string.
165//!
166//! ```rust
167//! use geojson::{GeoJson, Geometry, Value};
168//!
169//! /// Process top-level GeoJSON Object
170//! fn process_geojson(gj: &GeoJson) {
171//! match *gj {
172//! GeoJson::FeatureCollection(ref ctn) => {
173//! for feature in &ctn.features {
174//! if let Some(ref geom) = feature.geometry {
175//! process_geometry(geom)
176//! }
177//! }
178//! }
179//! GeoJson::Feature(ref feature) => {
180//! if let Some(ref geom) = feature.geometry {
181//! process_geometry(geom)
182//! }
183//! }
184//! GeoJson::Geometry(ref geometry) => process_geometry(geometry),
185//! }
186//! }
187//!
188//! /// Process GeoJSON geometries
189//! fn process_geometry(geom: &Geometry) {
190//! match geom.value {
191//! Value::Polygon(_) => println!("Matched a Polygon"),
192//! Value::MultiPolygon(_) => println!("Matched a MultiPolygon"),
193//! Value::GeometryCollection(ref gc) => {
194//! println!("Matched a GeometryCollection");
195//! // !!! GeometryCollections contain other Geometry types, and can
196//! // nest — we deal with this by recursively processing each geometry
197//! for geometry in gc {
198//! process_geometry(geometry)
199//! }
200//! }
201//! // Point, LineString, and their Multi– counterparts
202//! _ => println!("Matched some other geometry"),
203//! }
204//! }
205//!
206//! fn main() {
207//! let geojson_str = r#"
208//! {
209//! "type": "GeometryCollection",
210//! "geometries": [
211//! {"type": "Point", "coordinates": [0,1]},
212//! {"type": "MultiPoint", "coordinates": [[-1,0],[1,0]]},
213//! {"type": "LineString", "coordinates": [[-1,-1],[1,-1]]},
214//! {"type": "MultiLineString", "coordinates": [
215//! [[-2,-2],[2,-2]],
216//! [[-3,-3],[3,-3]]
217//! ]},
218//! {"type": "Polygon", "coordinates": [
219//! [[-5,-5],[5,-5],[0,5],[-5,-5]],
220//! [[-4,-4],[4,-4],[0,4],[-4,-4]]
221//! ]},
222//! { "type": "MultiPolygon", "coordinates": [[
223//! [[-7,-7],[7,-7],[0,7],[-7,-7]],
224//! [[-6,-6],[6,-6],[0,6],[-6,-6]]
225//! ],[
226//! [[-9,-9],[9,-9],[0,9],[-9,-9]],
227//! [[-8,-8],[8,-8],[0,8],[-8,-8]]]
228//! ]},
229//! {"type": "GeometryCollection", "geometries": [
230//! {"type": "Polygon", "coordinates": [
231//! [[-5.5,-5.5],[5,-5],[0,5],[-5,-5]],
232//! [[-4,-4],[4,-4],[0,4],[-4.5,-4.5]]
233//! ]}
234//! ]}
235//! ]
236//! }
237//! "#;
238//! let geojson = geojson_str.parse::<GeoJson>().unwrap();
239//! process_geojson(&geojson);
240//! }
241//! ```
242//!
243//! ## Use geojson with other crates by converting to geo-types
244//!
245//! [`geo-types`](../geo_types/index.html#structs) are a common geometry format used across many
246//! geospatial processing crates. The `geo-types` feature is enabled by default.
247//!
248//! ### Convert `geo-types` to `geojson`
249//!
250//! [`From`] is implemented on the [`Value`] enum variants to allow conversion _from_ [`geo-types`
251//! Geometries](../geo_types/index.html#structs).
252//!
253//! ```
254//! # #[cfg(feature = "geo-types")]
255//! # {
256//! // requires enabling the `geo-types` feature
257//! let geo_point: geo_types::Point<f64> = geo_types::Point::new(2., 9.);
258//! let geo_geometry: geo_types::Geometry<f64> = geo_types::Geometry::from(geo_point);
259//!
260//! assert_eq!(
261//! geojson::Value::from(&geo_point),
262//! geojson::Value::Point(vec![2., 9.]),
263//! );
264//! assert_eq!(
265//! geojson::Value::from(&geo_geometry),
266//! geojson::Value::Point(vec![2., 9.]),
267//! );
268//! # }
269//! ```
270//!
271//! If you wish to produce a [`FeatureCollection`] from a homogeneous collection of `geo-types`, a
272//! `From` impl is provided for `geo_types::GeometryCollection`:
273//!
274//! ```rust
275//! # #[cfg(feature = "geo-types")]
276//! # {
277//! // requires enabling the `geo-types` feature
278//! use geojson::FeatureCollection;
279//! use geo_types::{polygon, point, Geometry, GeometryCollection};
280//! use std::iter::FromIterator;
281//!
282//! let poly: Geometry<f64> = polygon![
283//! (x: -111., y: 45.),
284//! (x: -111., y: 41.),
285//! (x: -104., y: 41.),
286//! (x: -104., y: 45.),
287//! ].into();
288//!
289//! let point: Geometry<f64> = point!(x: 1.0, y: 2.0).into();
290//!
291//! let geometry_collection = GeometryCollection::from_iter(vec![poly, point]);
292//! let feature_collection = FeatureCollection::from(&geometry_collection);
293//!
294//! assert_eq!(2, feature_collection.features.len());
295//! # }
296//! ```
297//!
298//! ### Convert `geojson` to `geo-types`
299//!
300//! The `geo-types` feature implements the [`TryFrom`](../std/convert/trait.TryFrom.html) trait,
301//! providing **fallible** conversions _to_ [geo-types Geometries](../geo_types/index.html#structs)
302//! from [`GeoJson`], [`Value`], [`Feature`], [`FeatureCollection`] or [`Geometry`] types.
303//!
304//! #### Convert `geojson` to `geo_types::Geometry<f64>`
305//!
306//! ```
307//! # #[cfg(feature = "geo-types")]
308//! # {
309//! // This example requires the `geo-types` feature
310//! use geo_types::Geometry;
311//! use geojson::GeoJson;
312//! use std::convert::TryFrom;
313//! use std::str::FromStr;
314//!
315//! let geojson_str = r#"
316//! {
317//! "type": "Feature",
318//! "properties": {},
319//! "geometry": {
320//! "type": "Point",
321//! "coordinates": [
322//! -0.13583511114120483,
323//! 51.5218870403801
324//! ]
325//! }
326//! }
327//! "#;
328//! let geojson = GeoJson::from_str(geojson_str).unwrap();
329//! // Turn the GeoJSON string into a geo_types Geometry
330//! let geom = geo_types::Geometry::<f64>::try_from(geojson).unwrap();
331//! # }
332//! ```
333//!
334//! ### Caveats
335//! - Round-tripping with intermediate processing using the `geo` types may not produce identical output,
336//! as e.g. outer `Polygon` rings are automatically closed.
337//! - `geojson` attempts to output valid geometries. In particular, it may re-orient `Polygon` rings when serialising.
338//!
339//! The [`geojson_example`](https://github.com/urschrei/geojson_example) and
340//! [`polylabel_cmd`](https://github.com/urschrei/polylabel_cmd/blob/master/src/main.rs) crates contain example
341//! implementations which may be useful if you wish to perform this kind of processing yourself and require
342//! more granular control over performance and / or memory allocation.
343//!
344//! ## Using your own types with serde
345//!
346//! If your use case is simple enough, you can read and write GeoJSON directly to and from your own
347//! types using serde.
348//!
349//! Specifically, the requirements are:
350//! 1. Your type has a `geometry` field.
351//! 1. If your `geometry` field is a [`geo-types` Geometry](geo_types::geometry), you must use
352//! the provided `serialize_with`/`deserialize_with` helpers.
353//! 2. Otherwise, your `geometry` field must be a [`crate::Geometry`].
354//! 2. Other than `geometry`, you may only use a Feature's `properties` - all other fields, like
355//! foreign members, will be lost.
356//!
357//! ```ignore
358//! #[derive(serde::Serialize, serde::Deserialize)]
359//! struct MyStruct {
360//! // Serialize as geojson, rather than using the type's default serialization
361//! #[serde(serialize_with = "serialize_geometry", deserialize_with = "deserialize_geometry")]
362//! geometry: geo_types::Point<f64>,
363//! name: String,
364//! count: u64,
365//! }
366//! ```
367//!
368//! See more in the [serialization](ser) and [deserialization](de) modules.
369// only enables the `doc_cfg` feature when
370// the `docsrs` configuration attribute is defined
371#![cfg_attr(docsrs, feature(doc_cfg))]
372
373/// Bounding Boxes
374///
375/// [GeoJSON Format Specification § 5](https://tools.ietf.org/html/rfc7946#section-5)
376pub type Bbox = Vec<f64>;
377
378/// Positions
379///
380/// [GeoJSON Format Specification § 3.1.1](https://tools.ietf.org/html/rfc7946#section-3.1.1)
381pub type Position = Vec<f64>;
382
383pub type PointType = Position;
384pub type LineStringType = Vec<Position>;
385pub type PolygonType = Vec<Vec<Position>>;
386
387mod util;
388
389mod geojson;
390pub use crate::geojson::GeoJson;
391
392mod geometry;
393pub use crate::geometry::{Geometry, Value};
394
395pub mod feature;
396
397mod feature_collection;
398pub use crate::feature_collection::FeatureCollection;
399
400mod feature_iterator;
401#[allow(deprecated)]
402#[doc(hidden)]
403pub use crate::feature_iterator::FeatureIterator;
404
405pub mod errors;
406pub use crate::errors::{Error, Result};
407
408#[cfg(feature = "geo-types")]
409mod conversion;
410
411/// Build your struct from GeoJSON using [`serde`]
412pub mod de;
413
414/// Write your struct to GeoJSON using [`serde`]
415pub mod ser;
416
417mod feature_reader;
418pub use feature_reader::FeatureReader;
419
420mod feature_writer;
421pub use feature_writer::FeatureWriter;
422
423#[allow(deprecated)]
424#[cfg(feature = "geo-types")]
425pub use conversion::quick_collection;
426
427/// Feature Objects
428///
429/// [GeoJSON Format Specification § 3.2](https://tools.ietf.org/html/rfc7946#section-3.2)
430#[derive(Clone, Debug, Default, PartialEq)]
431pub struct Feature {
432 /// Bounding Box
433 ///
434 /// [GeoJSON Format Specification § 5](https://tools.ietf.org/html/rfc7946#section-5)
435 pub bbox: Option<Bbox>,
436 /// Geometry
437 ///
438 /// [GeoJSON Format Specification § 3.2](https://tools.ietf.org/html/rfc7946#section-3.2)
439 pub geometry: Option<Geometry>,
440 /// Identifier
441 ///
442 /// [GeoJSON Format Specification § 3.2](https://tools.ietf.org/html/rfc7946#section-3.2)
443 pub id: Option<feature::Id>,
444 /// Properties
445 ///
446 /// [GeoJSON Format Specification § 3.2](https://tools.ietf.org/html/rfc7946#section-3.2)
447 ///
448 /// NOTE: This crate will permissively parse a Feature whose json is missing a `properties` key.
449 /// Because the spec implies that the `properties` key must be present, we will always include
450 /// the `properties` key when serializing.
451 pub properties: Option<JsonObject>,
452 /// Foreign Members
453 ///
454 /// [GeoJSON Format Specification § 6](https://tools.ietf.org/html/rfc7946#section-6)
455 pub foreign_members: Option<JsonObject>,
456}
457
458pub type JsonValue = serde_json::Value;
459pub type JsonObject = serde_json::Map<String, JsonValue>;