├── .github ├── pull_request_template.md └── workflows │ └── rust.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGES.md ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches ├── parse.rs ├── serialize.rs └── to_geo_types.rs ├── examples ├── deserialize.rs ├── deserialize_to_geo_types.rs ├── deserialize_to_geojson_types.rs ├── geojson_to_string.rs └── stream_reader_writer.rs ├── src ├── conversion │ ├── from_geo_types.rs │ ├── mod.rs │ └── to_geo_types.rs ├── de.rs ├── errors.rs ├── feature.rs ├── feature_collection.rs ├── feature_iterator.rs ├── feature_reader.rs ├── feature_writer.rs ├── geojson.rs ├── geometry.rs ├── lib.rs ├── ser.rs └── util.rs └── tests ├── fixtures ├── README.md ├── canonical │ ├── good-feature-with-id.geojson │ ├── good-feature-with-string-id.geojson │ ├── good-feature.geojson │ ├── good-featurecollection-bbox.geojson │ ├── good-featurecollection-bbox3d.geojson │ ├── good-featurecollection-extensions.geojson │ ├── good-featurecollection.geojson │ ├── good-geometrycollection.geojson │ ├── good-linestring.geojson │ ├── good-multilinestring.geojson │ ├── good-multipoint.geojson │ ├── good-point-3d.geojson │ ├── good-point.geojson │ ├── good-polygon.geojson │ ├── multipolygon.geojson │ └── nullgeometry.geojson ├── countries.geojson └── geometry_collection.geojson └── roundtrip.rs /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | - [ ] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/master/CODE_OF_CONDUCT.md). 2 | - [ ] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users. 3 | --- 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Run Geojson tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - staging 8 | - trying 9 | - release/** 10 | pull_request: 11 | merge_group: 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref_name }} 15 | cancel-in-progress: true 16 | 17 | env: 18 | CARGO_TERM_COLOR: always 19 | 20 | jobs: 21 | build_and_test: 22 | name: Build and test all Geojson features 23 | runs-on: ubuntu-latest 24 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 25 | steps: 26 | - uses: actions/checkout@v4 27 | - run: cargo install cargo-all-features 28 | - run: cargo build-all-features --verbose 29 | - run: cargo test-all-features --verbose 30 | check: 31 | name: Geojson Rustfmt and Clippy check 32 | runs-on: ubuntu-latest 33 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v4 37 | - name: Install stable toolchain 38 | uses: dtolnay/rust-toolchain@stable 39 | with: 40 | components: clippy, rustfmt 41 | - name: Check formatting using Rustfmt 42 | run: cargo fmt --check 43 | - name: Lint using Clippy 44 | run: cargo clippy --tests 45 | all_checks_complete: 46 | needs: 47 | - build_and_test 48 | - check 49 | if: always() 50 | runs-on: ubuntu-latest 51 | steps: 52 | - name: Result 53 | run: | 54 | jq -C <<< "${needs}" 55 | # Check if all needs were successful or skipped. 56 | "$(jq -r 'all(.result as $result | (["success", "skipped"] | contains([$result])))' <<< "${needs}")" 57 | env: 58 | needs: ${{ toJson(needs) }} 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc/ 2 | target/ 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_code_in_doc_comments = true 2 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## Unreleased 4 | 5 | 6 | * Potentially breaking: De/Serializing your custom structs with serde now maps your struct's `id` field to `Feature.id`, rather than to `Feature.properties.id`. 7 | * Fix `geo_rect_conversion_test` to conform to the correctly-wound `Polygon` output from `geo_types::geometry::Rect.to_polygon` 8 | * See https://github.com/georust/geojson/issues/257 9 | 10 | 11 | ## 0.24.2 - 2025-02-24 12 | 13 | * Add `to_feature` to convert a single S: Serialize to a Feature 14 | * Add `from_feature` to convert a single Feature to a D: Deserialize 15 | * Upgrade from thiserror v1 to v2 16 | * Add support of serializing optional `geo-types` with `serialize_optional_geometry`. 17 | * Add support of deserializing optional `geo-types` with `deserialize_optional_geometry`. 18 | * Add support for foreign members to `FeatureWriter`. 19 | * Added conversion from `Vec` to `GeoJson`. 20 | * Changed `Serialize` impls to avoid creating intermediate `JsonObject`s. 21 | * Better CI: lint, all features 22 | * Implement `Default` on `FeatureCollection`. 23 | * Added `GeometryCollection::try_from(&GeoJson)` and deprecated 24 | `quick_collection` for conventional naming and simpler docs. 25 | * 26 | * Added `GeoJson::to_string_pretty` as convenience wrappers around the same `serde_json` methods. 27 | * The `bbox` property of a `Feature` can now be `null` (returning `None`) when parsing GeoJSON strings in order to facilitate easier processing of GeoJSON "in the wild" (e.g. Copernicus STAC GeoJSON). [The spec](https://datatracker.ietf.org/doc/html/rfc7946#section-5) does not allow this, but implementations appear to disagree. 28 | * 29 | 30 | ## 0.24.1 31 | 32 | * Modified conversion from JSON to reject zero- and one-dimensional positions. 33 | * PR: 34 | 35 | ## 0.24.0 36 | 37 | * Added `geojson::{ser, de}` helpers to convert your custom struct to and from GeoJSON. 38 | * For external geometry types like geo-types, use the `serialize_geometry`/`deserialize_geometry` helpers. 39 | * Example: 40 | ``` 41 | #[derive(Serialize, Deserialize)] 42 | struct MyStruct { 43 | #[serde(serialize_with = "serialize_geometry", deserialize_with = "deserialize_geometry")] 44 | geometry: geo_types::Point, 45 | name: String, 46 | age: u64, 47 | } 48 | 49 | // read your input 50 | let my_structs: Vec = geojson::de::deserialize_feature_collection(geojson_reader).unwrap(); 51 | 52 | // do some processing 53 | process(&mut my_structs); 54 | 55 | // write back your results 56 | geojson::ser::to_feature_collection_string(&my_structs).unwrap(); 57 | ``` 58 | * PR: 59 | * Added `geojson::{FeatureReader, FeatureWriter}` to stream the reading/writing of your custom struct to and from GeoJSON, greatly reducing the memory required to process a FeatureCollection. 60 | * PR: 61 | * PR: 62 | * PR: 63 | * Added IntoIter implementation for FeatureCollection. 64 | * 65 | * Added `geojson::Result`. 66 | * 67 | * Added `TryFrom<&geometry::Value>` for geo_type variants. 68 | * 69 | * Changed the Display string of the error produced when converting a geometry to an incompatible type - e.g. a LineString into a Point. 70 | * 71 | * Fix: FeatureIterator errors when reading "features" field before "type" field. 72 | * 73 | * BREAKING: Change the Result type of FeatureIterator from io::Result to crate::Result 74 | * 75 | 76 | ## 0.23.0 77 | 78 | * Enable optional geo-types integration by default. 79 | * 80 | * FIX: converting a single GeometryCollection Feature to geo_types 81 | * 82 | 83 | ## 0.22.4 84 | 85 | * Allow parsing `Feature`/`FeatureCollection` that are missing a `"properties"` key. 86 | * 87 | * Overhauled front page documentation. 88 | * 89 | * Parse `Geometry`/`Feature`/`FeatureCollection` directly from str rather than 90 | via `GeoJson` when you know what you're expecting. 91 | * 92 | * `Feature` now derives `Default` 93 | * 94 | * Reexport `JsonObject` and `JsonValue` from `serde_json`. 95 | * 96 | 97 | ## 0.22.3 98 | 99 | * Added `FromIterator` impl for `FeatureCollection` 100 | * 101 | * Added `'FeatureIterator` streaming feature collection deserializer 102 | * 103 | 104 | ## 0.22.2 105 | 106 | * Added convenience methods to convert from geo_types::Geometry directly to GeoJson 107 | * 108 | 109 | ## 0.22.1 110 | 111 | * Added convenience methods to convert from Geometry and Value to Feature 112 | * 113 | 114 | ## 0.22.0 115 | 116 | * Update `geo-types` to 0.7.0 117 | 118 | ## 0.21.0 119 | 120 | * `Display` implementation of `geojson::Value` prints` the GeoJSON string 121 | * 122 | 123 | ## 0.20.0 124 | * Switch to thiserror 125 | * Add more granular errors 126 | * `GeoJsonUnknownType` has been split into `NotAFeature` and `EmptyType` 127 | * Add additional Value context to errors where possible 128 | * Add conversions from Geo-Types Line, Triangle, Rect and GeometryCollection 129 | 130 | ## 0.19.0 131 | 132 | * Update `geo-types` to 0.6.0 133 | * Remove unnecessary allocations when parsing `GeometryCollection` 134 | * 135 | 136 | ## 0.18.0 137 | * Update `geo-types` to 0.5.0 138 | * Update docs 139 | * Add quick_collection function 140 | * 141 | * Add TryFrom impls for JsonObject and JsonValue 142 | * 143 | * Add from_json_value! macro 144 | * 145 | 146 | ## 0.17.0 147 | 148 | * Add `TryFrom` impls for `JsonObject` and `JsonValue` 149 | * 150 | * Add `from_json_value` for `GeoJson` enum 151 | * 152 | 153 | ## 0.16.0 154 | 155 | * Switch to Rust 2018 Edition 156 | * 157 | * Switch to `std::TryFrom` trait from custom in-crate `TryFrom` trait 158 | * 159 | * Implement `Display` for `Feature`, `Geometry`, and `FeatureCollection` 160 | * 161 | * 162 | * Make the `geo-types` conversion functionality opt-in 163 | * 164 | 165 | ## 0.15.0 166 | 167 | * Bump geo-types to 0.4.0. 168 | * 169 | 170 | ## 0.14.0 171 | 172 | * Bump geo-types to 0.3.0. 173 | * 174 | 175 | ## 0.13.0 176 | 177 | * Feature::id should either be a string or number; introduce `feature::Id` 178 | * 179 | * Fix broken GeoJSON links in docs 180 | * 181 | * Improve error message for mismatched type 182 | * 183 | * Performance improvements 184 | 185 | ## 0.12.0 186 | 187 | * Bump geo-types to 0.2.0. 188 | * 189 | 190 | ## 0.11.1 191 | 192 | * Don't inject empty interior rings when converting to geo Polygons 193 | * 194 | 195 | ## 0.11.0 196 | 197 | * Switch 'geo' dependency to 'geo-types' 198 | * 199 | 200 | ## 0.10.0 201 | 202 | * Deserialize Optimizations 203 | * 204 | * Expand docs with parsing examples and corner cases, and enable conversion docs 205 | * 206 | * Update GeoJSON spec links to point to published standard 207 | * 208 | * Bump geo and num-traits crates. 209 | * 210 | * Bump geo dependency: 0.7 -> 0.8. 211 | * 212 | 213 | ## 0.9.0 214 | 215 | * Don't publicize `assert_almost_eq` macro 216 | * Bump geo: 0.4 → 0.6 217 | * Use docs.rs for documentation links 218 | 219 | ## 0.8.0 220 | 221 | * [Remove `geojson::Crs`](https://github.com/georust/geojson/pull/71) 222 | * [Support `foreign_members`](https://github.com/georust/geojson/pull/70) 223 | 224 | ## 0.7.1 225 | 226 | * [Add missing reference to GeometryCollection](https://github.com/georust/geojson/pull/68) 227 | 228 | ## 0.7.0 229 | 230 | * [Upgrade to serde 1.0](https://github.com/georust/geojson/pull/64) 231 | 232 | ## 0.6.0 233 | 234 | * [Upgrade rust-geo dep, use num_traits instead of num](https://github.com/georust/geojson/pull/62) 235 | 236 | ## 0.5.0 237 | 238 | * [Upgrade to serde 0.9, remove rustc-serialize support, make geo-interop feature mandatory,](https://github.com/georust/geojson/pull/60) 239 | 240 | ## 0.4.3 241 | 242 | * [Ability to convert a structure from rust-geojson to rust-geo](https://github.com/georust/geojson/pull/56) 243 | 244 | ## 0.4.2 245 | 246 | * [Ability to convert a structure from rust-geo to rust-geojson](https://github.com/georust/geojson/issues/51) 247 | 248 | ## 0.4.1 249 | 250 | * [Derive `Eq` and `PartialEq` for `Error`.](https://github.com/georust/geojson/issues/51) 251 | 252 | ## 0.4.0 253 | 254 | * [Implement `Display` instead of `ToString` for `GeoJson`.](https://github.com/georust/geojson/pull/46) 255 | * [Upgrade Serde from 0.7 to 0.8](https://github.com/georust/geojson/pull/48) 256 | * [Add a few `convert::From` impls for `GeoJson`.](https://github.com/georust/geojson/pull/45) 257 | 258 | ## 0.3.0 259 | 260 | * [Permit `geometry` field on `feature` objects to be `null`](https://github.com/georust/geojson/issues/42) 261 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # The GeoRust Code of Conduct 2 | 3 | This document is based on, and aims to track the [Rust Code of Conduct](https://www.rust-lang.org/conduct.html) 4 | 5 | ## Conduct 6 | 7 | **Contact**: [mods@georust.org](mailto:mods@georust.org) 8 | 9 | * We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. 10 | * On IRC, please avoid using overtly sexual nicknames or other nicknames that might detract from a friendly, safe and welcoming environment for all. 11 | * Please be kind and courteous. There's no need to be mean or rude. 12 | * Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. 13 | * Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. 14 | * We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term "harassment" as including the definition in the Citizen Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don't tolerate behavior that excludes people in socially marginalized groups. 15 | * Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the [GeoRust moderation team][mod_team] immediately. Whether you're a regular contributor or a newcomer, we care about making this community a safe place for you and we've got your back. 16 | * Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome. 17 | 18 | ## Moderation 19 | 20 | 21 | These are the policies for upholding our community's standards of conduct. If you feel that a thread needs moderation, please contact the [GeoRust moderation team][mod_team]. 22 | 23 | 1. Remarks that violate the Rust standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.) 24 | 2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed. 25 | 3. Moderators will first respond to such remarks with a warning. 26 | 4. If the warning is unheeded, the user will be "kicked," i.e., kicked out of the communication channel to cool off. 27 | 5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded. 28 | 6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology. 29 | 7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, **in private**. Complaints about bans in-channel are not allowed. 30 | 8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others. 31 | 32 | In the GeoRust community we strive to go the extra step to look out for each other. Don't just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they're off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely. 33 | 34 | And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Rustaceans comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. 35 | 36 | *Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).* 37 | 38 | [mod_team]: mailto:mods@georust.org 39 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "geojson" 3 | description = "Read and write GeoJSON vector geographic data" 4 | version = "0.24.2" 5 | authors = ["The GeoRust Developers "] 6 | license = "MIT/Apache-2.0" 7 | repository = "https://github.com/georust/geojson" 8 | readme = "README.md" 9 | documentation = "https://docs.rs/geojson/" 10 | keywords = ["geojson", "gis", "json", "geo"] 11 | edition = "2018" 12 | 13 | [features] 14 | default = ["geo-types"] 15 | 16 | [dependencies] 17 | serde = { version="~1.0", features = ["derive"] } 18 | serde_json = "~1.0" 19 | geo-types = { version = "0.7.13", features = ["serde"], optional = true } 20 | thiserror = "2.0.6" 21 | log = "0.4.17" 22 | 23 | [dev-dependencies] 24 | num-traits = "0.2" 25 | criterion = "0.5.1" 26 | 27 | [[bench]] 28 | name = "parse" 29 | harness = false 30 | 31 | [[bench]] 32 | name = "serialize" 33 | harness = false 34 | 35 | [[bench]] 36 | name = "to_geo_types" 37 | harness = false 38 | 39 | [package.metadata.docs.rs] 40 | all-features = true 41 | rustdoc-args = ["--cfg", "docsrs"] 42 | 43 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 The Rust Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geojson 2 | 3 | [Documentation](https://docs.rs/geojson/) 4 | 5 | Library for serializing the [GeoJSON](http://geojson.org) vector GIS file format 6 | 7 | ## Minimum Rust Version 8 | 9 | This library requires a minimum Rust version of 1.34 (released April 11 2019) 10 | 11 | ## Examples 12 | 13 | ### Reading 14 | 15 | ```rust 16 | use geojson::GeoJson; 17 | 18 | let geojson_str = r#" 19 | { 20 | "type": "Feature", 21 | "properties": { 22 | "name": "Firestone Grill" 23 | }, 24 | "geometry": { 25 | "type": "Point", 26 | "coordinates": [-120.66029,35.2812] 27 | } 28 | } 29 | "#; 30 | 31 | let geojson = geojson_str.parse::().unwrap(); 32 | ``` 33 | 34 | ### Writing 35 | 36 | ```rust 37 | use geojson::{Feature, GeoJson, Geometry, Value, JsonObject, JsonValue}; 38 | 39 | let geometry = Geometry::new( 40 | Value::Point(vec![-120.66029,35.2812]) 41 | ); 42 | 43 | let mut properties = JsonObject::new(); 44 | properties.insert( 45 | String::from("name"), 46 | JsonValue::from("Firestone Grill"), 47 | ); 48 | 49 | let geojson = GeoJson::Feature(Feature { 50 | bbox: None, 51 | geometry: Some(geometry), 52 | id: None, 53 | properties: Some(properties), 54 | foreign_members: None, 55 | }); 56 | 57 | let geojson_string = geojson.to_string(); 58 | ``` 59 | 60 | ## License 61 | 62 | Licensed under either of 63 | 64 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 65 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 66 | 67 | at your option. 68 | 69 | ### Contribution 70 | 71 | Unless you explicitly state otherwise, any contribution intentionally submitted 72 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 73 | additional terms or conditions. 74 | -------------------------------------------------------------------------------- /benches/parse.rs: -------------------------------------------------------------------------------- 1 | use geojson::de::deserialize_geometry; 2 | use geojson::GeoJson; 3 | 4 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 5 | 6 | use std::io::BufReader; 7 | 8 | fn parse_feature_collection_benchmark(c: &mut Criterion) { 9 | let geojson_str = include_str!("../tests/fixtures/countries.geojson"); 10 | 11 | c.bench_function("parse (countries.geojson)", |b| { 12 | b.iter(|| match geojson_str.parse::() { 13 | Ok(GeoJson::FeatureCollection(fc)) => { 14 | assert_eq!(fc.features.len(), 180); 15 | black_box(fc) 16 | } 17 | _ => panic!("unexpected result"), 18 | }) 19 | }); 20 | 21 | c.bench_function("FeatureReader::features (countries.geojson)", |b| { 22 | b.iter(|| { 23 | let feature_reader = 24 | geojson::FeatureReader::from_reader(BufReader::new(geojson_str.as_bytes())); 25 | let mut count = 0; 26 | for feature in feature_reader.features() { 27 | let feature = feature.unwrap(); 28 | black_box(feature); 29 | count += 1; 30 | } 31 | assert_eq!(count, 180); 32 | }); 33 | }); 34 | 35 | c.bench_function("FeatureReader::deserialize (countries.geojson)", |b| { 36 | b.iter(|| { 37 | #[allow(unused)] 38 | #[derive(serde::Deserialize)] 39 | struct Country { 40 | geometry: geojson::Geometry, 41 | name: String, 42 | } 43 | let feature_reader = 44 | geojson::FeatureReader::from_reader(BufReader::new(geojson_str.as_bytes())); 45 | 46 | let mut count = 0; 47 | for feature in feature_reader.deserialize::().unwrap() { 48 | let feature = feature.unwrap(); 49 | black_box(feature); 50 | count += 1; 51 | } 52 | assert_eq!(count, 180); 53 | }); 54 | }); 55 | 56 | #[cfg(feature = "geo-types")] 57 | c.bench_function( 58 | "FeatureReader::deserialize to geo-types (countries.geojson)", 59 | |b| { 60 | b.iter(|| { 61 | #[allow(unused)] 62 | #[derive(serde::Deserialize)] 63 | struct Country { 64 | #[serde(deserialize_with = "deserialize_geometry")] 65 | geometry: geo_types::Geometry, 66 | name: String, 67 | } 68 | let feature_reader = 69 | geojson::FeatureReader::from_reader(BufReader::new(geojson_str.as_bytes())); 70 | 71 | let mut count = 0; 72 | for feature in feature_reader.deserialize::().unwrap() { 73 | let feature = feature.unwrap(); 74 | black_box(feature); 75 | count += 1; 76 | } 77 | assert_eq!(count, 180); 78 | }); 79 | }, 80 | ); 81 | } 82 | 83 | fn parse_geometry_collection_benchmark(c: &mut Criterion) { 84 | c.bench_function("parse (geometry_collection.geojson)", |b| { 85 | let geojson_str = include_str!("../tests/fixtures/geometry_collection.geojson"); 86 | 87 | b.iter(|| { 88 | let _ = black_box(geojson_str.parse::()); 89 | }); 90 | }); 91 | } 92 | 93 | criterion_group!( 94 | benches, 95 | parse_feature_collection_benchmark, 96 | parse_geometry_collection_benchmark 97 | ); 98 | criterion_main!(benches); 99 | -------------------------------------------------------------------------------- /benches/serialize.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use geojson::{de::deserialize_geometry, ser::serialize_geometry}; 3 | 4 | fn serialize_feature_collection_benchmark(c: &mut Criterion) { 5 | let geojson_str = include_str!("../tests/fixtures/countries.geojson"); 6 | 7 | c.bench_function( 8 | "serialize geojson::FeatureCollection struct (countries.geojson)", 9 | |b| { 10 | let geojson = geojson_str.parse::().unwrap(); 11 | 12 | b.iter(|| { 13 | let geojson_string = serde_json::to_string(&geojson).unwrap(); 14 | // Sanity check that we serialized a long string of some kind. 15 | assert_eq!(geojson_string.len(), 256890); 16 | black_box(geojson_string); 17 | }); 18 | }, 19 | ); 20 | 21 | c.bench_function("serialize custom struct (countries.geojson)", |b| { 22 | #[derive(serde::Serialize, serde::Deserialize)] 23 | struct Country { 24 | geometry: geojson::Geometry, 25 | name: String, 26 | } 27 | let features = 28 | geojson::de::deserialize_feature_collection_str_to_vec::(geojson_str).unwrap(); 29 | assert_eq!(features.len(), 180); 30 | 31 | b.iter(|| { 32 | let geojson_string = geojson::ser::to_feature_collection_string(&features).unwrap(); 33 | // Sanity check that we serialized a long string of some kind. 34 | // 35 | // Note this is slightly shorter than the GeoJson round-trip above because we drop 36 | // some fields, like foreign members 37 | assert_eq!(geojson_string.len(), 254908); 38 | black_box(geojson_string); 39 | }); 40 | }); 41 | 42 | #[cfg(feature = "geo-types")] 43 | c.bench_function( 44 | "serialize custom struct to geo-types (countries.geojson)", 45 | |b| { 46 | #[derive(serde::Serialize, serde::Deserialize)] 47 | struct Country { 48 | #[serde( 49 | serialize_with = "serialize_geometry", 50 | deserialize_with = "deserialize_geometry" 51 | )] 52 | geometry: geo_types::Geometry, 53 | name: String, 54 | } 55 | let features = 56 | geojson::de::deserialize_feature_collection_str_to_vec::(geojson_str) 57 | .unwrap(); 58 | assert_eq!(features.len(), 180); 59 | 60 | b.iter(|| { 61 | let geojson_string = geojson::ser::to_feature_collection_string(&features).unwrap(); 62 | // Sanity check that we serialized a long string of some kind. 63 | // 64 | // Note this is slightly shorter than the GeoJson round-trip above because we drop 65 | // some fields, like foreign members 66 | assert_eq!(geojson_string.len(), 254908); 67 | black_box(geojson_string); 68 | }); 69 | }, 70 | ); 71 | } 72 | 73 | criterion_group!(benches, serialize_feature_collection_benchmark); 74 | criterion_main!(benches); 75 | -------------------------------------------------------------------------------- /benches/to_geo_types.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use std::convert::TryFrom; 3 | 4 | fn benchmark_group(c: &mut Criterion) { 5 | let geojson_str = include_str!("../tests/fixtures/countries.geojson"); 6 | let geojson = geojson_str.parse::().unwrap(); 7 | 8 | #[cfg(feature = "geo-types")] 9 | c.bench_function("Convert to geo-types", move |b| { 10 | b.iter(|| black_box(geo_types::GeometryCollection::::try_from(&geojson).unwrap())); 11 | }); 12 | } 13 | 14 | criterion_group!(benches, benchmark_group); 15 | criterion_main!(benches); 16 | -------------------------------------------------------------------------------- /examples/deserialize.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | struct Country { 5 | // see the geo_types example if you want to store 6 | // geotypes in your struct 7 | geometry: geojson::Geometry, 8 | name: String, 9 | } 10 | 11 | use std::error::Error; 12 | use std::fs::File; 13 | use std::io::{BufReader, BufWriter}; 14 | 15 | fn main() -> Result<(), Box> { 16 | let file_reader = BufReader::new(File::open("tests/fixtures/countries.geojson")?); 17 | 18 | // Create a Vec of Country structs from the GeoJSON 19 | let countries: Vec = 20 | geojson::de::deserialize_feature_collection_to_vec::(file_reader)?; 21 | assert_eq!(countries.len(), 180); 22 | 23 | // Write the structs back to GeoJSON 24 | let file_writer = BufWriter::new(File::create("example-output-countries.geojson")?); 25 | geojson::ser::to_feature_collection_writer(file_writer, &countries)?; 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /examples/deserialize_to_geo_types.rs: -------------------------------------------------------------------------------- 1 | use geojson::{de::deserialize_geometry, ser::serialize_geometry}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use std::error::Error; 5 | use std::fs::File; 6 | use std::io::{BufReader, BufWriter}; 7 | 8 | #[cfg(not(feature = "geo-types"))] 9 | fn main() -> Result<(), Box> { 10 | panic!("this example requires geo-types") 11 | } 12 | 13 | #[cfg(feature = "geo-types")] 14 | fn main() -> Result<(), Box> { 15 | #[derive(Serialize, Deserialize)] 16 | struct Country { 17 | #[serde( 18 | serialize_with = "serialize_geometry", 19 | deserialize_with = "deserialize_geometry" 20 | )] 21 | geometry: geo_types::Geometry, 22 | name: String, 23 | } 24 | 25 | let file_reader = BufReader::new(File::open("tests/fixtures/countries.geojson")?); 26 | 27 | // Create a Vec of Country structs from the GeoJSON 28 | let countries: Vec = 29 | geojson::de::deserialize_feature_collection_to_vec::(file_reader)?; 30 | assert_eq!(countries.len(), 180); 31 | 32 | // Write the structs back to GeoJSON 33 | let file_writer = BufWriter::new(File::create("example-output-countries.geojson")?); 34 | geojson::ser::to_feature_collection_writer(file_writer, &countries)?; 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /examples/deserialize_to_geojson_types.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fs::File; 3 | use std::io::{BufReader, BufWriter}; 4 | 5 | use geojson::FeatureCollection; 6 | 7 | fn main() -> Result<(), Box> { 8 | let file_reader = BufReader::new(File::open("tests/fixtures/countries.geojson")?); 9 | 10 | let countries: FeatureCollection = serde_json::from_reader(file_reader)?; 11 | assert_eq!(countries.features.len(), 180); 12 | 13 | // Write the structs back to GeoJSON 14 | let file_writer = BufWriter::new(File::create("example-output-countries.geojson")?); 15 | serde_json::to_writer(file_writer, &countries)?; 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /examples/geojson_to_string.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fs::File; 3 | use std::io::BufReader; 4 | 5 | use geojson::{Feature, GeoJson}; 6 | 7 | fn main() -> Result<(), Box> { 8 | let file_reader = BufReader::new(File::open("tests/fixtures/canonical/good-feature.geojson")?); 9 | 10 | let feature: Feature = serde_json::from_reader(file_reader)?; 11 | 12 | let geojson: GeoJson = feature.into(); 13 | 14 | println!("{}", &geojson.to_string()); 15 | println!("{}", &geojson.to_string_pretty()?); 16 | 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /examples/stream_reader_writer.rs: -------------------------------------------------------------------------------- 1 | use geojson::{de::deserialize_geometry, ser::serialize_geometry, FeatureReader, FeatureWriter}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use std::error::Error; 5 | use std::fs::File; 6 | use std::io::{BufReader, BufWriter}; 7 | 8 | #[cfg(not(feature = "geo-types"))] 9 | fn main() -> Result<(), Box> { 10 | panic!("this example requires geo-types") 11 | } 12 | 13 | #[cfg(feature = "geo-types")] 14 | fn main() -> Result<(), Box> { 15 | #[derive(Serialize, Deserialize)] 16 | struct Country { 17 | #[serde( 18 | serialize_with = "serialize_geometry", 19 | deserialize_with = "deserialize_geometry" 20 | )] 21 | geometry: geo_types::Geometry, 22 | name: String, 23 | } 24 | 25 | let reader = { 26 | let file_reader = BufReader::new(File::open("tests/fixtures/countries.geojson")?); 27 | FeatureReader::from_reader(file_reader) 28 | }; 29 | 30 | let mut writer = { 31 | let file_writer = BufWriter::new(File::create("example-output-countries.geojson")?); 32 | FeatureWriter::from_writer(file_writer) 33 | }; 34 | 35 | let mut country_count = 0; 36 | for country in reader.deserialize::()? { 37 | let country = country?; 38 | country_count += 1; 39 | 40 | writer.serialize(&country)?; 41 | } 42 | 43 | assert_eq!(country_count, 180); 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /src/conversion/from_geo_types.rs: -------------------------------------------------------------------------------- 1 | use geo_types::{self, CoordFloat}; 2 | 3 | use crate::{geometry, Feature, FeatureCollection}; 4 | 5 | use crate::{LineStringType, PointType, PolygonType}; 6 | use std::convert::From; 7 | 8 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 9 | impl From<&geo_types::Point> for geometry::Value 10 | where 11 | T: CoordFloat, 12 | { 13 | fn from(point: &geo_types::Point) -> Self { 14 | let coords = create_point_type(point); 15 | 16 | geometry::Value::Point(coords) 17 | } 18 | } 19 | 20 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 21 | impl From<&geo_types::MultiPoint> for geometry::Value 22 | where 23 | T: CoordFloat, 24 | { 25 | fn from(multi_point: &geo_types::MultiPoint) -> Self { 26 | let coords = multi_point 27 | .0 28 | .iter() 29 | .map(|point| create_point_type(point)) 30 | .collect(); 31 | 32 | geometry::Value::MultiPoint(coords) 33 | } 34 | } 35 | 36 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 37 | impl From<&geo_types::LineString> for geometry::Value 38 | where 39 | T: CoordFloat, 40 | { 41 | fn from(line_string: &geo_types::LineString) -> Self { 42 | let coords = create_line_string_type(line_string); 43 | 44 | geometry::Value::LineString(coords) 45 | } 46 | } 47 | 48 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 49 | impl From<&geo_types::Line> for geometry::Value 50 | where 51 | T: CoordFloat, 52 | { 53 | fn from(line: &geo_types::Line) -> Self { 54 | let coords = create_from_line_type(line); 55 | 56 | geometry::Value::LineString(coords) 57 | } 58 | } 59 | 60 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 61 | impl From<&geo_types::Triangle> for geometry::Value 62 | where 63 | T: CoordFloat, 64 | { 65 | fn from(triangle: &geo_types::Triangle) -> Self { 66 | let coords = create_from_triangle_type(triangle); 67 | 68 | geometry::Value::Polygon(coords) 69 | } 70 | } 71 | 72 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 73 | impl From<&geo_types::Rect> for geometry::Value 74 | where 75 | T: CoordFloat, 76 | { 77 | fn from(rect: &geo_types::Rect) -> Self { 78 | let coords = create_from_rect_type(rect); 79 | 80 | geometry::Value::Polygon(coords) 81 | } 82 | } 83 | 84 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 85 | impl From<&geo_types::MultiLineString> for geometry::Value 86 | where 87 | T: CoordFloat, 88 | { 89 | fn from(multi_line_string: &geo_types::MultiLineString) -> Self { 90 | let coords = create_multi_line_string_type(multi_line_string); 91 | 92 | geometry::Value::MultiLineString(coords) 93 | } 94 | } 95 | 96 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 97 | impl From<&geo_types::Polygon> for geometry::Value 98 | where 99 | T: CoordFloat, 100 | { 101 | fn from(polygon: &geo_types::Polygon) -> Self { 102 | let coords = create_polygon_type(polygon); 103 | 104 | geometry::Value::Polygon(coords) 105 | } 106 | } 107 | 108 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 109 | impl From<&geo_types::MultiPolygon> for geometry::Value 110 | where 111 | T: CoordFloat, 112 | { 113 | fn from(multi_polygon: &geo_types::MultiPolygon) -> Self { 114 | let coords = create_multi_polygon_type(multi_polygon); 115 | 116 | geometry::Value::MultiPolygon(coords) 117 | } 118 | } 119 | 120 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 121 | impl From<&geo_types::GeometryCollection> for geometry::Value 122 | where 123 | T: CoordFloat, 124 | { 125 | fn from(geometry_collection: &geo_types::GeometryCollection) -> Self { 126 | let values = geometry_collection 127 | .0 128 | .iter() 129 | .map(|geometry| geometry::Geometry::new(geometry::Value::from(geometry))) 130 | .collect(); 131 | 132 | geometry::Value::GeometryCollection(values) 133 | } 134 | } 135 | 136 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 137 | impl From<&geo_types::GeometryCollection> for FeatureCollection 138 | where 139 | T: CoordFloat, 140 | { 141 | fn from(geometry_collection: &geo_types::GeometryCollection) -> Self { 142 | let values: Vec = geometry_collection 143 | .0 144 | .iter() 145 | .map(|geometry| geometry::Geometry::new(geometry::Value::from(geometry)).into()) 146 | .collect(); 147 | 148 | FeatureCollection { 149 | bbox: None, 150 | features: values, 151 | foreign_members: None, 152 | } 153 | } 154 | } 155 | 156 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 157 | impl<'a, T> From<&'a geo_types::Geometry> for geometry::Value 158 | where 159 | T: CoordFloat, 160 | { 161 | /// Convert from `geo_types::Geometry` enums 162 | fn from(geometry: &'a geo_types::Geometry) -> Self { 163 | match *geometry { 164 | geo_types::Geometry::Point(ref point) => geometry::Value::from(point), 165 | geo_types::Geometry::MultiPoint(ref multi_point) => geometry::Value::from(multi_point), 166 | geo_types::Geometry::LineString(ref line_string) => geometry::Value::from(line_string), 167 | geo_types::Geometry::Line(ref line) => geometry::Value::from(line), 168 | geo_types::Geometry::Triangle(ref triangle) => geometry::Value::from(triangle), 169 | geo_types::Geometry::Rect(ref rect) => geometry::Value::from(rect), 170 | geo_types::Geometry::GeometryCollection(ref gc) => geometry::Value::from(gc), 171 | geo_types::Geometry::MultiLineString(ref multi_line_string) => { 172 | geometry::Value::from(multi_line_string) 173 | } 174 | geo_types::Geometry::Polygon(ref polygon) => geometry::Value::from(polygon), 175 | geo_types::Geometry::MultiPolygon(ref multi_polygon) => { 176 | geometry::Value::from(multi_polygon) 177 | } 178 | } 179 | } 180 | } 181 | 182 | fn create_point_type(point: &geo_types::Point) -> PointType 183 | where 184 | T: CoordFloat, 185 | { 186 | let x: f64 = point.x().to_f64().unwrap(); 187 | let y: f64 = point.y().to_f64().unwrap(); 188 | 189 | vec![x, y] 190 | } 191 | 192 | fn create_line_string_type(line_string: &geo_types::LineString) -> LineStringType 193 | where 194 | T: CoordFloat, 195 | { 196 | line_string 197 | .points() 198 | .map(|point| create_point_type(&point)) 199 | .collect() 200 | } 201 | 202 | fn create_from_line_type(line_string: &geo_types::Line) -> LineStringType 203 | where 204 | T: CoordFloat, 205 | { 206 | vec![ 207 | create_point_type(&line_string.start_point()), 208 | create_point_type(&line_string.end_point()), 209 | ] 210 | } 211 | 212 | fn create_from_triangle_type(triangle: &geo_types::Triangle) -> PolygonType 213 | where 214 | T: CoordFloat, 215 | { 216 | create_polygon_type(&triangle.to_polygon()) 217 | } 218 | 219 | fn create_from_rect_type(rect: &geo_types::Rect) -> PolygonType 220 | where 221 | T: CoordFloat, 222 | { 223 | create_polygon_type(&rect.to_polygon()) 224 | } 225 | 226 | fn create_multi_line_string_type( 227 | multi_line_string: &geo_types::MultiLineString, 228 | ) -> Vec 229 | where 230 | T: CoordFloat, 231 | { 232 | multi_line_string 233 | .0 234 | .iter() 235 | .map(|line_string| create_line_string_type(line_string)) 236 | .collect() 237 | } 238 | 239 | fn create_polygon_type(polygon: &geo_types::Polygon) -> PolygonType 240 | where 241 | T: CoordFloat, 242 | { 243 | let mut coords = vec![polygon 244 | .exterior() 245 | .points() 246 | .map(|point| create_point_type(&point)) 247 | .collect()]; 248 | 249 | coords.extend( 250 | polygon 251 | .interiors() 252 | .iter() 253 | .map(|line_string| create_line_string_type(line_string)), 254 | ); 255 | 256 | coords 257 | } 258 | 259 | fn create_multi_polygon_type(multi_polygon: &geo_types::MultiPolygon) -> Vec 260 | where 261 | T: CoordFloat, 262 | { 263 | multi_polygon 264 | .0 265 | .iter() 266 | .map(|polygon| create_polygon_type(polygon)) 267 | .collect() 268 | } 269 | 270 | #[cfg(test)] 271 | mod tests { 272 | use crate::{GeoJson, Geometry, Value}; 273 | use geo_types::{ 274 | Coord, GeometryCollection, Line, LineString, MultiLineString, MultiPoint, MultiPolygon, 275 | Point, Polygon, Rect, Triangle, 276 | }; 277 | 278 | #[test] 279 | fn geo_point_conversion_test() { 280 | // Test with f32 coordinates 281 | let geo_point = Point::new(40.02f32, 116.34f32); 282 | let geojson_point = Value::from(&geo_point); 283 | 284 | if let Value::Point(c) = geojson_point { 285 | assert_almost_eq!(geo_point.x(), c[0] as f32, 1e-6); 286 | assert_almost_eq!(geo_point.y(), c[1] as f32, 1e-6); 287 | } else { 288 | panic!("Not valid geometry {:?}", geojson_point); 289 | } 290 | 291 | // Test with f64 coordinates. 292 | let geo_point = Point::new(40.02f64, 116.34f64); 293 | let geojson_point = Value::from(&geo_point); 294 | 295 | if let Value::Point(c) = geojson_point { 296 | assert_almost_eq!(geo_point.x(), c[0], 1e-6); 297 | assert_almost_eq!(geo_point.y(), c[1], 1e-6); 298 | } else { 299 | panic!("Not valid geometry {:?}", geojson_point); 300 | } 301 | } 302 | 303 | #[test] 304 | fn geo_multi_point_conversion_test() { 305 | let p1 = Point::new(40.02f64, 116.34f64); 306 | let p2 = Point::new(13.02f64, 24.34f64); 307 | 308 | let geo_multi_point = MultiPoint(vec![p1, p2]); 309 | let geojson_multi_point = Value::from(&geo_multi_point); 310 | 311 | if let Value::MultiPoint(c) = geojson_multi_point { 312 | assert_almost_eq!(p1.x(), c[0][0], 1e-6); 313 | assert_almost_eq!(p1.y(), c[0][1], 1e-6); 314 | assert_almost_eq!(p2.x(), c[1][0], 1e-6); 315 | assert_almost_eq!(p2.y(), c[1][1], 1e-6); 316 | } else { 317 | panic!("Not valid geometry {:?}", geojson_multi_point); 318 | } 319 | } 320 | 321 | #[test] 322 | fn geo_line_string_conversion_test() { 323 | let p1 = Point::new(40.02f64, 116.34f64); 324 | let p2 = Point::new(13.02f64, 24.34f64); 325 | 326 | let geo_line_string = LineString::from(vec![p1, p2]); 327 | let geojson_line_point = Value::from(&geo_line_string); 328 | 329 | if let Value::LineString(c) = geojson_line_point { 330 | assert_almost_eq!(p1.x(), c[0][0], 1e-6); 331 | assert_almost_eq!(p1.y(), c[0][1], 1e-6); 332 | assert_almost_eq!(p2.x(), c[1][0], 1e-6); 333 | assert_almost_eq!(p2.y(), c[1][1], 1e-6); 334 | } else { 335 | panic!("Not valid geometry {:?}", geojson_line_point); 336 | } 337 | } 338 | 339 | #[test] 340 | fn geo_line_conversion_test() { 341 | let p1 = Point::new(40.02f64, 116.34f64); 342 | let p2 = Point::new(13.02f64, 24.34f64); 343 | 344 | let geo_line = Line::new(p1, p2); 345 | let geojson_line_point = Value::from(&geo_line); 346 | 347 | if let Value::LineString(c) = geojson_line_point { 348 | assert_almost_eq!(p1.x(), c[0][0], 1e-6); 349 | assert_almost_eq!(p1.y(), c[0][1], 1e-6); 350 | assert_almost_eq!(p2.x(), c[1][0], 1e-6); 351 | assert_almost_eq!(p2.y(), c[1][1], 1e-6); 352 | } else { 353 | panic!("Not valid geometry {:?}", geojson_line_point); 354 | } 355 | } 356 | 357 | #[test] 358 | fn geo_triangle_conversion_test() { 359 | let c1: Coord = Coord { x: 0., y: 0. }; 360 | let c2: Coord = Coord { x: 10., y: 20. }; 361 | let c3: Coord = Coord { x: 20., y: -10. }; 362 | 363 | let triangle = Triangle(c1, c2, c3); 364 | 365 | let geojson_polygon = Value::from(&triangle); 366 | 367 | // Geo-types Polygon construction introduces an extra vertex: let's check it! 368 | if let Value::Polygon(c) = geojson_polygon { 369 | assert_almost_eq!(c1.x, c[0][0][0], 1e-6); 370 | assert_almost_eq!(c1.y, c[0][0][1], 1e-6); 371 | assert_almost_eq!(c2.x, c[0][1][0], 1e-6); 372 | assert_almost_eq!(c2.y, c[0][1][1], 1e-6); 373 | assert_almost_eq!(c3.x, c[0][2][0], 1e-6); 374 | assert_almost_eq!(c3.y, c[0][2][1], 1e-6); 375 | assert_almost_eq!(c1.x, c[0][3][0], 1e-6); 376 | assert_almost_eq!(c1.y, c[0][3][1], 1e-6); 377 | } else { 378 | panic!("Not valid geometry {:?}", geojson_polygon); 379 | } 380 | } 381 | 382 | #[test] 383 | fn geo_rect_conversion_test() { 384 | // Same rect as geo_types::geometry::Rect::to_polygon doctest 385 | let c1: Coord = Coord { x: 0., y: 0. }; 386 | let c2: Coord = Coord { x: 1., y: 2. }; 387 | 388 | let rect = Rect::new(c1, c2); 389 | 390 | let geojson_polygon = Value::from(&rect); 391 | 392 | // Geo-types Polygon construction introduces an extra vertex: let's check it! 393 | if let Value::Polygon(c) = geojson_polygon { 394 | // checks are in the same order as the geo_types::geometry::Rect.to_polygon doctest 395 | assert_almost_eq!(c2.x, c[0][0][0], 1e-6); 396 | assert_almost_eq!(c1.y, c[0][0][1], 1e-6); 397 | assert_almost_eq!(c2.x, c[0][1][0], 1e-6); 398 | assert_almost_eq!(c2.y, c[0][1][1], 1e-6); 399 | assert_almost_eq!(c1.x, c[0][2][0], 1e-6); 400 | assert_almost_eq!(c2.y, c[0][2][1], 1e-6); 401 | assert_almost_eq!(c1.x, c[0][3][0], 1e-6); 402 | assert_almost_eq!(c1.y, c[0][3][1], 1e-6); 403 | assert_almost_eq!(c2.x, c[0][4][0], 1e-6); 404 | assert_almost_eq!(c1.y, c[0][4][1], 1e-6); 405 | } else { 406 | panic!("Not valid geometry {:?}", geojson_polygon); 407 | } 408 | } 409 | 410 | #[test] 411 | fn geo_multi_line_string_conversion_test() { 412 | let p1 = Point::new(40.02f64, 116.34f64); 413 | let p2 = Point::new(13.02f64, 24.34f64); 414 | let p3 = Point::new(46.84f64, 160.95f64); 415 | let p4 = Point::new(42.02f64, 96.34f64); 416 | 417 | let geo_line_string1 = LineString::from(vec![p1, p2]); 418 | let geo_line_string2 = LineString::from(vec![p3, p4]); 419 | 420 | let geo_multi_line_string = MultiLineString(vec![geo_line_string1, geo_line_string2]); 421 | let geojson_multi_line_point = Value::from(&geo_multi_line_string); 422 | 423 | if let Value::MultiLineString(c) = geojson_multi_line_point { 424 | assert_almost_eq!(p1.x(), c[0][0][0], 1e-6); 425 | assert_almost_eq!(p1.y(), c[0][0][1], 1e-6); 426 | assert_almost_eq!(p2.x(), c[0][1][0], 1e-6); 427 | assert_almost_eq!(p2.y(), c[0][1][1], 1e-6); 428 | assert_almost_eq!(p3.x(), c[1][0][0], 1e-6); 429 | assert_almost_eq!(p3.y(), c[1][0][1], 1e-6); 430 | assert_almost_eq!(p4.x(), c[1][1][0], 1e-6); 431 | assert_almost_eq!(p4.y(), c[1][1][1], 1e-6); 432 | } else { 433 | panic!("Not valid geometry {:?}", geojson_multi_line_point); 434 | } 435 | } 436 | 437 | #[test] 438 | fn geo_polygon_conversion_test() { 439 | let p1 = Point::new(100.0f64, 0.0f64); 440 | let p2 = Point::new(101.0f64, 0.0f64); 441 | let p3 = Point::new(101.0f64, 1.0f64); 442 | let p4 = Point::new(104.0f64, 0.2f64); 443 | let p5 = Point::new(100.9f64, 0.2f64); 444 | let p6 = Point::new(100.9f64, 0.7f64); 445 | 446 | let geo_line_string1 = LineString::from(vec![p1, p2, p3, p1]); 447 | let geo_line_string2 = LineString::from(vec![p4, p5, p6, p4]); 448 | 449 | let geo_polygon = Polygon::new(geo_line_string1, vec![geo_line_string2]); 450 | let geojson_polygon = Value::from(&geo_polygon); 451 | 452 | if let Value::Polygon(c) = geojson_polygon { 453 | assert_almost_eq!(p1.x(), c[0][0][0], 1e-6); 454 | assert_almost_eq!(p1.y(), c[0][0][1], 1e-6); 455 | assert_almost_eq!(p2.x(), c[0][1][0], 1e-6); 456 | assert_almost_eq!(p2.y(), c[0][1][1], 1e-6); 457 | assert_almost_eq!(p3.x(), c[0][2][0], 1e-6); 458 | assert_almost_eq!(p3.y(), c[0][2][1], 1e-6); 459 | assert_almost_eq!(p4.x(), c[1][0][0], 1e-6); 460 | assert_almost_eq!(p4.y(), c[1][0][1], 1e-6); 461 | assert_almost_eq!(p5.x(), c[1][1][0], 1e-6); 462 | assert_almost_eq!(p5.y(), c[1][1][1], 1e-6); 463 | assert_almost_eq!(p6.x(), c[1][2][0], 1e-6); 464 | assert_almost_eq!(p6.y(), c[1][2][1], 1e-6); 465 | } else { 466 | panic!("Not valid geometry {:?}", geojson_polygon); 467 | } 468 | } 469 | 470 | #[test] 471 | fn geo_multi_polygon_conversion_test() { 472 | let p1 = Point::new(102.0f64, 2.0f64); 473 | let p2 = Point::new(103.0f64, 2.0f64); 474 | let p3 = Point::new(103.0f64, 3.0f64); 475 | let p4 = Point::new(100.0f64, 0.0f64); 476 | let p5 = Point::new(101.0f64, 0.0f64); 477 | let p6 = Point::new(101.0f64, 1.0f64); 478 | 479 | let geo_line_string1 = LineString::from(vec![p1, p2, p3, p1]); 480 | let geo_line_string2 = LineString::from(vec![p4, p5, p6, p4]); 481 | 482 | let geo_polygon1 = Polygon::new(geo_line_string1, vec![]); 483 | let geo_polygon2 = Polygon::new(geo_line_string2, vec![]); 484 | let geo_multi_polygon = MultiPolygon(vec![geo_polygon1, geo_polygon2]); 485 | let geojson_multi_polygon = Value::from(&geo_multi_polygon); 486 | 487 | if let Value::MultiPolygon(c) = geojson_multi_polygon { 488 | assert_almost_eq!(p1.x(), c[0][0][0][0], 1e-6); 489 | assert_almost_eq!(p1.y(), c[0][0][0][1], 1e-6); 490 | assert_almost_eq!(p2.x(), c[0][0][1][0], 1e-6); 491 | assert_almost_eq!(p2.y(), c[0][0][1][1], 1e-6); 492 | assert_almost_eq!(p3.x(), c[0][0][2][0], 1e-6); 493 | assert_almost_eq!(p3.y(), c[0][0][2][1], 1e-6); 494 | assert_almost_eq!(p4.x(), c[1][0][0][0], 1e-6); 495 | assert_almost_eq!(p4.y(), c[1][0][0][1], 1e-6); 496 | assert_almost_eq!(p5.x(), c[1][0][1][0], 1e-6); 497 | assert_almost_eq!(p5.y(), c[1][0][1][1], 1e-6); 498 | assert_almost_eq!(p6.x(), c[1][0][2][0], 1e-6); 499 | assert_almost_eq!(p6.y(), c[1][0][2][1], 1e-6); 500 | } else { 501 | panic!("Not valid geometry {:?}", geojson_multi_polygon); 502 | } 503 | } 504 | 505 | #[test] 506 | fn geo_geometry_collection_conversion_test() { 507 | let p1 = Point::new(100.0f64, 0.0f64); 508 | let p2 = Point::new(100.0f64, 1.0f64); 509 | let p3 = Point::new(101.0f64, 1.0f64); 510 | let p4 = Point::new(102.0f64, 0.0f64); 511 | let p5 = Point::new(101.0f64, 0.0f64); 512 | let geo_multi_point = MultiPoint(vec![p1, p2]); 513 | let geo_multi_line_string = MultiLineString(vec![ 514 | LineString::from(vec![p1, p2]), 515 | LineString::from(vec![p2, p3]), 516 | ]); 517 | let geo_multi_polygon = MultiPolygon(vec![ 518 | Polygon::new(LineString::from(vec![p3, p4, p5, p3]), vec![]), 519 | Polygon::new(LineString::from(vec![p1, p5, p3, p1]), vec![]), 520 | ]); 521 | let geo_geometry_collection = GeometryCollection(vec![ 522 | geo_types::Geometry::MultiPoint(geo_multi_point), 523 | geo_types::Geometry::MultiLineString(geo_multi_line_string), 524 | geo_types::Geometry::MultiPolygon(geo_multi_polygon), 525 | ]); 526 | 527 | let geojson_geometry_collection = Value::from(&geo_geometry_collection); 528 | 529 | if let Value::GeometryCollection(geometries) = geojson_geometry_collection { 530 | let geometry_type = |geometry: &Geometry| match geometry.value { 531 | Value::Point(..) => "Point", 532 | Value::MultiPoint(..) => "MultiPoint", 533 | Value::LineString(..) => "LineString", 534 | Value::MultiLineString(..) => "MultiLineString", 535 | Value::Polygon(..) => "Polygon", 536 | Value::MultiPolygon(..) => "MultiPolygon", 537 | Value::GeometryCollection(..) => "GeometryCollection", 538 | }; 539 | 540 | assert_eq!(3, geometries.len()); 541 | assert_eq!(geometry_type(&geometries[0]), "MultiPoint"); 542 | assert_eq!(geometry_type(&geometries[1]), "MultiLineString"); 543 | assert_eq!(geometry_type(&geometries[2]), "MultiPolygon"); 544 | } else { 545 | panic!("Not valid geometry {:?}", geojson_geometry_collection); 546 | } 547 | } 548 | 549 | #[test] 550 | fn test_from_geo_type_to_geojson() { 551 | let p1 = geo_types::Point::new(100.0f64, 0.0f64); 552 | let actual = serde_json::Value::from(GeoJson::from(&p1)); 553 | let expected: serde_json::Value = 554 | serde_json::json!({"coordinates": [100.0, 0.0], "type": "Point"}); 555 | assert_eq!(expected, actual); 556 | } 557 | 558 | #[test] 559 | fn test_from_iter_geo_type_to_geojson() { 560 | let p1 = geo_types::Point::new(100.0f64, 0.0f64); 561 | let p2 = geo_types::Point::new(200.0f64, 0.0f64); 562 | let points: Vec<_> = vec![p1, p2]; 563 | 564 | use std::iter::FromIterator; 565 | 566 | let actual = GeoJson::from_iter(points.iter()); 567 | let actual2 = points.iter().collect::(); 568 | assert_eq!(actual, actual2); 569 | 570 | let expected: serde_json::Value = serde_json::json!({ 571 | "type": "GeometryCollection", 572 | "geometries": [ 573 | {"coordinates": [100.0, 0.0], "type": "Point"}, 574 | {"coordinates": [200.0, 0.0], "type": "Point"}, 575 | ] 576 | }); 577 | assert_eq!(expected, serde_json::Value::from(actual)); 578 | } 579 | } 580 | -------------------------------------------------------------------------------- /src/conversion/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The GeoRust Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use geo_types::CoordFloat; 16 | 17 | use crate::geojson::GeoJson; 18 | 19 | use crate::Result; 20 | use std::convert::TryFrom; 21 | 22 | #[cfg(test)] 23 | macro_rules! assert_almost_eq { 24 | ($x:expr, $y:expr, $epsilon:expr) => {{ 25 | use num_traits::Zero; 26 | let a = $x.abs(); 27 | let b = $y.abs(); 28 | let delta = (a - b).abs(); 29 | 30 | if a.is_infinite() || a.is_nan() || b.is_infinite() || b.is_nan() { 31 | panic!( 32 | "Assertion failed: Non comparable value ({} = {}, {} = {})", 33 | stringify!($x), 34 | $x, 35 | stringify!($y), 36 | $y 37 | ); 38 | } else if a.is_zero() || b.is_zero() { 39 | if delta > $epsilon { 40 | panic!( 41 | "Assertion failed: ({} = {}, {} = {}, delta = {})", 42 | stringify!($x), 43 | $x, 44 | stringify!($y), 45 | $y, 46 | delta / b 47 | ); 48 | } 49 | } else { 50 | let normalized_delta = delta / b; 51 | if normalized_delta > $epsilon { 52 | panic!( 53 | "Assertion failed: ({} = {}, {} = {}, delta = {})", 54 | stringify!($x), 55 | $x, 56 | stringify!($y), 57 | $y, 58 | normalized_delta 59 | ); 60 | } 61 | } 62 | }}; 63 | } 64 | 65 | macro_rules! try_from_owned_value { 66 | ($to:ty) => { 67 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 68 | impl TryFrom for $to { 69 | type Error = Error; 70 | 71 | fn try_from(value: geometry::Value) -> Result { 72 | (&value).try_into() 73 | } 74 | } 75 | }; 76 | } 77 | 78 | pub(crate) mod from_geo_types; 79 | pub(crate) mod to_geo_types; 80 | 81 | /// A shortcut for producing `geo_types` [GeometryCollection](../geo_types/struct.GeometryCollection.html) objects 82 | /// from arbitrary valid GeoJSON input. 83 | /// 84 | /// This function is primarily intended for easy processing of GeoJSON `FeatureCollection` 85 | /// objects using the `geo` crate, and sacrifices a little performance for convenience. 86 | /// # Example 87 | /// 88 | /// ``` 89 | /// use geo_types::{Geometry, GeometryCollection, Point}; 90 | /// #[allow(deprecated)] 91 | /// use geojson::{quick_collection, GeoJson}; 92 | /// 93 | /// let geojson_str = r#" 94 | /// { 95 | /// "type": "FeatureCollection", 96 | /// "features": [ 97 | /// { 98 | /// "type": "Feature", 99 | /// "properties": {}, 100 | /// "geometry": { 101 | /// "type": "Point", 102 | /// "coordinates": [-1.0, 2.0] 103 | /// } 104 | /// } 105 | /// ] 106 | /// } 107 | /// "#; 108 | /// let geojson = geojson_str.parse::().unwrap(); 109 | /// // Turn the GeoJSON string into a geo_types GeometryCollection 110 | /// #[allow(deprecated)] 111 | /// let mut collection: GeometryCollection = quick_collection(&geojson).unwrap(); 112 | /// assert_eq!(collection[0], Geometry::Point(Point::new(-1.0, 2.0))) 113 | /// ``` 114 | #[deprecated( 115 | since = "0.24.1", 116 | note = "use `geo_types::GeometryCollection::try_from(&geojson)` instead" 117 | )] 118 | #[cfg_attr(docsrs, doc(cfg(feature = "geo-types")))] 119 | pub fn quick_collection(gj: &GeoJson) -> Result> 120 | where 121 | T: CoordFloat, 122 | { 123 | geo_types::GeometryCollection::try_from(gj) 124 | } 125 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Module for all GeoJSON-related errors 2 | use crate::Feature; 3 | use serde_json::value::Value; 4 | use thiserror::Error; 5 | 6 | /// Errors which can occur when encoding, decoding, and converting GeoJSON 7 | #[derive(Error, Debug)] 8 | pub enum Error { 9 | #[error("Encountered non-array value for a 'bbox' object: `{0}`")] 10 | BboxExpectedArray(Value), 11 | #[error("Encountered non-numeric value within 'bbox' array")] 12 | BboxExpectedNumericValues(Value), 13 | #[error("Encountered a non-object type for GeoJSON: `{0}`")] 14 | GeoJsonExpectedObject(Value), 15 | /// This was previously `GeoJsonUnknownType`, but has been split for clarity 16 | #[error("Expected a Feature, FeatureCollection, or Geometry, but got an empty type")] 17 | EmptyType, 18 | #[error("invalid writer state: {0}")] 19 | InvalidWriterState(&'static str), 20 | #[error("IO Error: {0}")] 21 | Io(std::io::Error), 22 | /// This was previously `GeoJsonUnknownType`, but has been split for clarity 23 | #[error("Expected a Feature mapping, but got a `{0}`")] 24 | NotAFeature(String), 25 | #[error("Expected type: `{expected_type}`, but found `{found_type}`")] 26 | InvalidGeometryConversion { 27 | expected_type: &'static str, 28 | found_type: &'static str, 29 | }, 30 | #[error( 31 | "Attempted to a convert a feature without a geometry into a geo_types::Geometry: `{0}`" 32 | )] 33 | FeatureHasNoGeometry(Feature), 34 | #[error("Encountered an unknown 'geometry' object type: `{0}`")] 35 | GeometryUnknownType(String), 36 | #[error("Error while deserializing JSON: {0}")] 37 | MalformedJson(serde_json::Error), 38 | #[error("Encountered neither object type nor null type for 'properties' object: `{0}`")] 39 | PropertiesExpectedObjectOrNull(Value), 40 | #[error("Encountered neither object type nor null type for 'geometry' field on 'feature' object: `{0}`")] 41 | FeatureInvalidGeometryValue(Value), 42 | #[error( 43 | "Encountered neither number type nor string type for 'id' field on 'feature' object: `{0}`" 44 | )] 45 | FeatureInvalidIdentifierType(Value), 46 | #[error("Expected GeoJSON type `{expected}`, found `{actual}`")] 47 | ExpectedType { expected: String, actual: String }, 48 | #[error("Expected a String value, but got a `{0}`")] 49 | ExpectedStringValue(Value), 50 | #[error("Expected a GeoJSON property for `{0}`, but got None")] 51 | ExpectedProperty(String), 52 | #[error("Expected a floating-point value, but got None")] 53 | ExpectedF64Value, 54 | #[error("Expected an Array value, but got `{0}`")] 55 | ExpectedArrayValue(String), 56 | #[error("Expected an owned Object, but got `{0}`")] 57 | ExpectedObjectValue(Value), 58 | #[error("A position must contain two or more elements, but got `{0}`")] 59 | PositionTooShort(usize), 60 | } 61 | 62 | pub type Result = std::result::Result; 63 | 64 | impl From for Error { 65 | fn from(error: serde_json::Error) -> Self { 66 | Self::MalformedJson(error) 67 | } 68 | } 69 | 70 | impl From for Error { 71 | fn from(error: std::io::Error) -> Self { 72 | Self::Io(error) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/feature.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The GeoRust Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::convert::TryFrom; 16 | use std::str::FromStr; 17 | 18 | use crate::errors::{Error, Result}; 19 | use crate::{util, Feature, Geometry, Value}; 20 | use crate::{JsonObject, JsonValue}; 21 | use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; 22 | 23 | impl From for Feature { 24 | fn from(geom: Geometry) -> Feature { 25 | Feature { 26 | bbox: geom.bbox.clone(), 27 | foreign_members: geom.foreign_members.clone(), 28 | geometry: Some(geom), 29 | id: None, 30 | properties: None, 31 | } 32 | } 33 | } 34 | 35 | impl From for Feature { 36 | fn from(val: Value) -> Feature { 37 | Feature { 38 | bbox: None, 39 | foreign_members: None, 40 | geometry: Some(Geometry::from(val)), 41 | id: None, 42 | properties: None, 43 | } 44 | } 45 | } 46 | 47 | impl FromStr for Feature { 48 | type Err = Error; 49 | 50 | fn from_str(s: &str) -> Result { 51 | Self::try_from(crate::GeoJson::from_str(s)?) 52 | } 53 | } 54 | 55 | impl<'a> From<&'a Feature> for JsonObject { 56 | fn from(feature: &'a Feature) -> JsonObject { 57 | // The unwrap() should never panic, because Feature contains only JSON-serializable types 58 | match serde_json::to_value(feature).unwrap() { 59 | serde_json::Value::Object(obj) => obj, 60 | value => { 61 | // Panic should never happen, because `impl Serialize for Feature` always produces an 62 | // Object 63 | panic!( 64 | "serializing Feature should result in an Object, but got something {:?}", 65 | value 66 | ) 67 | } 68 | } 69 | } 70 | } 71 | 72 | impl Feature { 73 | pub fn from_json_object(object: JsonObject) -> Result { 74 | Self::try_from(object) 75 | } 76 | 77 | pub fn from_json_value(value: JsonValue) -> Result { 78 | Self::try_from(value) 79 | } 80 | 81 | /// Return the value of this property, if it's set 82 | pub fn property(&self, key: impl AsRef) -> Option<&JsonValue> { 83 | self.properties 84 | .as_ref() 85 | .and_then(|props| props.get(key.as_ref())) 86 | } 87 | 88 | /// Return true iff this key is set 89 | pub fn contains_property(&self, key: impl AsRef) -> bool { 90 | match &self.properties { 91 | None => false, 92 | Some(props) => props.contains_key(key.as_ref()), 93 | } 94 | } 95 | 96 | /// Set a property to this value, overwriting any possible older value 97 | pub fn set_property(&mut self, key: impl Into, value: impl Into) { 98 | let key: String = key.into(); 99 | let value: JsonValue = value.into(); 100 | if self.properties.is_none() { 101 | self.properties = Some(JsonObject::new()); 102 | } 103 | 104 | self.properties.as_mut().unwrap().insert(key, value); 105 | } 106 | 107 | /// Removes a key from the `properties` map, returning the value at the key if the key 108 | /// was previously in the `properties` map. 109 | pub fn remove_property(&mut self, key: impl AsRef) -> Option { 110 | self.properties 111 | .as_mut() 112 | .and_then(|props| props.remove(key.as_ref())) 113 | } 114 | 115 | /// The number of properties 116 | pub fn len_properties(&self) -> usize { 117 | match &self.properties { 118 | None => 0, 119 | Some(props) => props.len(), 120 | } 121 | } 122 | 123 | /// Returns an iterator over all the properties 124 | pub fn properties_iter(&self) -> Box + '_> { 125 | match self.properties.as_ref() { 126 | None => Box::new(std::iter::empty()), 127 | Some(props) => Box::new(props.iter()), 128 | } 129 | } 130 | } 131 | 132 | impl TryFrom for Feature { 133 | type Error = Error; 134 | 135 | fn try_from(mut object: JsonObject) -> Result { 136 | let res = &*util::expect_type(&mut object)?; 137 | match res { 138 | "Feature" => Ok(Feature { 139 | geometry: util::get_geometry(&mut object)?, 140 | properties: util::get_properties(&mut object)?, 141 | id: util::get_id(&mut object)?, 142 | bbox: util::get_bbox(&mut object)?, 143 | foreign_members: util::get_foreign_members(object)?, 144 | }), 145 | _ => Err(Error::NotAFeature(res.to_string())), 146 | } 147 | } 148 | } 149 | 150 | impl TryFrom for Feature { 151 | type Error = Error; 152 | 153 | fn try_from(value: JsonValue) -> Result { 154 | if let JsonValue::Object(obj) = value { 155 | Self::try_from(obj) 156 | } else { 157 | Err(Error::GeoJsonExpectedObject(value)) 158 | } 159 | } 160 | } 161 | 162 | impl Serialize for Feature { 163 | fn serialize(&self, serializer: S) -> std::result::Result 164 | where 165 | S: Serializer, 166 | { 167 | let mut map = serializer.serialize_map(None)?; 168 | map.serialize_entry("type", "Feature")?; 169 | map.serialize_entry("geometry", &self.geometry)?; 170 | map.serialize_entry("properties", &self.properties)?; 171 | if let Some(ref bbox) = self.bbox { 172 | map.serialize_entry("bbox", bbox)?; 173 | } 174 | if let Some(ref id) = self.id { 175 | map.serialize_entry("id", id)?; 176 | } 177 | if let Some(ref foreign_members) = self.foreign_members { 178 | for (key, value) in foreign_members { 179 | map.serialize_entry(key, value)?; 180 | } 181 | } 182 | map.end() 183 | } 184 | } 185 | 186 | impl<'de> Deserialize<'de> for Feature { 187 | fn deserialize(deserializer: D) -> std::result::Result 188 | where 189 | D: Deserializer<'de>, 190 | { 191 | use serde::de::Error as SerdeError; 192 | 193 | let val = JsonObject::deserialize(deserializer)?; 194 | 195 | Feature::from_json_object(val).map_err(|e| D::Error::custom(e.to_string())) 196 | } 197 | } 198 | 199 | /// Feature identifier 200 | /// 201 | /// [GeoJSON Format Specification § 3.2](https://tools.ietf.org/html/rfc7946#section-3.2) 202 | #[derive(Clone, Debug, PartialEq)] 203 | pub enum Id { 204 | String(String), 205 | Number(serde_json::Number), 206 | } 207 | 208 | impl Serialize for Id { 209 | fn serialize(&self, serializer: S) -> std::result::Result 210 | where 211 | S: Serializer, 212 | { 213 | match self { 214 | Id::String(ref s) => s.serialize(serializer), 215 | Id::Number(ref n) => n.serialize(serializer), 216 | } 217 | } 218 | } 219 | 220 | #[cfg(test)] 221 | mod tests { 222 | use crate::JsonObject; 223 | use crate::{feature, Error, Feature, GeoJson, Geometry, Value}; 224 | use serde_json::json; 225 | 226 | use std::str::FromStr; 227 | 228 | fn feature_json_str() -> &'static str { 229 | "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{}}" 230 | } 231 | 232 | fn properties() -> Option { 233 | Some(JsonObject::new()) 234 | } 235 | 236 | fn feature() -> Feature { 237 | crate::Feature { 238 | geometry: Some(Geometry { 239 | value: value(), 240 | bbox: None, 241 | foreign_members: None, 242 | }), 243 | properties: properties(), 244 | bbox: None, 245 | id: None, 246 | foreign_members: None, 247 | } 248 | } 249 | 250 | fn value() -> Value { 251 | Value::Point(vec![1.1, 2.1]) 252 | } 253 | 254 | fn geometry() -> Geometry { 255 | Geometry::new(value()) 256 | } 257 | 258 | fn encode(feature: &Feature) -> String { 259 | serde_json::to_string(&feature).unwrap() 260 | } 261 | 262 | fn decode(json_string: String) -> GeoJson { 263 | json_string.parse().unwrap() 264 | } 265 | 266 | #[test] 267 | fn encode_decode_feature() { 268 | let feature = feature(); 269 | 270 | // Test encoding 271 | let json_string = encode(&feature); 272 | assert_eq!(json_string, feature_json_str()); 273 | 274 | // Test decoding 275 | let decoded_feature = match decode(json_string) { 276 | GeoJson::Feature(f) => f, 277 | _ => unreachable!(), 278 | }; 279 | assert_eq!(decoded_feature, feature); 280 | } 281 | 282 | #[test] 283 | fn try_from_value() { 284 | use serde_json::json; 285 | use std::convert::TryInto; 286 | 287 | let json_value = json!({ 288 | "type": "Feature", 289 | "geometry": { 290 | "type": "Point", 291 | "coordinates": [1.1, 2.1] 292 | }, 293 | "properties": null, 294 | }); 295 | assert!(json_value.is_object()); 296 | 297 | let feature: Feature = json_value.try_into().unwrap(); 298 | assert_eq!( 299 | feature, 300 | Feature { 301 | bbox: None, 302 | geometry: Some(geometry()), 303 | id: None, 304 | properties: None, 305 | foreign_members: None, 306 | } 307 | ) 308 | } 309 | 310 | #[test] 311 | fn null_bbox() { 312 | let geojson_str = r#"{ 313 | "geometry": null, 314 | "bbox": null, 315 | "properties":{}, 316 | "type":"Feature" 317 | }"#; 318 | let geojson = geojson_str.parse::().unwrap(); 319 | let feature = match geojson { 320 | GeoJson::Feature(feature) => feature, 321 | _ => unimplemented!(), 322 | }; 323 | assert!(feature.bbox.is_none()); 324 | } 325 | 326 | #[test] 327 | fn test_display_feature() { 328 | let f = feature().to_string(); 329 | assert_eq!(f, "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{}}"); 330 | } 331 | 332 | #[test] 333 | fn feature_json_null_geometry() { 334 | let geojson_str = r#"{ 335 | "geometry": null, 336 | "properties":{}, 337 | "type":"Feature" 338 | }"#; 339 | let geojson = geojson_str.parse::().unwrap(); 340 | let feature = match geojson { 341 | GeoJson::Feature(feature) => feature, 342 | _ => unimplemented!(), 343 | }; 344 | assert!(feature.geometry.is_none()); 345 | } 346 | 347 | #[test] 348 | fn feature_json_invalid_geometry() { 349 | let geojson_str = r#"{"geometry":3.14,"properties":{},"type":"Feature"}"#; 350 | match geojson_str.parse::().unwrap_err() { 351 | Error::FeatureInvalidGeometryValue(_) => (), 352 | _ => unreachable!(), 353 | } 354 | } 355 | 356 | #[test] 357 | fn encode_decode_feature_with_id_number() { 358 | let feature_json_str = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{},\"id\":0}"; 359 | let feature = crate::Feature { 360 | geometry: Some(Geometry { 361 | value: Value::Point(vec![1.1, 2.1]), 362 | bbox: None, 363 | foreign_members: None, 364 | }), 365 | properties: properties(), 366 | bbox: None, 367 | id: Some(feature::Id::Number(0.into())), 368 | foreign_members: None, 369 | }; 370 | // Test encode 371 | let json_string = encode(&feature); 372 | assert_eq!(json_string, feature_json_str); 373 | 374 | // Test decode 375 | let decoded_feature = match decode(feature_json_str.into()) { 376 | GeoJson::Feature(f) => f, 377 | _ => unreachable!(), 378 | }; 379 | assert_eq!(decoded_feature, feature); 380 | } 381 | 382 | #[test] 383 | fn encode_decode_feature_with_id_string() { 384 | let feature_json_str = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{},\"id\":\"foo\"}"; 385 | let feature = crate::Feature { 386 | geometry: Some(Geometry { 387 | value: Value::Point(vec![1.1, 2.1]), 388 | bbox: None, 389 | foreign_members: None, 390 | }), 391 | properties: properties(), 392 | bbox: None, 393 | id: Some(feature::Id::String("foo".into())), 394 | foreign_members: None, 395 | }; 396 | // Test encode 397 | let json_string = encode(&feature); 398 | assert_eq!(json_string, feature_json_str); 399 | 400 | // Test decode 401 | let decoded_feature = match decode(feature_json_str.into()) { 402 | GeoJson::Feature(f) => f, 403 | _ => unreachable!(), 404 | }; 405 | assert_eq!(decoded_feature, feature); 406 | } 407 | 408 | #[test] 409 | fn decode_feature_with_invalid_id_type_object() { 410 | let feature_json_str = "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"id\":{},\"properties\":{},\"type\":\"Feature\"}"; 411 | assert!(matches!( 412 | feature_json_str.parse::(), 413 | Err(Error::FeatureInvalidIdentifierType(_)) 414 | )); 415 | } 416 | 417 | #[test] 418 | fn decode_feature_with_invalid_id_type_null() { 419 | let feature_json_str = "{\"geometry\":{\"coordinates\":[1.1,2.1],\"type\":\"Point\"},\"id\":null,\"properties\":{},\"type\":\"Feature\"}"; 420 | assert!(matches!( 421 | feature_json_str.parse::(), 422 | Err(Error::FeatureInvalidIdentifierType(_)) 423 | )); 424 | } 425 | 426 | #[test] 427 | fn encode_decode_feature_with_foreign_member() { 428 | use crate::JsonObject; 429 | use serde_json; 430 | let feature_json_str = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[1.1,2.1]},\"properties\":{},\"other_member\":\"some_value\"}"; 431 | 432 | let mut foreign_members = JsonObject::new(); 433 | foreign_members.insert( 434 | String::from("other_member"), 435 | serde_json::to_value("some_value").unwrap(), 436 | ); 437 | let feature = crate::Feature { 438 | geometry: Some(Geometry { 439 | value: Value::Point(vec![1.1, 2.1]), 440 | bbox: None, 441 | foreign_members: None, 442 | }), 443 | properties: properties(), 444 | bbox: None, 445 | id: None, 446 | foreign_members: Some(foreign_members), 447 | }; 448 | // Test encode 449 | let json_string = encode(&feature); 450 | assert_eq!(json_string, feature_json_str); 451 | 452 | // Test decode 453 | let decoded_feature = match decode(feature_json_str.into()) { 454 | GeoJson::Feature(f) => f, 455 | _ => unreachable!(), 456 | }; 457 | assert_eq!(decoded_feature, feature); 458 | } 459 | 460 | #[test] 461 | fn encode_decode_feature_with_null_properties() { 462 | let feature_json_str = r#"{"type":"Feature","geometry":{"type":"Point","coordinates":[1.1,2.1]},"properties":null}"#; 463 | 464 | let feature = crate::Feature { 465 | geometry: Some(Value::Point(vec![1.1, 2.1]).into()), 466 | properties: None, 467 | bbox: None, 468 | id: None, 469 | foreign_members: None, 470 | }; 471 | // Test encode 472 | let json_string = encode(&feature); 473 | assert_eq!(json_string, feature_json_str); 474 | 475 | // Test decode 476 | let decoded_feature = match decode(feature_json_str.into()) { 477 | GeoJson::Feature(f) => f, 478 | _ => unreachable!(), 479 | }; 480 | assert_eq!(decoded_feature, feature); 481 | } 482 | 483 | #[test] 484 | fn feature_ergonomic_property_access() { 485 | use serde_json::json; 486 | 487 | let mut feature = feature(); 488 | 489 | assert_eq!(feature.len_properties(), 0); 490 | assert_eq!(feature.property("foo"), None); 491 | assert!(!feature.contains_property("foo")); 492 | assert_eq!(feature.properties_iter().collect::>(), vec![]); 493 | 494 | feature.set_property("foo", 12); 495 | assert_eq!(feature.property("foo"), Some(&json!(12))); 496 | assert_eq!(feature.len_properties(), 1); 497 | assert!(feature.contains_property("foo")); 498 | assert_eq!( 499 | feature.properties_iter().collect::>(), 500 | vec![(&"foo".to_string(), &json!(12))] 501 | ); 502 | 503 | assert_eq!(Some(json!(12)), feature.remove_property("foo")); 504 | assert_eq!(feature.property("foo"), None); 505 | assert_eq!(feature.len_properties(), 0); 506 | assert!(!feature.contains_property("foo")); 507 | assert_eq!(feature.properties_iter().collect::>(), vec![]); 508 | } 509 | 510 | #[test] 511 | fn test_from_str_ok() { 512 | let feature_json = json!({ 513 | "type": "Feature", 514 | "geometry": { 515 | "type": "Point", 516 | "coordinates": [125.6, 10.1] 517 | }, 518 | "properties": { 519 | "name": "Dinagat Islands" 520 | } 521 | }) 522 | .to_string(); 523 | 524 | let feature = Feature::from_str(&feature_json).unwrap(); 525 | assert_eq!("Dinagat Islands", feature.property("name").unwrap()); 526 | } 527 | 528 | #[test] 529 | fn test_from_str_with_unexpected_type() { 530 | let geometry_json = json!({ 531 | "type": "Point", 532 | "coordinates": [125.6, 10.1] 533 | }) 534 | .to_string(); 535 | 536 | let actual_failure = Feature::from_str(&geometry_json).unwrap_err(); 537 | match actual_failure { 538 | Error::ExpectedType { actual, expected } => { 539 | assert_eq!(actual, "Geometry"); 540 | assert_eq!(expected, "Feature"); 541 | } 542 | e => panic!("unexpected error: {}", e), 543 | }; 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /src/feature_collection.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The GeoRust Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use serde::ser::SerializeMap; 16 | use std::convert::TryFrom; 17 | use std::iter::FromIterator; 18 | use std::str::FromStr; 19 | 20 | use crate::errors::{Error, Result}; 21 | use crate::{util, Bbox, Feature}; 22 | use crate::{JsonObject, JsonValue}; 23 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 24 | 25 | /// Feature Collection Objects 26 | /// 27 | /// [GeoJSON Format Specification § 3.3](https://tools.ietf.org/html/rfc7946#section-3.3) 28 | /// 29 | /// # Examples 30 | /// 31 | /// Serialization: 32 | /// 33 | /// ``` 34 | /// use geojson::FeatureCollection; 35 | /// use geojson::GeoJson; 36 | /// 37 | /// let feature_collection = FeatureCollection { 38 | /// bbox: None, 39 | /// features: vec![], 40 | /// foreign_members: None, 41 | /// }; 42 | /// 43 | /// let serialized = GeoJson::from(feature_collection).to_string(); 44 | /// 45 | /// assert_eq!( 46 | /// serialized, 47 | /// "{\"type\":\"FeatureCollection\",\"features\":[]}" 48 | /// ); 49 | /// ``` 50 | /// 51 | /// Collect from an iterator: 52 | /// 53 | /// ```rust 54 | /// use geojson::{Feature, FeatureCollection, Value}; 55 | /// 56 | /// let fc: FeatureCollection = (0..10) 57 | /// .map(|idx| -> Feature { 58 | /// let c = idx as f64; 59 | /// Value::Point(vec![1.0 * c, 2.0 * c, 3.0 * c]).into() 60 | /// }) 61 | /// .collect(); 62 | /// assert_eq!(fc.features.len(), 10); 63 | /// ``` 64 | #[derive(Clone, Debug, Default, PartialEq)] 65 | pub struct FeatureCollection { 66 | /// Bounding Box 67 | /// 68 | /// [GeoJSON Format Specification § 5](https://tools.ietf.org/html/rfc7946#section-5) 69 | pub bbox: Option, 70 | pub features: Vec, 71 | /// Foreign Members 72 | /// 73 | /// [GeoJSON Format Specification § 6](https://tools.ietf.org/html/rfc7946#section-6) 74 | pub foreign_members: Option, 75 | } 76 | 77 | impl IntoIterator for FeatureCollection { 78 | type Item = Feature; 79 | type IntoIter = std::vec::IntoIter; 80 | 81 | fn into_iter(self) -> Self::IntoIter { 82 | self.features.into_iter() 83 | } 84 | } 85 | 86 | impl<'a> IntoIterator for &'a FeatureCollection { 87 | type Item = &'a Feature; 88 | type IntoIter = std::slice::Iter<'a, Feature>; 89 | 90 | fn into_iter(self) -> Self::IntoIter { 91 | IntoIterator::into_iter(&self.features) 92 | } 93 | } 94 | 95 | impl<'a> From<&'a FeatureCollection> for JsonObject { 96 | fn from(fc: &'a FeatureCollection) -> JsonObject { 97 | // The unwrap() should never panic, because FeatureCollection contains only JSON-serializable types 98 | match serde_json::to_value(fc).unwrap() { 99 | serde_json::Value::Object(obj) => obj, 100 | value => { 101 | // Panic should never happen, because `impl Serialize for FeatureCollection` always produces an 102 | // Object 103 | panic!("serializing FeatureCollection should result in an Object, but got something {:?}", value) 104 | } 105 | } 106 | } 107 | } 108 | 109 | impl FeatureCollection { 110 | pub fn from_json_object(object: JsonObject) -> Result { 111 | Self::try_from(object) 112 | } 113 | 114 | pub fn from_json_value(value: JsonValue) -> Result { 115 | Self::try_from(value) 116 | } 117 | } 118 | 119 | impl TryFrom for FeatureCollection { 120 | type Error = Error; 121 | 122 | fn try_from(mut object: JsonObject) -> Result { 123 | match util::expect_type(&mut object)? { 124 | ref type_ if type_ == "FeatureCollection" => Ok(FeatureCollection { 125 | bbox: util::get_bbox(&mut object)?, 126 | features: util::get_features(&mut object)?, 127 | foreign_members: util::get_foreign_members(object)?, 128 | }), 129 | type_ => Err(Error::ExpectedType { 130 | expected: "FeatureCollection".to_owned(), 131 | actual: type_, 132 | }), 133 | } 134 | } 135 | } 136 | 137 | impl TryFrom for FeatureCollection { 138 | type Error = Error; 139 | 140 | fn try_from(value: JsonValue) -> Result { 141 | if let JsonValue::Object(obj) = value { 142 | Self::try_from(obj) 143 | } else { 144 | Err(Error::GeoJsonExpectedObject(value)) 145 | } 146 | } 147 | } 148 | 149 | impl FromStr for FeatureCollection { 150 | type Err = Error; 151 | 152 | fn from_str(s: &str) -> Result { 153 | Self::try_from(crate::GeoJson::from_str(s)?) 154 | } 155 | } 156 | 157 | impl Serialize for FeatureCollection { 158 | fn serialize(&self, serializer: S) -> std::result::Result 159 | where 160 | S: Serializer, 161 | { 162 | let mut map = serializer.serialize_map(None)?; 163 | map.serialize_entry("type", "FeatureCollection")?; 164 | map.serialize_entry("features", &self.features)?; 165 | 166 | if let Some(ref bbox) = self.bbox { 167 | map.serialize_entry("bbox", bbox)?; 168 | } 169 | 170 | if let Some(ref foreign_members) = self.foreign_members { 171 | for (key, value) in foreign_members { 172 | map.serialize_entry(key, value)?; 173 | } 174 | } 175 | 176 | map.end() 177 | } 178 | } 179 | 180 | impl<'de> Deserialize<'de> for FeatureCollection { 181 | fn deserialize(deserializer: D) -> std::result::Result 182 | where 183 | D: Deserializer<'de>, 184 | { 185 | use serde::de::Error as SerdeError; 186 | 187 | let val = JsonObject::deserialize(deserializer)?; 188 | 189 | FeatureCollection::from_json_object(val).map_err(|e| D::Error::custom(e.to_string())) 190 | } 191 | } 192 | 193 | /// Create a [`FeatureCollection`] using the [`collect`] 194 | /// method on an iterator of `Feature`s. If every item 195 | /// contains a bounding-box of the same dimension, then the 196 | /// output has a bounding-box of the union of them. 197 | /// Otherwise, the output will not have a bounding-box. 198 | /// 199 | /// [`collect`]: std::iter::Iterator::collect 200 | impl FromIterator for FeatureCollection { 201 | fn from_iter>(iter: T) -> Self { 202 | let mut bbox = Some(vec![]); 203 | 204 | let features = iter 205 | .into_iter() 206 | .inspect(|feat| { 207 | // Try to compute the bounding-box 208 | 209 | let (curr_bbox, curr_len) = match &mut bbox { 210 | Some(curr_bbox) => { 211 | let curr_len = curr_bbox.len(); 212 | (curr_bbox, curr_len) 213 | } 214 | None => { 215 | // implies we can't compute a 216 | // bounding-box for this collection 217 | return; 218 | } 219 | }; 220 | 221 | match &feat.bbox { 222 | None => { 223 | bbox = None; 224 | } 225 | Some(fbox) if fbox.is_empty() || fbox.len() % 2 != 0 => { 226 | bbox = None; 227 | } 228 | Some(fbox) if curr_len == 0 => { 229 | // First iteration: just copy values from fbox 230 | curr_bbox.clone_from(fbox); 231 | } 232 | Some(fbox) if curr_len != fbox.len() => { 233 | bbox = None; 234 | } 235 | Some(fbox) => { 236 | // Update bbox by computing min/max 237 | curr_bbox.iter_mut().zip(fbox.iter()).enumerate().for_each( 238 | |(idx, (bc, fc))| { 239 | if idx < curr_len / 2 { 240 | // These are the min coords 241 | *bc = fc.min(*bc); 242 | } else { 243 | *bc = fc.max(*bc); 244 | } 245 | }, 246 | ); 247 | } 248 | }; 249 | }) 250 | .collect(); 251 | FeatureCollection { 252 | bbox, 253 | features, 254 | foreign_members: None, 255 | } 256 | } 257 | } 258 | 259 | #[cfg(test)] 260 | mod tests { 261 | use crate::{Error, Feature, FeatureCollection, Value}; 262 | use serde_json::json; 263 | 264 | use std::str::FromStr; 265 | 266 | #[test] 267 | fn test_fc_from_iterator() { 268 | let features: Vec = vec![ 269 | { 270 | let mut feat: Feature = Value::Point(vec![0., 0., 0.]).into(); 271 | feat.bbox = Some(vec![-1., -1., -1., 1., 1., 1.]); 272 | feat 273 | }, 274 | { 275 | let mut feat: Feature = 276 | Value::MultiPoint(vec![vec![10., 10., 10.], vec![11., 11., 11.]]).into(); 277 | feat.bbox = Some(vec![10., 10., 10., 11., 11., 11.]); 278 | feat 279 | }, 280 | ]; 281 | 282 | let fc: FeatureCollection = features.into_iter().collect(); 283 | assert_eq!(fc.features.len(), 2); 284 | assert_eq!(fc.bbox, Some(vec![-1., -1., -1., 11., 11., 11.])); 285 | } 286 | 287 | fn feature_collection_json() -> String { 288 | json!({ "type": "FeatureCollection", "features": [ 289 | { 290 | "type": "Feature", 291 | "geometry": { 292 | "type": "Point", 293 | "coordinates": [11.1, 22.2] 294 | }, 295 | "properties": { 296 | "name": "Downtown" 297 | } 298 | }, 299 | { 300 | "type": "Feature", 301 | "geometry": { 302 | "type": "Point", 303 | "coordinates": [33.3, 44.4] 304 | }, 305 | "properties": { 306 | "name": "Uptown" 307 | } 308 | }, 309 | ]}) 310 | .to_string() 311 | } 312 | 313 | #[test] 314 | fn test_from_str_ok() { 315 | let feature_collection = FeatureCollection::from_str(&feature_collection_json()).unwrap(); 316 | assert_eq!(2, feature_collection.features.len()); 317 | } 318 | 319 | #[test] 320 | fn iter_features() { 321 | let feature_collection = FeatureCollection::from_str(&feature_collection_json()).unwrap(); 322 | 323 | let mut names: Vec = vec![]; 324 | for feature in &feature_collection { 325 | let name = feature 326 | .property("name") 327 | .unwrap() 328 | .as_str() 329 | .unwrap() 330 | .to_string(); 331 | names.push(name); 332 | } 333 | 334 | assert_eq!(names, vec!["Downtown", "Uptown"]); 335 | } 336 | 337 | #[test] 338 | fn test_from_str_with_unexpected_type() { 339 | let geometry_json = json!({ 340 | "type": "Point", 341 | "coordinates": [125.6, 10.1] 342 | }) 343 | .to_string(); 344 | 345 | let actual_failure = FeatureCollection::from_str(&geometry_json).unwrap_err(); 346 | match actual_failure { 347 | Error::ExpectedType { actual, expected } => { 348 | assert_eq!(actual, "Geometry"); 349 | assert_eq!(expected, "FeatureCollection"); 350 | } 351 | e => panic!("unexpected error: {}", e), 352 | }; 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /src/feature_iterator.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The GeoRust Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #![allow(deprecated)] 15 | 16 | use crate::{Feature, Result}; 17 | 18 | use serde::Deserialize; 19 | use std::io; 20 | use std::marker::PhantomData; 21 | 22 | // TODO: Eventually make this private - and expose only FeatureReader. 23 | #[deprecated( 24 | since = "0.24.0", 25 | note = "use FeatureReader::from_reader(io).features() instead" 26 | )] 27 | /// Iteratively deserialize individual features from a stream containing a 28 | /// GeoJSON [`FeatureCollection`](struct@crate::FeatureCollection) 29 | /// 30 | /// This has the benefit of not having to wait until the end of the 31 | /// stream to get results, and avoids having to allocate memory for the complete collection. 32 | /// 33 | /// Based on example code found at . 34 | /// 35 | /// [GeoJSON Format Specification § 3.3](https://datatracker.ietf.org/doc/html/rfc7946#section-3.3) 36 | pub struct FeatureIterator<'de, R, D = Feature> { 37 | reader: R, 38 | state: State, 39 | output: PhantomData, 40 | lifetime: PhantomData<&'de ()>, 41 | } 42 | 43 | #[allow(clippy::enum_variant_names)] 44 | #[derive(Debug, Copy, Clone)] 45 | enum State { 46 | BeforeFeatures, 47 | DuringFeatures, 48 | AfterFeatures, 49 | } 50 | 51 | impl FeatureIterator<'_, R, D> { 52 | pub fn new(reader: R) -> Self { 53 | FeatureIterator { 54 | reader, 55 | state: State::BeforeFeatures, 56 | output: PhantomData, 57 | lifetime: PhantomData, 58 | } 59 | } 60 | } 61 | 62 | impl FeatureIterator<'_, R, D> 63 | where 64 | R: io::Read, 65 | { 66 | fn seek_to_next_feature(&mut self) -> Result { 67 | let mut next_bytes = [0]; 68 | loop { 69 | self.reader.read_exact(&mut next_bytes)?; 70 | let next_byte = next_bytes[0] as char; 71 | if next_byte.is_whitespace() { 72 | continue; 73 | } 74 | 75 | match (self.state, next_byte) { 76 | (State::BeforeFeatures, '[') => { 77 | self.state = State::DuringFeatures; 78 | return Ok(true); 79 | } 80 | (State::BeforeFeatures, _) => { 81 | continue; 82 | } 83 | (State::DuringFeatures, ',') => { 84 | return Ok(true); 85 | } 86 | (State::DuringFeatures, ']') => { 87 | self.state = State::AfterFeatures; 88 | return Ok(false); 89 | } 90 | (State::AfterFeatures, _) => { 91 | unreachable!("should not seek if we've already finished processing features") 92 | } 93 | _ => { 94 | return Err(io::Error::new( 95 | io::ErrorKind::InvalidInput, 96 | format!("next byte: {}", next_byte), 97 | ) 98 | .into()); 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | impl<'de, R, D> Iterator for FeatureIterator<'de, R, D> 106 | where 107 | R: io::Read, 108 | D: Deserialize<'de>, 109 | { 110 | type Item = Result; 111 | 112 | fn next(&mut self) -> Option { 113 | match self.seek_to_next_feature() { 114 | Ok(true) => {} 115 | Ok(false) => return None, 116 | Err(err) => { 117 | return Some(Err(err)); 118 | } 119 | } 120 | 121 | let de = serde_json::Deserializer::from_reader(&mut self.reader); 122 | match de.into_iter().next() { 123 | Some(Ok(v)) => Some(Ok(v)), 124 | Some(Err(err)) => Some(Err(err.into())), 125 | None => None, 126 | } 127 | } 128 | } 129 | 130 | #[cfg(test)] 131 | mod tests { 132 | use super::*; 133 | use crate::{Geometry, Value}; 134 | 135 | use std::io::BufReader; 136 | 137 | fn fc() -> &'static str { 138 | r#" 139 | { 140 | "type": "FeatureCollection", 141 | "features": [ 142 | { 143 | "type": "Feature", 144 | "geometry": { 145 | "type": "Point", 146 | "coordinates": [102.0, 0.5] 147 | }, 148 | "properties": { 149 | "prop0": "value0" 150 | } 151 | }, 152 | { 153 | "type": "Feature", 154 | "geometry": { 155 | "type": "LineString", 156 | "coordinates": [ 157 | [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0] 158 | ] 159 | }, 160 | "properties": { 161 | "prop0": "value0", 162 | "prop1": 0.0 163 | } 164 | }, 165 | { 166 | "type": "Feature", 167 | "geometry": { 168 | "type": "Polygon", 169 | "coordinates": [ 170 | [ 171 | [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], 172 | [100.0, 1.0], [100.0, 0.0] 173 | ] 174 | ] 175 | }, 176 | "properties": { 177 | "prop0": "value0", 178 | "prop1": { "this": "that" } 179 | } 180 | } 181 | ] 182 | }"# 183 | } 184 | 185 | #[test] 186 | fn stream_read_test() { 187 | let mut fi = FeatureIterator::<_, Feature>::new(BufReader::new(fc().as_bytes())); 188 | assert_eq!( 189 | Geometry { 190 | bbox: None, 191 | value: Value::Point(vec![102.0, 0.5]), 192 | foreign_members: None, 193 | }, 194 | fi.next().unwrap().unwrap().geometry.unwrap() 195 | ); 196 | assert_eq!( 197 | Geometry { 198 | bbox: None, 199 | value: Value::LineString(vec![ 200 | vec![102.0, 0.0], 201 | vec![103.0, 1.0], 202 | vec![104.0, 0.0], 203 | vec![105.0, 1.0] 204 | ]), 205 | foreign_members: None, 206 | }, 207 | fi.next().unwrap().unwrap().geometry.unwrap() 208 | ); 209 | assert_eq!( 210 | Geometry { 211 | bbox: None, 212 | value: Value::Polygon(vec![vec![ 213 | vec![100.0, 0.0], 214 | vec![101.0, 0.0], 215 | vec![101.0, 1.0], 216 | vec![100.0, 1.0], 217 | vec![100.0, 0.0] 218 | ]]), 219 | foreign_members: None, 220 | }, 221 | fi.next().unwrap().unwrap().geometry.unwrap() 222 | ); 223 | assert!(fi.next().is_none()); 224 | } 225 | 226 | mod field_ordering { 227 | use super::*; 228 | use crate::Feature; 229 | 230 | #[test] 231 | fn type_field_before_features_field() { 232 | let type_first = r#" 233 | { 234 | type: "FeatureCollection", 235 | features: [ 236 | { 237 | "type": "Feature", 238 | "geometry": { 239 | "type": "Point", 240 | "coordinates": [1.1, 1.2] 241 | }, 242 | "properties": { } 243 | }, 244 | { 245 | "type": "Feature", 246 | "geometry": { 247 | "type": "Point", 248 | "coordinates": [2.1, 2.2] 249 | }, 250 | "properties": { } 251 | } 252 | ] 253 | } 254 | "#; 255 | let features: Vec = 256 | FeatureIterator::new(BufReader::new(type_first.as_bytes())) 257 | .map(Result::unwrap) 258 | .collect(); 259 | assert_eq!(features.len(), 2); 260 | } 261 | 262 | #[test] 263 | fn features_field_before_type_field() { 264 | let type_first = r#" 265 | { 266 | features: [ 267 | { 268 | "type": "Feature", 269 | "geometry": { 270 | "type": "Point", 271 | "coordinates": [1.1, 1.2] 272 | }, 273 | "properties": {} 274 | }, 275 | { 276 | "type": "Feature", 277 | "geometry": { 278 | "type": "Point", 279 | "coordinates": [2.1, 2.2] 280 | }, 281 | "properties": { } 282 | } 283 | ], 284 | type: "FeatureCollection" 285 | } 286 | "#; 287 | let features: Vec = 288 | FeatureIterator::new(BufReader::new(type_first.as_bytes())) 289 | .map(Result::unwrap) 290 | .collect(); 291 | assert_eq!(features.len(), 2); 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /src/feature_reader.rs: -------------------------------------------------------------------------------- 1 | use crate::de::deserialize_feature_collection; 2 | use crate::{Feature, Result}; 3 | 4 | use serde::de::DeserializeOwned; 5 | 6 | use std::io::Read; 7 | 8 | /// Enumerates individual Features from a GeoJSON FeatureCollection 9 | pub struct FeatureReader { 10 | reader: R, 11 | } 12 | 13 | impl FeatureReader { 14 | /// Create a FeatureReader from the given `reader`. 15 | pub fn from_reader(reader: R) -> Self { 16 | Self { reader } 17 | } 18 | 19 | /// Iterate over the individual [`Feature`s](Feature) of a FeatureCollection. 20 | /// 21 | /// If instead you'd like to deserialize directly to your own struct, see [`FeatureReader::deserialize`]. 22 | /// 23 | /// # Examples 24 | /// 25 | /// ``` 26 | /// let feature_collection_string = r#"{ 27 | /// "type": "FeatureCollection", 28 | /// "features": [ 29 | /// { 30 | /// "type": "Feature", 31 | /// "geometry": { "type": "Point", "coordinates": [125.6, 10.1] }, 32 | /// "properties": { 33 | /// "name": "Dinagat Islands", 34 | /// "age": 123 35 | /// } 36 | /// }, 37 | /// { 38 | /// "type": "Feature", 39 | /// "geometry": { "type": "Point", "coordinates": [2.3, 4.5] }, 40 | /// "properties": { 41 | /// "name": "Neverland", 42 | /// "age": 456 43 | /// } 44 | /// } 45 | /// ] 46 | /// }"# 47 | /// .as_bytes(); 48 | /// let io_reader = std::io::BufReader::new(feature_collection_string); 49 | /// 50 | /// use geojson::FeatureReader; 51 | /// let feature_reader = FeatureReader::from_reader(io_reader); 52 | /// for feature in feature_reader.features() { 53 | /// let feature = feature.expect("valid geojson feature"); 54 | /// 55 | /// let name = feature.property("name").unwrap().as_str().unwrap(); 56 | /// let age = feature.property("age").unwrap().as_u64().unwrap(); 57 | /// 58 | /// if name == "Dinagat Islands" { 59 | /// assert_eq!(123, age); 60 | /// } else if name == "Neverland" { 61 | /// assert_eq!(456, age); 62 | /// } else { 63 | /// panic!("unexpected name: {}", name); 64 | /// } 65 | /// } 66 | /// ``` 67 | pub fn features(self) -> impl Iterator> { 68 | #[allow(deprecated)] 69 | crate::FeatureIterator::new(self.reader) 70 | } 71 | 72 | /// Deserialize the features of FeatureCollection into your own custom 73 | /// struct using the [`serde`](../../serde) crate. 74 | /// 75 | /// # Examples 76 | /// 77 | /// Your struct must implement or derive [`serde::Deserialize`]. 78 | /// 79 | /// If you have enabled the `geo-types` feature, which is enabled by default, you can 80 | /// deserialize directly to a useful geometry type. 81 | /// 82 | /// ```rust,ignore 83 | /// use geojson::{FeatureReader, de::deserialize_geometry}; 84 | /// 85 | /// #[derive(serde::Deserialize)] 86 | /// struct MyStruct { 87 | /// #[serde(deserialize_with = "deserialize_geometry")] 88 | /// geometry: geo_types::Point, 89 | /// name: String, 90 | /// age: u64, 91 | /// } 92 | /// ``` 93 | /// 94 | /// Then you can deserialize the FeatureCollection directly to your type. 95 | #[cfg_attr(feature = "geo-types", doc = "```")] 96 | #[cfg_attr(not(feature = "geo-types"), doc = "```ignore")] 97 | /// let feature_collection_string = r#"{ 98 | /// "type": "FeatureCollection", 99 | /// "features": [ 100 | /// { 101 | /// "type": "Feature", 102 | /// "geometry": { "type": "Point", "coordinates": [125.6, 10.1] }, 103 | /// "properties": { 104 | /// "name": "Dinagat Islands", 105 | /// "age": 123 106 | /// } 107 | /// }, 108 | /// { 109 | /// "type": "Feature", 110 | /// "geometry": { "type": "Point", "coordinates": [2.3, 4.5] }, 111 | /// "properties": { 112 | /// "name": "Neverland", 113 | /// "age": 456 114 | /// } 115 | /// } 116 | /// ] 117 | /// }"#.as_bytes(); 118 | /// let io_reader = std::io::BufReader::new(feature_collection_string); 119 | /// # 120 | /// # use geojson::{FeatureReader, de::deserialize_geometry}; 121 | /// # 122 | /// # #[derive(serde::Deserialize)] 123 | /// # struct MyStruct { 124 | /// # #[serde(deserialize_with = "deserialize_geometry")] 125 | /// # geometry: geo_types::Point, 126 | /// # name: String, 127 | /// # age: u64, 128 | /// # } 129 | /// 130 | /// let feature_reader = FeatureReader::from_reader(io_reader); 131 | /// for feature in feature_reader.deserialize::().unwrap() { 132 | /// let my_struct = feature.expect("valid geojson feature"); 133 | /// 134 | /// if my_struct.name == "Dinagat Islands" { 135 | /// assert_eq!(123, my_struct.age); 136 | /// } else if my_struct.name == "Neverland" { 137 | /// assert_eq!(456, my_struct.age); 138 | /// } else { 139 | /// panic!("unexpected name: {}", my_struct.name); 140 | /// } 141 | /// } 142 | /// ``` 143 | /// 144 | /// If you're not using [`geo-types`](geo_types), you can deserialize to a `geojson::Geometry` instead. 145 | /// ```rust,ignore 146 | /// use serde::Deserialize; 147 | /// #[derive(Deserialize)] 148 | /// struct MyStruct { 149 | /// geometry: geojson::Geometry, 150 | /// name: String, 151 | /// age: u64, 152 | /// } 153 | /// ``` 154 | pub fn deserialize(self) -> Result>> { 155 | deserialize_feature_collection(self.reader) 156 | } 157 | } 158 | 159 | #[cfg(test)] 160 | mod tests { 161 | use super::*; 162 | 163 | use serde::Deserialize; 164 | use serde_json::json; 165 | 166 | #[derive(Deserialize)] 167 | struct MyRecord { 168 | geometry: crate::Geometry, 169 | name: String, 170 | age: u64, 171 | } 172 | 173 | fn feature_collection_string() -> String { 174 | json!({ 175 | "type": "FeatureCollection", 176 | "features": [ 177 | { 178 | "type": "Feature", 179 | "geometry": { 180 | "type": "Point", 181 | "coordinates": [125.6, 10.1] 182 | }, 183 | "properties": { 184 | "name": "Dinagat Islands", 185 | "age": 123 186 | } 187 | }, 188 | { 189 | "type": "Feature", 190 | "geometry": { 191 | "type": "Point", 192 | "coordinates": [2.3, 4.5] 193 | }, 194 | "properties": { 195 | "name": "Neverland", 196 | "age": 456 197 | } 198 | } 199 | ] 200 | }) 201 | .to_string() 202 | } 203 | 204 | #[test] 205 | #[cfg(feature = "geo-types")] 206 | fn deserialize_into_type() { 207 | let feature_collection_string = feature_collection_string(); 208 | let mut bytes_reader = feature_collection_string.as_bytes(); 209 | let feature_reader = FeatureReader::from_reader(&mut bytes_reader); 210 | 211 | let records: Vec = feature_reader 212 | .deserialize() 213 | .expect("a valid feature collection") 214 | .map(|result| result.expect("a valid feature")) 215 | .collect(); 216 | 217 | assert_eq!(records.len(), 2); 218 | 219 | assert_eq!( 220 | records[0].geometry, 221 | (&geo_types::point!(x: 125.6, y: 10.1)).into() 222 | ); 223 | assert_eq!(records[0].name, "Dinagat Islands"); 224 | assert_eq!(records[0].age, 123); 225 | 226 | assert_eq!( 227 | records[1].geometry, 228 | (&geo_types::point!(x: 2.3, y: 4.5)).into() 229 | ); 230 | assert_eq!(records[1].name, "Neverland"); 231 | assert_eq!(records[1].age, 456); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/feature_writer.rs: -------------------------------------------------------------------------------- 1 | use crate::ser::to_feature_writer; 2 | use crate::{Error, Feature, Result}; 3 | 4 | use serde::Serialize; 5 | use std::io::Write; 6 | 7 | #[derive(PartialEq)] 8 | enum State { 9 | New, 10 | WritingFeatures, 11 | WritingForeignMembers, 12 | Finished, 13 | } 14 | 15 | /// Write Features to a FeatureCollection 16 | pub struct FeatureWriter { 17 | writer: W, 18 | state: State, 19 | } 20 | 21 | impl FeatureWriter { 22 | /// Create a FeatureWriter from the given `writer`. 23 | /// 24 | /// To append features from your custom structs, use [`FeatureWriter::serialize`]. 25 | /// 26 | /// To append features from [`Feature`] use [`FeatureWriter::write_feature`]. 27 | /// 28 | /// To write a foreign member, use [`FeatureWriter::write_foreign_member`] before appending any 29 | /// features. 30 | pub fn from_writer(writer: W) -> Self { 31 | Self { 32 | writer, 33 | state: State::New, 34 | } 35 | } 36 | 37 | /// Write a [`crate::Feature`] struct to the output stream. If you'd like to 38 | /// serialize your own custom structs, see [`FeatureWriter::serialize`] instead. 39 | pub fn write_feature(&mut self, feature: &Feature) -> Result<()> { 40 | match self.state { 41 | State::Finished => { 42 | return Err(Error::InvalidWriterState( 43 | "cannot write another Feature when writer has already finished", 44 | )) 45 | } 46 | State::New => { 47 | self.write_prefix()?; 48 | self.state = State::WritingFeatures; 49 | } 50 | State::WritingFeatures => { 51 | self.write_str(",")?; 52 | } 53 | State::WritingForeignMembers => { 54 | self.write_str(r#" "features": ["#)?; 55 | self.state = State::WritingFeatures; 56 | } 57 | } 58 | serde_json::to_writer(&mut self.writer, feature)?; 59 | Ok(()) 60 | } 61 | 62 | /// Serialize your own custom struct to the features of a FeatureCollection using the 63 | /// [`serde`] crate. 64 | /// 65 | /// # Examples 66 | /// 67 | /// Your struct must implement or derive [`serde::Serialize`]. 68 | /// 69 | /// If you have enabled the `geo-types` feature, which is enabled by default, you can 70 | /// serialize directly from a useful geometry type. 71 | /// 72 | /// ```rust,ignore 73 | /// use geojson::{FeatureWriter, ser::serialize_geometry}; 74 | /// 75 | /// #[derive(serde::Serialize)] 76 | /// struct MyStruct { 77 | /// #[serde(serialize_with = "serialize_geometry")] 78 | /// geometry: geo_types::Point, 79 | /// name: String, 80 | /// age: u64, 81 | /// } 82 | /// ``` 83 | /// 84 | /// Then you can serialize the FeatureCollection directly from your type. 85 | #[cfg_attr(feature = "geo-types", doc = "```")] 86 | #[cfg_attr(not(feature = "geo-types"), doc = "```ignore")] 87 | /// # 88 | /// # use geojson::{FeatureWriter, ser::serialize_geometry}; 89 | /// # 90 | /// # #[derive(serde::Serialize)] 91 | /// # struct MyStruct { 92 | /// # #[serde(serialize_with = "serialize_geometry")] 93 | /// # geometry: geo_types::Point, 94 | /// # name: String, 95 | /// # age: u64, 96 | /// # } 97 | /// 98 | /// let dinagat = MyStruct { 99 | /// geometry: geo_types::point!(x: 125.6, y: 10.1), 100 | /// name: "Dinagat Islands".to_string(), 101 | /// age: 123 102 | /// }; 103 | /// 104 | /// let neverland = MyStruct { 105 | /// geometry: geo_types::point!(x: 2.3, y: 4.5), 106 | /// name: "Neverland".to_string(), 107 | /// age: 456 108 | /// }; 109 | /// 110 | /// let mut output: Vec = vec![]; 111 | /// { 112 | /// let io_writer = std::io::BufWriter::new(&mut output); 113 | /// let mut feature_writer = FeatureWriter::from_writer(io_writer); 114 | /// feature_writer.serialize(&dinagat).unwrap(); 115 | /// feature_writer.serialize(&neverland).unwrap(); 116 | /// } 117 | /// 118 | /// let expected_output = r#"{ 119 | /// "type": "FeatureCollection", 120 | /// "features": [ 121 | /// { 122 | /// "type": "Feature", 123 | /// "geometry": { "type": "Point", "coordinates": [125.6, 10.1] }, 124 | /// "properties": { 125 | /// "name": "Dinagat Islands", 126 | /// "age": 123 127 | /// } 128 | /// }, 129 | /// { 130 | /// "type": "Feature", 131 | /// "geometry": { "type": "Point", "coordinates": [2.3, 4.5] }, 132 | /// "properties": { 133 | /// "name": "Neverland", 134 | /// "age": 456 135 | /// } 136 | /// } 137 | /// ] 138 | /// }"#.as_bytes(); 139 | /// 140 | /// fn assert_eq_json(bytes_1: &[u8], bytes_2: &[u8]) { 141 | /// // check for semantic equality, discarding any formatting/whitespace differences 142 | /// let json_1: serde_json::Value = serde_json::from_slice(bytes_1).unwrap(); 143 | /// let json_2: serde_json::Value = serde_json::from_slice(bytes_2).unwrap(); 144 | /// assert_eq!(json_1, json_2); 145 | /// } 146 | /// 147 | /// assert_eq_json(expected_output, &output); 148 | /// ``` 149 | /// 150 | /// If you're not using [`geo-types`](geo_types), you can deserialize to a `geojson::Geometry` instead. 151 | /// ```rust,ignore 152 | /// use serde::Deserialize; 153 | /// #[derive(Deserialize)] 154 | /// struct MyStruct { 155 | /// geometry: geojson::Geometry, 156 | /// name: String, 157 | /// age: u64, 158 | /// } 159 | /// ``` 160 | pub fn serialize(&mut self, value: &S) -> Result<()> { 161 | match self.state { 162 | State::Finished => { 163 | return Err(Error::InvalidWriterState( 164 | "cannot serialize another record when writer has already finished", 165 | )) 166 | } 167 | State::New => { 168 | self.write_prefix()?; 169 | self.state = State::WritingFeatures; 170 | } 171 | State::WritingFeatures => { 172 | self.write_str(",")?; 173 | } 174 | State::WritingForeignMembers => { 175 | self.write_str(r#" "features": ["#)?; 176 | self.state = State::WritingFeatures; 177 | } 178 | } 179 | to_feature_writer(&mut self.writer, value) 180 | } 181 | 182 | /// Write a [foreign member](https://datatracker.ietf.org/doc/html/rfc7946#section-6) to the 183 | /// output stream. This must be done before appending any features. 184 | pub fn write_foreign_member( 185 | &mut self, 186 | key: &str, 187 | value: &T, 188 | ) -> Result<()> { 189 | match self.state { 190 | State::Finished => Err(Error::InvalidWriterState( 191 | "cannot write foreign member when writer has already finished", 192 | )), 193 | State::New => { 194 | self.write_str(r#"{ "type": "FeatureCollection", "#)?; 195 | write!(self.writer, "\"{key}\": ")?; 196 | serde_json::to_writer(&mut self.writer, value)?; 197 | self.write_str(",")?; 198 | 199 | self.state = State::WritingForeignMembers; 200 | Ok(()) 201 | } 202 | State::WritingFeatures => Err(Error::InvalidWriterState( 203 | "must write foreign members before any features", 204 | )), 205 | State::WritingForeignMembers => { 206 | write!(self.writer, "\"{key}\": ")?; 207 | serde_json::to_writer(&mut self.writer, value)?; 208 | self.write_str(",")?; 209 | Ok(()) 210 | } 211 | } 212 | } 213 | 214 | /// Writes the closing syntax for the FeatureCollection. 215 | /// 216 | /// You shouldn't normally need to call this manually, as the writer will close itself upon 217 | /// being dropped. 218 | pub fn finish(&mut self) -> Result<()> { 219 | match self.state { 220 | State::Finished => { 221 | return Err(Error::InvalidWriterState( 222 | "cannot finish writer - it's already finished", 223 | )) 224 | } 225 | State::New => { 226 | self.state = State::Finished; 227 | self.write_prefix()?; 228 | self.write_suffix()?; 229 | } 230 | State::WritingFeatures | State::WritingForeignMembers => { 231 | self.state = State::Finished; 232 | self.write_suffix()?; 233 | } 234 | } 235 | Ok(()) 236 | } 237 | 238 | /// Flush the underlying writer buffer. 239 | /// 240 | /// You shouldn't normally need to call this manually, as the writer will flush itself upon 241 | /// being dropped. 242 | pub fn flush(&mut self) -> Result<()> { 243 | Ok(self.writer.flush()?) 244 | } 245 | 246 | fn write_prefix(&mut self) -> Result<()> { 247 | self.write_str(r#"{ "type": "FeatureCollection", "features": ["#) 248 | } 249 | 250 | fn write_suffix(&mut self) -> Result<()> { 251 | self.write_str("]}") 252 | } 253 | 254 | fn write_str(&mut self, text: &str) -> Result<()> { 255 | self.writer.write_all(text.as_bytes())?; 256 | Ok(()) 257 | } 258 | } 259 | 260 | impl Drop for FeatureWriter { 261 | fn drop(&mut self) { 262 | if self.state != State::Finished { 263 | _ = self.finish().map_err(|e| { 264 | log::error!("FeatureWriter errored while finishing in Drop impl. To handle errors like this, explicitly call `FeatureWriter::finish`. Error: {}", e); 265 | }); 266 | } 267 | } 268 | } 269 | 270 | #[cfg(test)] 271 | mod tests { 272 | use super::*; 273 | use crate::JsonValue; 274 | 275 | use serde_json::json; 276 | 277 | // an example struct that we want to serialize 278 | #[derive(Serialize)] 279 | struct MyRecord { 280 | geometry: crate::Geometry, 281 | name: String, 282 | age: u64, 283 | } 284 | 285 | #[test] 286 | fn write_empty() { 287 | let mut buffer: Vec = vec![]; 288 | { 289 | let mut writer = FeatureWriter::from_writer(&mut buffer); 290 | writer.finish().unwrap(); 291 | } 292 | 293 | let expected = json!({ 294 | "type": "FeatureCollection", 295 | "features": [] 296 | }); 297 | 298 | let actual_json: JsonValue = serde_json::from_slice(&buffer).unwrap(); 299 | assert_eq!(actual_json, expected); 300 | } 301 | 302 | #[test] 303 | fn finish_on_drop() { 304 | let mut buffer: Vec = vec![]; 305 | { 306 | _ = FeatureWriter::from_writer(&mut buffer); 307 | } 308 | 309 | let expected = json!({ 310 | "type": "FeatureCollection", 311 | "features": [] 312 | }); 313 | 314 | let actual_json: JsonValue = serde_json::from_slice(&buffer).unwrap(); 315 | assert_eq!(actual_json, expected); 316 | } 317 | 318 | #[test] 319 | fn write_feature() { 320 | let mut buffer: Vec = vec![]; 321 | { 322 | let mut writer = FeatureWriter::from_writer(&mut buffer); 323 | 324 | let record_1 = { 325 | let mut props = serde_json::Map::new(); 326 | props.insert("name".to_string(), "Mishka".into()); 327 | props.insert("age".to_string(), 12.into()); 328 | 329 | Feature { 330 | bbox: None, 331 | geometry: Some(crate::Geometry::from(crate::Value::Point(vec![1.1, 1.2]))), 332 | id: None, 333 | properties: Some(props), 334 | foreign_members: None, 335 | } 336 | }; 337 | 338 | let record_2 = { 339 | let mut props = serde_json::Map::new(); 340 | props.insert("name".to_string(), "Jane".into()); 341 | props.insert("age".to_string(), 22.into()); 342 | 343 | Feature { 344 | bbox: None, 345 | geometry: Some(crate::Geometry::from(crate::Value::Point(vec![2.1, 2.2]))), 346 | id: None, 347 | properties: Some(props), 348 | foreign_members: None, 349 | } 350 | }; 351 | 352 | writer.write_feature(&record_1).unwrap(); 353 | writer.write_feature(&record_2).unwrap(); 354 | writer.flush().unwrap(); 355 | } 356 | 357 | let expected = json!({ 358 | "type": "FeatureCollection", 359 | "features": [ 360 | { 361 | "type": "Feature", 362 | "geometry": { "type": "Point", "coordinates": [1.1, 1.2] }, 363 | "properties": { "name": "Mishka", "age": 12 364 | } 365 | }, 366 | { 367 | "type": "Feature", 368 | "geometry": { "type": "Point", "coordinates": [2.1, 2.2] }, 369 | "properties": { 370 | "name": "Jane", 371 | "age": 22 372 | } 373 | } 374 | ] 375 | }); 376 | 377 | let actual_json: JsonValue = serde_json::from_slice(&buffer).expect("valid json"); 378 | assert_eq!(actual_json, expected) 379 | } 380 | 381 | #[test] 382 | fn serialize() { 383 | let mut buffer: Vec = vec![]; 384 | { 385 | let mut writer = FeatureWriter::from_writer(&mut buffer); 386 | let record_1 = MyRecord { 387 | geometry: crate::Geometry::from(crate::Value::Point(vec![1.1, 1.2])), 388 | name: "Mishka".to_string(), 389 | age: 12, 390 | }; 391 | let record_2 = MyRecord { 392 | geometry: crate::Geometry::from(crate::Value::Point(vec![2.1, 2.2])), 393 | name: "Jane".to_string(), 394 | age: 22, 395 | }; 396 | writer.serialize(&record_1).unwrap(); 397 | writer.serialize(&record_2).unwrap(); 398 | writer.flush().unwrap(); 399 | } 400 | 401 | let expected = json!({ 402 | "type": "FeatureCollection", 403 | "features": [ 404 | { 405 | "type": "Feature", 406 | "geometry": { "type": "Point", "coordinates": [1.1, 1.2] }, 407 | "properties": { "name": "Mishka", "age": 12 408 | } 409 | }, 410 | { 411 | "type": "Feature", 412 | "geometry": { "type": "Point", "coordinates": [2.1, 2.2] }, 413 | "properties": { 414 | "name": "Jane", 415 | "age": 22 416 | } 417 | } 418 | ] 419 | }); 420 | 421 | let actual_json: JsonValue = serde_json::from_slice(&buffer).expect("valid json"); 422 | assert_eq!(actual_json, expected) 423 | } 424 | 425 | #[test] 426 | fn write_foreign_members() { 427 | let mut buffer: Vec = vec![]; 428 | { 429 | let mut writer = FeatureWriter::from_writer(&mut buffer); 430 | 431 | writer.write_foreign_member("extra", "string").unwrap(); 432 | writer.write_foreign_member("list", &[1, 2, 3]).unwrap(); 433 | writer 434 | .write_foreign_member("nested", &json!({"foo": "bar"})) 435 | .unwrap(); 436 | 437 | let record_1 = { 438 | let mut props = serde_json::Map::new(); 439 | props.insert("name".to_string(), "Mishka".into()); 440 | props.insert("age".to_string(), 12.into()); 441 | 442 | Feature { 443 | bbox: None, 444 | geometry: Some(crate::Geometry::from(crate::Value::Point(vec![1.1, 1.2]))), 445 | id: None, 446 | properties: Some(props), 447 | foreign_members: None, 448 | } 449 | }; 450 | 451 | writer.write_feature(&record_1).unwrap(); 452 | writer.flush().unwrap(); 453 | } 454 | 455 | let expected = json!({ 456 | "type": "FeatureCollection", 457 | "extra": "string", 458 | "list": [1, 2, 3], 459 | "nested": { 460 | "foo": "bar", 461 | }, 462 | "features": [ 463 | { 464 | "type": "Feature", 465 | "geometry": { "type": "Point", "coordinates": [1.1, 1.2] }, 466 | "properties": { "name": "Mishka", "age": 12 467 | } 468 | }, 469 | ] 470 | }); 471 | 472 | println!("{}", String::from_utf8(buffer.clone()).unwrap()); 473 | let actual_json: JsonValue = serde_json::from_slice(&buffer).expect("valid json"); 474 | assert_eq!(actual_json, expected) 475 | } 476 | 477 | #[cfg(feature = "geo-types")] 478 | mod test_geo_types { 479 | use super::*; 480 | use crate::ser::serialize_geometry; 481 | 482 | // an example struct that we want to serialize 483 | #[derive(Serialize)] 484 | struct MyGeoRecord { 485 | #[serde(serialize_with = "serialize_geometry")] 486 | geometry: geo_types::Point, 487 | name: String, 488 | age: u64, 489 | } 490 | 491 | #[test] 492 | fn serialize_geo_types() { 493 | let mut buffer: Vec = vec![]; 494 | { 495 | let mut writer = FeatureWriter::from_writer(&mut buffer); 496 | let record_1 = MyGeoRecord { 497 | geometry: geo_types::point!(x: 1.1, y: 1.2), 498 | name: "Mishka".to_string(), 499 | age: 12, 500 | }; 501 | let record_2 = MyGeoRecord { 502 | geometry: geo_types::point!(x: 2.1, y: 2.2), 503 | name: "Jane".to_string(), 504 | age: 22, 505 | }; 506 | writer.serialize(&record_1).unwrap(); 507 | writer.serialize(&record_2).unwrap(); 508 | writer.flush().unwrap(); 509 | } 510 | 511 | let expected = json!({ 512 | "type": "FeatureCollection", 513 | "features": [ 514 | { 515 | "type": "Feature", 516 | "geometry": { "type": "Point", "coordinates": [1.1, 1.2] }, 517 | "properties": { 518 | "name": "Mishka", 519 | "age": 12 520 | } 521 | }, 522 | { 523 | "type": "Feature", 524 | "geometry": { "type": "Point", "coordinates": [2.1, 2.2] }, 525 | "properties": { 526 | "name": "Jane", 527 | "age": 22 528 | } 529 | } 530 | ] 531 | }); 532 | 533 | let actual_json: JsonValue = serde_json::from_slice(&buffer).expect("valid json"); 534 | assert_eq!(actual_json, expected) 535 | } 536 | } 537 | } 538 | -------------------------------------------------------------------------------- /src/geojson.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The GeoRust Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::errors::{Error, Result}; 16 | use crate::{Feature, FeatureCollection, Geometry}; 17 | use crate::{JsonObject, JsonValue}; 18 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 19 | use std::convert::TryFrom; 20 | use std::fmt; 21 | use std::iter::FromIterator; 22 | use std::str::FromStr; 23 | 24 | /// GeoJSON Objects 25 | /// 26 | /// ``` 27 | /// use std::convert::TryInto; 28 | /// use geojson::{Feature, GeoJson, Geometry, Value}; 29 | /// use serde_json::json; 30 | /// let json_value = json!({ 31 | /// "type": "Feature", 32 | /// "geometry": { 33 | /// "type": "Point", 34 | /// "coordinates": [102.0, 0.5] 35 | /// }, 36 | /// "properties": null, 37 | /// }); 38 | /// let feature: Feature = json_value.try_into().unwrap(); 39 | /// 40 | /// // Easily convert a feature to a GeoJson 41 | /// let geojson: GeoJson = feature.into(); 42 | /// // and back again 43 | /// let feature2: Feature = geojson.try_into().unwrap(); 44 | /// ``` 45 | /// [GeoJSON Format Specification § 3](https://tools.ietf.org/html/rfc7946#section-3) 46 | #[derive(Clone, Debug, PartialEq)] 47 | pub enum GeoJson { 48 | Geometry(Geometry), 49 | Feature(Feature), 50 | FeatureCollection(FeatureCollection), 51 | } 52 | 53 | impl<'a> From<&'a GeoJson> for JsonObject { 54 | fn from(geojson: &'a GeoJson) -> JsonObject { 55 | match *geojson { 56 | GeoJson::Geometry(ref geometry) => geometry.into(), 57 | GeoJson::Feature(ref feature) => feature.into(), 58 | GeoJson::FeatureCollection(ref fc) => fc.into(), 59 | } 60 | } 61 | } 62 | 63 | impl From for JsonValue { 64 | fn from(geojson: GeoJson) -> JsonValue { 65 | match geojson { 66 | GeoJson::Geometry(geometry) => JsonValue::Object(JsonObject::from(&geometry)), 67 | GeoJson::Feature(feature) => JsonValue::Object(JsonObject::from(&feature)), 68 | GeoJson::FeatureCollection(fc) => JsonValue::Object(JsonObject::from(&fc)), 69 | } 70 | } 71 | } 72 | 73 | impl> From for GeoJson { 74 | fn from(geometry: G) -> Self { 75 | GeoJson::Geometry(geometry.into()) 76 | } 77 | } 78 | 79 | impl> FromIterator for GeoJson { 80 | fn from_iter>(iter: I) -> Self { 81 | use crate::Value; 82 | let geometries = iter.into_iter().map(|g| g.into()).collect(); 83 | let collection = Value::GeometryCollection(geometries); 84 | GeoJson::Geometry(Geometry::new(collection)) 85 | } 86 | } 87 | 88 | impl From for GeoJson { 89 | fn from(feature: Feature) -> Self { 90 | GeoJson::Feature(feature) 91 | } 92 | } 93 | 94 | impl From for GeoJson { 95 | fn from(feature_collection: FeatureCollection) -> GeoJson { 96 | GeoJson::FeatureCollection(feature_collection) 97 | } 98 | } 99 | 100 | impl From> for GeoJson { 101 | fn from(features: Vec) -> GeoJson { 102 | GeoJson::from(features.into_iter().collect::()) 103 | } 104 | } 105 | 106 | impl TryFrom for Geometry { 107 | type Error = Error; 108 | fn try_from(value: GeoJson) -> Result { 109 | match value { 110 | GeoJson::Geometry(g) => Ok(g), 111 | GeoJson::Feature(_) => Err(Error::ExpectedType { 112 | expected: "Geometry".to_string(), 113 | actual: "Feature".to_string(), 114 | }), 115 | GeoJson::FeatureCollection(_) => Err(Error::ExpectedType { 116 | expected: "Geometry".to_string(), 117 | actual: "FeatureCollection".to_string(), 118 | }), 119 | } 120 | } 121 | } 122 | 123 | impl TryFrom for Feature { 124 | type Error = Error; 125 | fn try_from(value: GeoJson) -> Result { 126 | match value { 127 | GeoJson::Geometry(_) => Err(Error::ExpectedType { 128 | expected: "Feature".to_string(), 129 | actual: "Geometry".to_string(), 130 | }), 131 | GeoJson::Feature(f) => Ok(f), 132 | GeoJson::FeatureCollection(_) => Err(Error::ExpectedType { 133 | expected: "Feature".to_string(), 134 | actual: "FeatureCollection".to_string(), 135 | }), 136 | } 137 | } 138 | } 139 | 140 | impl TryFrom for FeatureCollection { 141 | type Error = Error; 142 | fn try_from(value: GeoJson) -> Result { 143 | match value { 144 | GeoJson::Geometry(_) => Err(Error::ExpectedType { 145 | expected: "FeatureCollection".to_string(), 146 | actual: "Geometry".to_string(), 147 | }), 148 | GeoJson::Feature(_) => Err(Error::ExpectedType { 149 | expected: "FeatureCollection".to_string(), 150 | actual: "Feature".to_string(), 151 | }), 152 | GeoJson::FeatureCollection(f) => Ok(f), 153 | } 154 | } 155 | } 156 | 157 | impl GeoJson { 158 | pub fn from_json_object(object: JsonObject) -> Result { 159 | Self::try_from(object) 160 | } 161 | 162 | /// Converts a JSON Value into a GeoJson object. 163 | /// 164 | /// # Example 165 | /// ``` 166 | /// use std::convert::TryInto; 167 | /// use geojson::{Feature, GeoJson, Geometry, Value}; 168 | /// use serde_json::json; 169 | /// 170 | /// let json_value = json!({ 171 | /// "type": "Feature", 172 | /// "geometry": { 173 | /// "type": "Point", 174 | /// "coordinates": [102.0, 0.5] 175 | /// }, 176 | /// "properties": null, 177 | /// }); 178 | /// 179 | /// assert!(json_value.is_object()); 180 | /// 181 | /// let geojson: GeoJson = json_value.try_into().unwrap(); 182 | /// 183 | /// assert_eq!( 184 | /// geojson, 185 | /// GeoJson::Feature(Feature { 186 | /// bbox: None, 187 | /// geometry: Some(Geometry::new(Value::Point(vec![102.0, 0.5]))), 188 | /// id: None, 189 | /// properties: None, 190 | /// foreign_members: None, 191 | /// }) 192 | /// ); 193 | /// ``` 194 | pub fn from_json_value(value: JsonValue) -> Result { 195 | Self::try_from(value) 196 | } 197 | 198 | /// Convenience method to convert to a JSON Value. Uses `From`. 199 | /// ``` 200 | /// use std::convert::TryFrom; 201 | /// use geojson::GeoJson; 202 | /// use serde_json::json; 203 | /// 204 | /// let geojson = GeoJson::try_from( json!({ 205 | /// "type": "Feature", 206 | /// "geometry": { 207 | /// "type": "Point", 208 | /// "coordinates": [102.0, 0.5] 209 | /// }, 210 | /// "properties": {}, 211 | /// })).unwrap(); 212 | /// 213 | /// let json_value = geojson.to_json_value(); 214 | /// assert_eq!(json_value, 215 | /// json!({ 216 | /// "type": "Feature", 217 | /// "geometry": { 218 | /// "type": "Point", 219 | /// "coordinates": [102.0, 0.5] 220 | /// }, 221 | /// "properties": {}, 222 | /// }) 223 | /// ); 224 | /// ``` 225 | pub fn to_json_value(self) -> JsonValue { 226 | JsonValue::from(self) 227 | } 228 | 229 | // Deserialize a GeoJson object from an IO stream of JSON 230 | pub fn from_reader(rdr: R) -> serde_json::Result 231 | where 232 | R: std::io::Read, 233 | { 234 | serde_json::from_reader(rdr) 235 | } 236 | 237 | /// Convenience wrapper for [serde_json::to_string_pretty()] 238 | pub fn to_string_pretty(self) -> Result { 239 | ::serde_json::to_string_pretty(&self) 240 | .map_err(Error::MalformedJson) 241 | .map(|s| s.to_string()) 242 | } 243 | } 244 | 245 | impl TryFrom for GeoJson { 246 | type Error = Error; 247 | 248 | fn try_from(object: JsonObject) -> Result { 249 | let type_ = match object.get("type") { 250 | Some(JsonValue::String(t)) => Type::from_str(t), 251 | _ => return Err(Error::GeometryUnknownType("type".to_owned())), 252 | }; 253 | let type_ = type_.ok_or(Error::EmptyType)?; 254 | match type_ { 255 | Type::Feature => Feature::try_from(object).map(GeoJson::Feature), 256 | Type::FeatureCollection => { 257 | FeatureCollection::try_from(object).map(GeoJson::FeatureCollection) 258 | } 259 | _ => Geometry::try_from(object).map(GeoJson::Geometry), 260 | } 261 | } 262 | } 263 | 264 | impl TryFrom for GeoJson { 265 | type Error = Error; 266 | 267 | fn try_from(value: JsonValue) -> Result { 268 | if let JsonValue::Object(obj) = value { 269 | Self::try_from(obj) 270 | } else { 271 | Err(Error::GeoJsonExpectedObject(value)) 272 | } 273 | } 274 | } 275 | 276 | #[derive(PartialEq, Clone, Copy)] 277 | enum Type { 278 | Point, 279 | MultiPoint, 280 | LineString, 281 | MultiLineString, 282 | Polygon, 283 | MultiPolygon, 284 | GeometryCollection, 285 | Feature, 286 | FeatureCollection, 287 | } 288 | 289 | impl Type { 290 | fn from_str(s: &str) -> Option { 291 | match s { 292 | "Point" => Some(Type::Point), 293 | "MultiPoint" => Some(Type::MultiPoint), 294 | "LineString" => Some(Type::LineString), 295 | "MultiLineString" => Some(Type::MultiLineString), 296 | "Polygon" => Some(Type::Polygon), 297 | "MultiPolygon" => Some(Type::MultiPolygon), 298 | "GeometryCollection" => Some(Type::GeometryCollection), 299 | "Feature" => Some(Type::Feature), 300 | "FeatureCollection" => Some(Type::FeatureCollection), 301 | _ => None, 302 | } 303 | } 304 | } 305 | 306 | impl Serialize for GeoJson { 307 | fn serialize(&self, serializer: S) -> std::result::Result 308 | where 309 | S: Serializer, 310 | { 311 | match self { 312 | GeoJson::Geometry(ref geometry) => geometry.serialize(serializer), 313 | GeoJson::Feature(ref feature) => feature.serialize(serializer), 314 | GeoJson::FeatureCollection(ref fc) => fc.serialize(serializer), 315 | } 316 | } 317 | } 318 | 319 | impl<'de> Deserialize<'de> for GeoJson { 320 | fn deserialize(deserializer: D) -> std::result::Result 321 | where 322 | D: Deserializer<'de>, 323 | { 324 | use serde::de::Error as SerdeError; 325 | 326 | let val = JsonObject::deserialize(deserializer)?; 327 | 328 | GeoJson::from_json_object(val).map_err(|e| D::Error::custom(e.to_string())) 329 | } 330 | } 331 | 332 | /// # Example 333 | ///``` 334 | /// use geojson::GeoJson; 335 | /// use std::str::FromStr; 336 | /// 337 | /// let geojson_str = r#"{ 338 | /// "type": "FeatureCollection", 339 | /// "features": [ 340 | /// { 341 | /// "type": "Feature", 342 | /// "properties": {}, 343 | /// "geometry": { 344 | /// "type": "Point", 345 | /// "coordinates": [ 346 | /// -0.13583511114120483, 347 | /// 51.5218870403801 348 | /// ] 349 | /// } 350 | /// } 351 | /// ] 352 | /// } 353 | /// "#; 354 | /// let geo_json = GeoJson::from_str(&geojson_str).unwrap(); 355 | /// if let GeoJson::FeatureCollection(collection) = geo_json { 356 | /// assert_eq!(1, collection.features.len()); 357 | /// } else { 358 | /// panic!("expected feature collection"); 359 | /// } 360 | /// ``` 361 | impl FromStr for GeoJson { 362 | type Err = Error; 363 | 364 | fn from_str(s: &str) -> Result { 365 | let object = get_object(s)?; 366 | 367 | GeoJson::from_json_object(object) 368 | } 369 | } 370 | 371 | fn get_object(s: &str) -> Result { 372 | match ::serde_json::from_str(s)? { 373 | JsonValue::Object(object) => Ok(object), 374 | other => Err(Error::ExpectedObjectValue(other)), 375 | } 376 | } 377 | 378 | impl fmt::Display for GeoJson { 379 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 380 | ::serde_json::to_string(self) 381 | .map_err(|_| fmt::Error) 382 | .and_then(|s| f.write_str(&s)) 383 | } 384 | } 385 | 386 | impl fmt::Display for Feature { 387 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 388 | ::serde_json::to_string(self) 389 | .map_err(|_| fmt::Error) 390 | .and_then(|s| f.write_str(&s)) 391 | } 392 | } 393 | 394 | impl fmt::Display for Geometry { 395 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 396 | ::serde_json::to_string(self) 397 | .map_err(|_| fmt::Error) 398 | .and_then(|s| f.write_str(&s)) 399 | } 400 | } 401 | 402 | impl fmt::Display for FeatureCollection { 403 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 404 | ::serde_json::to_string(self) 405 | .map_err(|_| fmt::Error) 406 | .and_then(|s| f.write_str(&s)) 407 | } 408 | } 409 | 410 | #[cfg(test)] 411 | mod tests { 412 | use crate::{Error, Feature, FeatureCollection, GeoJson, Geometry, Value}; 413 | use serde_json::json; 414 | use std::convert::TryInto; 415 | use std::str::FromStr; 416 | 417 | #[test] 418 | fn test_geojson_from_reader() { 419 | let json_str = r#"{ 420 | "type": "Feature", 421 | "geometry": { 422 | "type": "Point", 423 | "coordinates": [102.0, 0.5] 424 | }, 425 | "properties": null 426 | }"#; 427 | 428 | let g1 = GeoJson::from_reader(json_str.as_bytes()).unwrap(); 429 | 430 | let json_value = json!({ 431 | "type": "Feature", 432 | "geometry": { 433 | "type": "Point", 434 | "coordinates": [102.0, 0.5] 435 | }, 436 | "properties": null, 437 | }); 438 | 439 | let g2: GeoJson = json_value.try_into().unwrap(); 440 | 441 | assert_eq!(g1, g2); 442 | } 443 | 444 | #[test] 445 | fn test_geojson_from_value() { 446 | let json_value = json!({ 447 | "type": "Feature", 448 | "geometry": { 449 | "type": "Point", 450 | "coordinates": [102.0, 0.5] 451 | }, 452 | "properties": null, 453 | }); 454 | 455 | assert!(json_value.is_object()); 456 | 457 | let geojson: GeoJson = json_value.try_into().unwrap(); 458 | 459 | assert_eq!( 460 | geojson, 461 | GeoJson::Feature(Feature { 462 | bbox: None, 463 | geometry: Some(Geometry::new(Value::Point(vec![102.0, 0.5]))), 464 | id: None, 465 | properties: None, 466 | foreign_members: None, 467 | }) 468 | ); 469 | } 470 | 471 | #[test] 472 | fn test_geojson_from_features() { 473 | let features: Vec = vec![ 474 | Value::Point(vec![0., 0., 0.]).into(), 475 | Value::Point(vec![1., 1., 1.]).into(), 476 | ]; 477 | 478 | let geojson: GeoJson = features.into(); 479 | assert_eq!( 480 | geojson, 481 | GeoJson::FeatureCollection(FeatureCollection { 482 | features: vec![ 483 | Feature { 484 | bbox: None, 485 | geometry: Some(Geometry::new(Value::Point(vec![0., 0., 0.]))), 486 | id: None, 487 | properties: None, 488 | foreign_members: None, 489 | }, 490 | Feature { 491 | bbox: None, 492 | geometry: Some(Geometry::new(Value::Point(vec![1., 1., 1.]))), 493 | id: None, 494 | properties: None, 495 | foreign_members: None, 496 | }, 497 | ], 498 | bbox: None, 499 | foreign_members: None, 500 | }) 501 | ); 502 | } 503 | 504 | #[test] 505 | fn test_missing_properties_key() { 506 | let json_value = json!({ 507 | "type": "Feature", 508 | "geometry": { 509 | "type": "Point", 510 | "coordinates": [102.0, 0.5] 511 | }, 512 | }); 513 | 514 | assert!(json_value.is_object()); 515 | 516 | let geojson: GeoJson = json_value.try_into().unwrap(); 517 | assert_eq!( 518 | geojson, 519 | GeoJson::Feature(Feature { 520 | bbox: None, 521 | geometry: Some(Geometry::new(Value::Point(vec![102.0, 0.5]))), 522 | id: None, 523 | properties: None, 524 | foreign_members: None, 525 | }) 526 | ); 527 | } 528 | 529 | #[test] 530 | fn test_invalid_json() { 531 | let geojson_str = r#"{ 532 | "type": "FeatureCollection", 533 | "features": [ 534 | !INTENTIONAL_TYPO! { 535 | "type": "Feature", 536 | "properties": {}, 537 | "geometry": { 538 | "type": "Point", 539 | "coordinates": [ 540 | -0.13583511114120483, 541 | 51.5218870403801 542 | ] 543 | } 544 | } 545 | ] 546 | }"#; 547 | assert!(matches!( 548 | GeoJson::from_str(geojson_str), 549 | Err(Error::MalformedJson(_)) 550 | )) 551 | } 552 | } 553 | -------------------------------------------------------------------------------- /src/geometry.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The GeoRust Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::str::FromStr; 16 | use std::{convert::TryFrom, fmt}; 17 | 18 | use crate::errors::{Error, Result}; 19 | use crate::{util, Bbox, LineStringType, PointType, PolygonType}; 20 | use crate::{JsonObject, JsonValue}; 21 | use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; 22 | 23 | /// The underlying value for a `Geometry`. 24 | /// 25 | /// # Conversion from `geo_types` 26 | /// 27 | /// A `Value` can be created by using the `From` impl which is available for both `geo_types` 28 | /// primitives AND `geo_types::Geometry` enum members: 29 | /// 30 | /// ```rust 31 | /// # #[cfg(feature = "geo-types")] 32 | /// # fn test() { 33 | /// let point = geo_types::Point::new(2., 9.); 34 | /// let genum = geo_types::Geometry::from(point); 35 | /// assert_eq!( 36 | /// geojson::Value::from(&point), 37 | /// geojson::Value::Point(vec![2., 9.]), 38 | /// ); 39 | /// assert_eq!( 40 | /// geojson::Value::from(&genum), 41 | /// geojson::Value::Point(vec![2., 9.]), 42 | /// ); 43 | /// # } 44 | /// # #[cfg(not(feature = "geo-types"))] 45 | /// # fn test() {} 46 | /// # test() 47 | /// ``` 48 | #[derive(Clone, Debug, PartialEq)] 49 | pub enum Value { 50 | /// Point 51 | /// 52 | /// [GeoJSON Format Specification § 3.1.2](https://tools.ietf.org/html/rfc7946#section-3.1.2) 53 | Point(PointType), 54 | 55 | /// MultiPoint 56 | /// 57 | /// [GeoJSON Format Specification § 3.1.3](https://tools.ietf.org/html/rfc7946#section-3.1.3) 58 | MultiPoint(Vec), 59 | 60 | /// LineString 61 | /// 62 | /// [GeoJSON Format Specification § 3.1.4](https://tools.ietf.org/html/rfc7946#section-3.1.4) 63 | LineString(LineStringType), 64 | 65 | /// MultiLineString 66 | /// 67 | /// [GeoJSON Format Specification § 3.1.5](https://tools.ietf.org/html/rfc7946#section-3.1.5) 68 | MultiLineString(Vec), 69 | 70 | /// Polygon 71 | /// 72 | /// [GeoJSON Format Specification § 3.1.6](https://tools.ietf.org/html/rfc7946#section-3.1.6) 73 | Polygon(PolygonType), 74 | 75 | /// MultiPolygon 76 | /// 77 | /// [GeoJSON Format Specification § 3.1.7](https://tools.ietf.org/html/rfc7946#section-3.1.7) 78 | MultiPolygon(Vec), 79 | 80 | /// GeometryCollection 81 | /// 82 | /// [GeoJSON Format Specification § 3.1.8](https://tools.ietf.org/html/rfc7946#section-3.1.8) 83 | GeometryCollection(Vec), 84 | } 85 | 86 | impl Value { 87 | pub fn type_name(&self) -> &'static str { 88 | match self { 89 | Value::Point(..) => "Point", 90 | Value::MultiPoint(..) => "MultiPoint", 91 | Value::LineString(..) => "LineString", 92 | Value::MultiLineString(..) => "MultiLineString", 93 | Value::Polygon(..) => "Polygon", 94 | Value::MultiPolygon(..) => "MultiPolygon", 95 | Value::GeometryCollection(..) => "GeometryCollection", 96 | } 97 | } 98 | } 99 | 100 | impl<'a> From<&'a Value> for JsonObject { 101 | fn from(value: &'a Value) -> JsonObject { 102 | let mut map = JsonObject::new(); 103 | map.insert( 104 | String::from("type"), 105 | // The unwrap() should never panic, because &str always serializes to JSON 106 | ::serde_json::to_value(value.type_name()).unwrap(), 107 | ); 108 | map.insert( 109 | String::from(match value { 110 | Value::GeometryCollection(..) => "geometries", 111 | _ => "coordinates", 112 | }), 113 | // The unwrap() should never panic, because Value contains only JSON-serializable types 114 | ::serde_json::to_value(value).unwrap(), 115 | ); 116 | map 117 | } 118 | } 119 | 120 | impl Value { 121 | pub fn from_json_object(object: JsonObject) -> Result { 122 | Self::try_from(object) 123 | } 124 | 125 | pub fn from_json_value(value: JsonValue) -> Result { 126 | Self::try_from(value) 127 | } 128 | 129 | fn serialize_to_map( 130 | &self, 131 | map: &mut SM, 132 | ) -> std::result::Result<(), SM::Error> { 133 | map.serialize_entry("type", self.type_name())?; 134 | map.serialize_entry( 135 | match self { 136 | Value::GeometryCollection(..) => "geometries", 137 | _ => "coordinates", 138 | }, 139 | self, 140 | )?; 141 | Ok(()) 142 | } 143 | } 144 | 145 | impl TryFrom for Value { 146 | type Error = Error; 147 | 148 | fn try_from(mut object: JsonObject) -> Result { 149 | util::get_value(&mut object) 150 | } 151 | } 152 | 153 | impl TryFrom for Value { 154 | type Error = Error; 155 | 156 | fn try_from(value: JsonValue) -> Result { 157 | if let JsonValue::Object(obj) = value { 158 | Self::try_from(obj) 159 | } else { 160 | Err(Error::GeoJsonExpectedObject(value)) 161 | } 162 | } 163 | } 164 | 165 | impl fmt::Display for Value { 166 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 167 | ::serde_json::to_string(&JsonObject::from(self)) 168 | .map_err(|_| fmt::Error) 169 | .and_then(|s| f.write_str(&s)) 170 | } 171 | } 172 | 173 | impl<'a> From<&'a Value> for JsonValue { 174 | fn from(value: &'a Value) -> JsonValue { 175 | ::serde_json::to_value(value).unwrap() 176 | } 177 | } 178 | 179 | impl Serialize for Value { 180 | fn serialize(&self, serializer: S) -> std::result::Result 181 | where 182 | S: Serializer, 183 | { 184 | match self { 185 | Value::Point(x) => x.serialize(serializer), 186 | Value::MultiPoint(x) => x.serialize(serializer), 187 | Value::LineString(x) => x.serialize(serializer), 188 | Value::MultiLineString(x) => x.serialize(serializer), 189 | Value::Polygon(x) => x.serialize(serializer), 190 | Value::MultiPolygon(x) => x.serialize(serializer), 191 | Value::GeometryCollection(x) => x.serialize(serializer), 192 | } 193 | } 194 | } 195 | 196 | /// Geometry Objects 197 | /// 198 | /// [GeoJSON Format Specification § 3.1](https://tools.ietf.org/html/rfc7946#section-3.1) 199 | /// 200 | /// ## Examples 201 | /// 202 | /// Constructing a `Geometry`: 203 | /// 204 | /// ``` 205 | /// use geojson::{Geometry, Value}; 206 | /// 207 | /// let geometry = Geometry::new(Value::Point(vec![7.428959, 1.513394])); 208 | /// ``` 209 | /// 210 | /// Geometries can be created from `Value`s. 211 | /// ``` 212 | /// # use geojson::{Geometry, Value}; 213 | /// let geometry1: Geometry = Value::Point(vec![7.428959, 1.513394]).into(); 214 | /// ``` 215 | /// 216 | /// Serializing a `Geometry` to a GeoJSON string: 217 | /// 218 | /// ``` 219 | /// use geojson::{GeoJson, Geometry, Value}; 220 | /// use serde_json; 221 | /// 222 | /// let geometry = Geometry::new(Value::Point(vec![7.428959, 1.513394])); 223 | /// 224 | /// let geojson_string = geometry.to_string(); 225 | /// 226 | /// assert_eq!( 227 | /// "{\"type\":\"Point\",\"coordinates\":[7.428959,1.513394]}", 228 | /// geojson_string, 229 | /// ); 230 | /// ``` 231 | /// 232 | /// Deserializing a GeoJSON string into a `Geometry`: 233 | /// 234 | /// ``` 235 | /// use geojson::{GeoJson, Geometry, Value}; 236 | /// 237 | /// let geojson_str = "{\"coordinates\":[7.428959,1.513394],\"type\":\"Point\"}"; 238 | /// 239 | /// let geometry = match geojson_str.parse::() { 240 | /// Ok(GeoJson::Geometry(g)) => g, 241 | /// _ => return, 242 | /// }; 243 | /// 244 | /// assert_eq!( 245 | /// Geometry::new(Value::Point(vec![7.428959, 1.513394]),), 246 | /// geometry, 247 | /// ); 248 | /// ``` 249 | /// 250 | /// Transforming a `Geometry` into a `geo_types::Geometry` (which requires the `geo-types` 251 | /// feature): 252 | /// 253 | /// ``` 254 | /// use geojson::{Geometry, Value}; 255 | /// use std::convert::TryInto; 256 | /// 257 | /// let geometry = Geometry::new(Value::Point(vec![7.428959, 1.513394])); 258 | /// # #[cfg(feature = "geo-types")] 259 | /// let geom: geo_types::Geometry = geometry.try_into().unwrap(); 260 | /// ``` 261 | #[derive(Clone, Debug, PartialEq)] 262 | pub struct Geometry { 263 | /// Bounding Box 264 | /// 265 | /// [GeoJSON Format Specification § 5](https://tools.ietf.org/html/rfc7946#section-5) 266 | pub bbox: Option, 267 | pub value: Value, 268 | /// Foreign Members 269 | /// 270 | /// [GeoJSON Format Specification § 6](https://tools.ietf.org/html/rfc7946#section-6) 271 | pub foreign_members: Option, 272 | } 273 | 274 | impl Geometry { 275 | /// Returns a new `Geometry` with the specified `value`. `bbox` and `foreign_members` will be 276 | /// set to `None`. 277 | pub fn new(value: Value) -> Self { 278 | Geometry { 279 | bbox: None, 280 | value, 281 | foreign_members: None, 282 | } 283 | } 284 | } 285 | 286 | impl<'a> From<&'a Geometry> for JsonObject { 287 | fn from(geometry: &'a Geometry) -> JsonObject { 288 | let mut map = JsonObject::from(&geometry.value); 289 | if let Some(ref bbox) = geometry.bbox { 290 | map.insert(String::from("bbox"), ::serde_json::to_value(bbox).unwrap()); 291 | } 292 | 293 | if let Some(ref foreign_members) = geometry.foreign_members { 294 | for (key, value) in foreign_members { 295 | map.insert(key.to_owned(), value.to_owned()); 296 | } 297 | } 298 | map 299 | } 300 | } 301 | 302 | impl Geometry { 303 | pub fn from_json_object(object: JsonObject) -> Result { 304 | Self::try_from(object) 305 | } 306 | 307 | pub fn from_json_value(value: JsonValue) -> Result { 308 | Self::try_from(value) 309 | } 310 | 311 | fn serialize_to_map( 312 | &self, 313 | map: &mut SM, 314 | ) -> std::result::Result<(), SM::Error> { 315 | self.value.serialize_to_map(map)?; 316 | if let Some(ref bbox) = self.bbox { 317 | map.serialize_entry("bbox", bbox)?; 318 | } 319 | 320 | if let Some(ref foreign_members) = self.foreign_members { 321 | for (key, value) in foreign_members { 322 | map.serialize_entry(key, value)? 323 | } 324 | } 325 | Ok(()) 326 | } 327 | } 328 | 329 | impl TryFrom for Geometry { 330 | type Error = Error; 331 | 332 | fn try_from(mut object: JsonObject) -> Result { 333 | let bbox = util::get_bbox(&mut object)?; 334 | let value = util::get_value(&mut object)?; 335 | let foreign_members = util::get_foreign_members(object)?; 336 | Ok(Geometry { 337 | bbox, 338 | value, 339 | foreign_members, 340 | }) 341 | } 342 | } 343 | 344 | impl TryFrom for Geometry { 345 | type Error = Error; 346 | 347 | fn try_from(value: JsonValue) -> Result { 348 | if let JsonValue::Object(obj) = value { 349 | Self::try_from(obj) 350 | } else { 351 | Err(Error::GeoJsonExpectedObject(value)) 352 | } 353 | } 354 | } 355 | 356 | impl FromStr for Geometry { 357 | type Err = Error; 358 | 359 | fn from_str(s: &str) -> Result { 360 | Self::try_from(crate::GeoJson::from_str(s)?) 361 | } 362 | } 363 | 364 | impl Serialize for Geometry { 365 | fn serialize(&self, serializer: S) -> std::result::Result 366 | where 367 | S: Serializer, 368 | { 369 | let mut map = serializer.serialize_map(None)?; 370 | self.serialize_to_map(&mut map)?; 371 | map.end() 372 | } 373 | } 374 | 375 | impl<'de> Deserialize<'de> for Geometry { 376 | fn deserialize(deserializer: D) -> std::result::Result 377 | where 378 | D: Deserializer<'de>, 379 | { 380 | use serde::de::Error as SerdeError; 381 | 382 | let val = JsonObject::deserialize(deserializer)?; 383 | 384 | Geometry::from_json_object(val).map_err(|e| D::Error::custom(e.to_string())) 385 | } 386 | } 387 | 388 | impl From for Geometry 389 | where 390 | V: Into, 391 | { 392 | fn from(v: V) -> Geometry { 393 | Geometry::new(v.into()) 394 | } 395 | } 396 | 397 | #[cfg(test)] 398 | mod tests { 399 | use crate::{Error, GeoJson, Geometry, JsonObject, Value}; 400 | use serde_json::json; 401 | use std::str::FromStr; 402 | 403 | fn encode(geometry: &Geometry) -> String { 404 | serde_json::to_string(&geometry).unwrap() 405 | } 406 | fn decode(json_string: String) -> GeoJson { 407 | json_string.parse().unwrap() 408 | } 409 | 410 | #[test] 411 | fn encode_decode_geometry() { 412 | let geometry_json_str = "{\"type\":\"Point\",\"coordinates\":[1.1,2.1]}"; 413 | let geometry = Geometry { 414 | value: Value::Point(vec![1.1, 2.1]), 415 | bbox: None, 416 | foreign_members: None, 417 | }; 418 | 419 | // Test encode 420 | let json_string = encode(&geometry); 421 | assert_eq!(json_string, geometry_json_str); 422 | 423 | // Test decode 424 | let decoded_geometry = match decode(json_string) { 425 | GeoJson::Geometry(g) => g, 426 | _ => unreachable!(), 427 | }; 428 | assert_eq!(decoded_geometry, geometry); 429 | } 430 | 431 | #[test] 432 | fn test_geometry_from_value() { 433 | use serde_json::json; 434 | use std::convert::TryInto; 435 | 436 | let json_value = json!({ 437 | "type": "Point", 438 | "coordinates": [ 439 | 0.0, 0.1 440 | ], 441 | }); 442 | assert!(json_value.is_object()); 443 | 444 | let geometry: Geometry = json_value.try_into().unwrap(); 445 | assert_eq!( 446 | geometry, 447 | Geometry { 448 | value: Value::Point(vec![0.0, 0.1]), 449 | bbox: None, 450 | foreign_members: None, 451 | } 452 | ) 453 | } 454 | 455 | #[test] 456 | fn test_geometry_display() { 457 | let v = Value::LineString(vec![vec![0.0, 0.1], vec![0.1, 0.2], vec![0.2, 0.3]]); 458 | let geometry = Geometry::new(v); 459 | assert_eq!( 460 | geometry.to_string(), 461 | "{\"type\":\"LineString\",\"coordinates\":[[0.0,0.1],[0.1,0.2],[0.2,0.3]]}" 462 | ); 463 | } 464 | 465 | #[test] 466 | fn test_value_display() { 467 | let v = Value::LineString(vec![vec![0.0, 0.1], vec![0.1, 0.2], vec![0.2, 0.3]]); 468 | assert_eq!( 469 | "{\"coordinates\":[[0.0,0.1],[0.1,0.2],[0.2,0.3]],\"type\":\"LineString\"}", 470 | v.to_string() 471 | ); 472 | } 473 | 474 | #[test] 475 | fn encode_decode_geometry_with_foreign_member() { 476 | let geometry_json_str = 477 | "{\"type\":\"Point\",\"coordinates\":[1.1,2.1],\"other_member\":true}"; 478 | let mut foreign_members = JsonObject::new(); 479 | foreign_members.insert( 480 | String::from("other_member"), 481 | serde_json::to_value(true).unwrap(), 482 | ); 483 | let geometry = Geometry { 484 | value: Value::Point(vec![1.1, 2.1]), 485 | bbox: None, 486 | foreign_members: Some(foreign_members), 487 | }; 488 | 489 | // Test encode 490 | let json_string = encode(&geometry); 491 | assert_eq!(json_string, geometry_json_str); 492 | 493 | // Test decode 494 | let decoded_geometry = match decode(geometry_json_str.into()) { 495 | GeoJson::Geometry(g) => g, 496 | _ => unreachable!(), 497 | }; 498 | assert_eq!(decoded_geometry, geometry); 499 | } 500 | 501 | #[test] 502 | fn encode_decode_geometry_collection() { 503 | let geometry_collection = Geometry { 504 | bbox: None, 505 | value: Value::GeometryCollection(vec![ 506 | Geometry { 507 | bbox: None, 508 | value: Value::Point(vec![100.0, 0.0]), 509 | foreign_members: None, 510 | }, 511 | Geometry { 512 | bbox: None, 513 | value: Value::LineString(vec![vec![101.0, 0.0], vec![102.0, 1.0]]), 514 | foreign_members: None, 515 | }, 516 | ]), 517 | foreign_members: None, 518 | }; 519 | 520 | let geometry_collection_string = "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[100.0,0.0]},{\"type\":\"LineString\",\"coordinates\":[[101.0,0.0],[102.0,1.0]]}]}"; 521 | // Test encode 522 | let json_string = encode(&geometry_collection); 523 | assert_eq!(json_string, geometry_collection_string); 524 | 525 | // Test decode 526 | let decoded_geometry = match decode(geometry_collection_string.into()) { 527 | GeoJson::Geometry(g) => g, 528 | _ => unreachable!(), 529 | }; 530 | assert_eq!(decoded_geometry, geometry_collection); 531 | } 532 | 533 | #[test] 534 | fn test_from_str_ok() { 535 | let geometry_json = json!({ 536 | "type": "Point", 537 | "coordinates": [125.6f64, 10.1] 538 | }) 539 | .to_string(); 540 | 541 | let geometry = Geometry::from_str(&geometry_json).unwrap(); 542 | assert!(matches!(geometry.value, Value::Point(_))); 543 | } 544 | 545 | #[test] 546 | fn test_from_str_with_unexpected_type() { 547 | let feature_json = json!({ 548 | "type": "Feature", 549 | "geometry": { 550 | "type": "Point", 551 | "coordinates": [125.6, 10.1] 552 | }, 553 | "properties": { 554 | "name": "Dinagat Islands" 555 | } 556 | }) 557 | .to_string(); 558 | 559 | let actual_failure = Geometry::from_str(&feature_json).unwrap_err(); 560 | match actual_failure { 561 | Error::ExpectedType { actual, expected } => { 562 | assert_eq!(actual, "Feature"); 563 | assert_eq!(expected, "Geometry"); 564 | } 565 | e => panic!("unexpected error: {}", e), 566 | }; 567 | } 568 | 569 | #[test] 570 | fn test_reject_too_few_coordinates() { 571 | let err = Geometry::from_str(r#"{"type": "Point", "coordinates": []}"#).unwrap_err(); 572 | assert_eq!( 573 | err.to_string(), 574 | "A position must contain two or more elements, but got `0`" 575 | ); 576 | 577 | let err = Geometry::from_str(r#"{"type": "Point", "coordinates": [23.42]}"#).unwrap_err(); 578 | assert_eq!( 579 | err.to_string(), 580 | "A position must contain two or more elements, but got `1`" 581 | ); 582 | } 583 | } 584 | -------------------------------------------------------------------------------- /src/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::().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::().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 = geo_types::Point::new(2., 9.); 258 | //! let geo_geometry: geo_types::Geometry = 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 = 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 = 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` 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::::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, 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) 376 | pub type Bbox = Vec; 377 | 378 | /// Positions 379 | /// 380 | /// [GeoJSON Format Specification § 3.1.1](https://tools.ietf.org/html/rfc7946#section-3.1.1) 381 | pub type Position = Vec; 382 | 383 | pub type PointType = Position; 384 | pub type LineStringType = Vec; 385 | pub type PolygonType = Vec>; 386 | 387 | mod util; 388 | 389 | mod geojson; 390 | pub use crate::geojson::GeoJson; 391 | 392 | mod geometry; 393 | pub use crate::geometry::{Geometry, Value}; 394 | 395 | pub mod feature; 396 | 397 | mod feature_collection; 398 | pub use crate::feature_collection::FeatureCollection; 399 | 400 | mod feature_iterator; 401 | #[allow(deprecated)] 402 | #[doc(hidden)] 403 | pub use crate::feature_iterator::FeatureIterator; 404 | 405 | pub mod errors; 406 | pub use crate::errors::{Error, Result}; 407 | 408 | #[cfg(feature = "geo-types")] 409 | mod conversion; 410 | 411 | /// Build your struct from GeoJSON using [`serde`] 412 | pub mod de; 413 | 414 | /// Write your struct to GeoJSON using [`serde`] 415 | pub mod ser; 416 | 417 | mod feature_reader; 418 | pub use feature_reader::FeatureReader; 419 | 420 | mod feature_writer; 421 | pub use feature_writer::FeatureWriter; 422 | 423 | #[allow(deprecated)] 424 | #[cfg(feature = "geo-types")] 425 | pub 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)] 431 | pub struct Feature { 432 | /// Bounding Box 433 | /// 434 | /// [GeoJSON Format Specification § 5](https://tools.ietf.org/html/rfc7946#section-5) 435 | pub bbox: Option, 436 | /// Geometry 437 | /// 438 | /// [GeoJSON Format Specification § 3.2](https://tools.ietf.org/html/rfc7946#section-3.2) 439 | pub geometry: Option, 440 | /// Identifier 441 | /// 442 | /// [GeoJSON Format Specification § 3.2](https://tools.ietf.org/html/rfc7946#section-3.2) 443 | pub id: Option, 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, 452 | /// Foreign Members 453 | /// 454 | /// [GeoJSON Format Specification § 6](https://tools.ietf.org/html/rfc7946#section-6) 455 | pub foreign_members: Option, 456 | } 457 | 458 | pub type JsonValue = serde_json::Value; 459 | pub type JsonObject = serde_json::Map; 460 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The GeoRust Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use crate::errors::{Error, Result}; 16 | use crate::{feature, Bbox, Feature, Geometry, Position, Value}; 17 | use crate::{JsonObject, JsonValue}; 18 | 19 | pub fn expect_type(value: &mut JsonObject) -> Result { 20 | let prop = expect_property(value, "type")?; 21 | expect_string(prop) 22 | } 23 | 24 | pub fn expect_string(value: JsonValue) -> Result { 25 | match value { 26 | JsonValue::String(s) => Ok(s), 27 | _ => Err(Error::ExpectedStringValue(value)), 28 | } 29 | } 30 | 31 | pub fn expect_f64(value: &JsonValue) -> Result { 32 | match value.as_f64() { 33 | Some(v) => Ok(v), 34 | None => Err(Error::ExpectedF64Value), 35 | } 36 | } 37 | 38 | pub fn expect_array(value: &JsonValue) -> Result<&Vec> { 39 | match value.as_array() { 40 | Some(v) => Ok(v), 41 | None => Err(Error::ExpectedArrayValue("None".to_string())), 42 | } 43 | } 44 | 45 | fn expect_property(obj: &mut JsonObject, name: &'static str) -> Result { 46 | match obj.remove(name) { 47 | Some(v) => Ok(v), 48 | None => Err(Error::ExpectedProperty(name.to_string())), 49 | } 50 | } 51 | 52 | fn expect_owned_array(value: JsonValue) -> Result> { 53 | match value { 54 | JsonValue::Array(v) => Ok(v), 55 | _ => match value { 56 | // it can never be Array, but that's exhaustive matches for you 57 | JsonValue::Array(_) => Err(Error::ExpectedArrayValue("Array".to_string())), 58 | JsonValue::Null => Err(Error::ExpectedArrayValue("Null".to_string())), 59 | JsonValue::Bool(_) => Err(Error::ExpectedArrayValue("Bool".to_string())), 60 | JsonValue::Number(_) => Err(Error::ExpectedArrayValue("Number".to_string())), 61 | JsonValue::String(_) => Err(Error::ExpectedArrayValue("String".to_string())), 62 | JsonValue::Object(_) => Err(Error::ExpectedArrayValue("Object".to_string())), 63 | }, 64 | } 65 | } 66 | 67 | pub(crate) fn expect_owned_object(value: JsonValue) -> Result { 68 | match value { 69 | JsonValue::Object(o) => Ok(o), 70 | _ => Err(Error::ExpectedObjectValue(value)), 71 | } 72 | } 73 | 74 | pub fn get_coords_value(object: &mut JsonObject) -> Result { 75 | expect_property(object, "coordinates") 76 | } 77 | 78 | /// Used by FeatureCollection, Feature, Geometry 79 | pub fn get_bbox(object: &mut JsonObject) -> Result> { 80 | let bbox_json = match object.remove("bbox") { 81 | Some(JsonValue::Null) | None => return Ok(None), 82 | Some(b) => b, 83 | }; 84 | let bbox_array = match bbox_json { 85 | JsonValue::Array(a) => a, 86 | _ => return Err(Error::BboxExpectedArray(bbox_json)), 87 | }; 88 | let bbox = bbox_array 89 | .into_iter() 90 | .map(|i| i.as_f64().ok_or(Error::BboxExpectedNumericValues(i))) 91 | .collect::>>()?; 92 | Ok(Some(bbox)) 93 | } 94 | 95 | /// Used by FeatureCollection, Feature, Geometry 96 | pub fn get_foreign_members(object: JsonObject) -> Result> { 97 | if object.is_empty() { 98 | Ok(None) 99 | } else { 100 | Ok(Some(object)) 101 | } 102 | } 103 | 104 | /// Used by Feature 105 | pub fn get_properties(object: &mut JsonObject) -> Result> { 106 | let properties = expect_property(object, "properties"); 107 | match properties { 108 | Ok(JsonValue::Object(x)) => Ok(Some(x)), 109 | Ok(JsonValue::Null) | Err(Error::ExpectedProperty(_)) => Ok(None), 110 | Ok(not_a_dictionary) => Err(Error::PropertiesExpectedObjectOrNull(not_a_dictionary)), 111 | Err(e) => Err(e), 112 | } 113 | } 114 | 115 | /// Retrieve a single Position from the value of the "coordinates" key 116 | /// 117 | /// Used by Value::Point 118 | pub fn get_coords_one_pos(object: &mut JsonObject) -> Result { 119 | let coords_json = get_coords_value(object)?; 120 | json_to_position(&coords_json) 121 | } 122 | 123 | /// Retrieve a one dimensional Vec of Positions from the value of the "coordinates" key 124 | /// 125 | /// Used by Value::MultiPoint and Value::LineString 126 | pub fn get_coords_1d_pos(object: &mut JsonObject) -> Result> { 127 | let coords_json = get_coords_value(object)?; 128 | json_to_1d_positions(&coords_json) 129 | } 130 | 131 | /// Retrieve a two dimensional Vec of Positions from the value of the "coordinates" key 132 | /// 133 | /// Used by Value::MultiLineString and Value::Polygon 134 | pub fn get_coords_2d_pos(object: &mut JsonObject) -> Result>> { 135 | let coords_json = get_coords_value(object)?; 136 | json_to_2d_positions(&coords_json) 137 | } 138 | 139 | /// Retrieve a three dimensional Vec of Positions from the value of the "coordinates" key 140 | /// 141 | /// Used by Value::MultiPolygon 142 | pub fn get_coords_3d_pos(object: &mut JsonObject) -> Result>>> { 143 | let coords_json = get_coords_value(object)?; 144 | json_to_3d_positions(&coords_json) 145 | } 146 | 147 | /// Used by Value::GeometryCollection 148 | pub fn get_geometries(object: &mut JsonObject) -> Result> { 149 | let geometries_json = expect_property(object, "geometries")?; 150 | let geometries_array = expect_owned_array(geometries_json)?; 151 | let mut geometries = Vec::with_capacity(geometries_array.len()); 152 | for json in geometries_array { 153 | let obj = expect_owned_object(json)?; 154 | let geometry = Geometry::from_json_object(obj)?; 155 | geometries.push(geometry); 156 | } 157 | Ok(geometries) 158 | } 159 | 160 | /// Used by Feature 161 | pub fn get_id(object: &mut JsonObject) -> Result> { 162 | match object.remove("id") { 163 | Some(JsonValue::Number(x)) => Ok(Some(feature::Id::Number(x))), 164 | Some(JsonValue::String(s)) => Ok(Some(feature::Id::String(s))), 165 | Some(v) => Err(Error::FeatureInvalidIdentifierType(v)), 166 | None => Ok(None), 167 | } 168 | } 169 | 170 | /// Used by Geometry, Value 171 | pub fn get_value(object: &mut JsonObject) -> Result { 172 | let res = &*expect_type(object)?; 173 | match res { 174 | "Point" => Ok(Value::Point(get_coords_one_pos(object)?)), 175 | "MultiPoint" => Ok(Value::MultiPoint(get_coords_1d_pos(object)?)), 176 | "LineString" => Ok(Value::LineString(get_coords_1d_pos(object)?)), 177 | "MultiLineString" => Ok(Value::MultiLineString(get_coords_2d_pos(object)?)), 178 | "Polygon" => Ok(Value::Polygon(get_coords_2d_pos(object)?)), 179 | "MultiPolygon" => Ok(Value::MultiPolygon(get_coords_3d_pos(object)?)), 180 | "GeometryCollection" => Ok(Value::GeometryCollection(get_geometries(object)?)), 181 | _ => Err(Error::GeometryUnknownType(res.to_string())), 182 | } 183 | } 184 | 185 | /// Used by Feature 186 | pub fn get_geometry(object: &mut JsonObject) -> Result> { 187 | let geometry = expect_property(object, "geometry")?; 188 | match geometry { 189 | JsonValue::Object(x) => { 190 | let geometry_object = Geometry::from_json_object(x)?; 191 | Ok(Some(geometry_object)) 192 | } 193 | JsonValue::Null => Ok(None), 194 | _ => Err(Error::FeatureInvalidGeometryValue(geometry)), 195 | } 196 | } 197 | 198 | /// Used by FeatureCollection 199 | pub fn get_features(object: &mut JsonObject) -> Result> { 200 | let prop = expect_property(object, "features")?; 201 | let features_json = expect_owned_array(prop)?; 202 | let mut features = Vec::with_capacity(features_json.len()); 203 | for feature in features_json { 204 | let feature = expect_owned_object(feature)?; 205 | let feature: Feature = Feature::from_json_object(feature)?; 206 | features.push(feature); 207 | } 208 | Ok(features) 209 | } 210 | 211 | fn json_to_position(json: &JsonValue) -> Result { 212 | let coords_array = expect_array(json)?; 213 | if coords_array.len() < 2 { 214 | return Err(Error::PositionTooShort(coords_array.len())); 215 | } 216 | let mut coords = Vec::with_capacity(coords_array.len()); 217 | for position in coords_array { 218 | coords.push(expect_f64(position)?); 219 | } 220 | Ok(coords) 221 | } 222 | 223 | fn json_to_1d_positions(json: &JsonValue) -> Result> { 224 | let coords_array = expect_array(json)?; 225 | let mut coords = Vec::with_capacity(coords_array.len()); 226 | for item in coords_array { 227 | coords.push(json_to_position(item)?); 228 | } 229 | Ok(coords) 230 | } 231 | 232 | fn json_to_2d_positions(json: &JsonValue) -> Result>> { 233 | let coords_array = expect_array(json)?; 234 | let mut coords = Vec::with_capacity(coords_array.len()); 235 | for item in coords_array { 236 | coords.push(json_to_1d_positions(item)?); 237 | } 238 | Ok(coords) 239 | } 240 | 241 | fn json_to_3d_positions(json: &JsonValue) -> Result>>> { 242 | let coords_array = expect_array(json)?; 243 | let mut coords = Vec::with_capacity(coords_array.len()); 244 | for item in coords_array { 245 | coords.push(json_to_2d_positions(item)?); 246 | } 247 | Ok(coords) 248 | } 249 | -------------------------------------------------------------------------------- /tests/fixtures/README.md: -------------------------------------------------------------------------------- 1 | This directory contains a set of interesting geojson files intended to 2 | cover a wide breadth of parsing behaviors. 3 | 4 | Those in the `canonical` directory represent valid canonical geojson 5 | files, meaning they can be parsed and rewritten without losing any data. 6 | 7 | Some of the test geojson files were lifted from MapBox's geojsonhint project (ISC LICENSE) 8 | 9 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-feature-with-id.geojson: -------------------------------------------------------------------------------- 1 | { "type": "Feature", 2 | "id": 100, 3 | "geometry": {"type": "Point", "coordinates": [102.0, 0.5]}, 4 | "properties": {"prop0": "value0"} 5 | } 6 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-feature-with-string-id.geojson: -------------------------------------------------------------------------------- 1 | { "type": "Feature", 2 | "id": "myfeature", 3 | "geometry": {"type": "Point", "coordinates": [102.0, 0.5]}, 4 | "properties": {"prop0": "value0"} 5 | } 6 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-feature.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "bbox": [102.0, 0.5, 102.0, 0.5], 4 | "geometry": {"type": "Point", "coordinates": [102.0, 0.5]}, 5 | "properties": {"prop0": "value0"} 6 | } 7 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-featurecollection-bbox.geojson: -------------------------------------------------------------------------------- 1 | { "type": "FeatureCollection", 2 | "bbox": [100.0, 0.5, 102.0, 2.5], 3 | "features": [ 4 | { "type": "Feature", 5 | "geometry": {"type": "Point", "coordinates": [102.0, 0.5]}, 6 | "properties": {"prop0": "value0"} 7 | }, 8 | { "type": "Feature", 9 | "geometry": {"type": "Point", "coordinates": [100.0, 2.5]}, 10 | "properties": {"prop0": "value0"} 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-featurecollection-bbox3d.geojson: -------------------------------------------------------------------------------- 1 | { "type": "FeatureCollection", 2 | "bbox": [100.0, 0.5, 15.0, 102.0, 2.5, 25.0], 3 | "features": [ 4 | { "type": "Feature", 5 | "geometry": {"type": "Point", "coordinates": [102.0, 0.5, 15.0]}, 6 | "properties": {"prop0": "value0"} 7 | }, 8 | { "type": "Feature", 9 | "geometry": {"type": "Point", "coordinates": [100.0, 2.5, 25.0]}, 10 | "properties": {"prop0": "value0"} 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-featurecollection-extensions.geojson: -------------------------------------------------------------------------------- 1 | { "type": "FeatureCollection", 2 | "custom": true, 3 | "features": [ 4 | { "type": "Feature", 5 | "geometry": { 6 | "type": "Point", 7 | "custom": true, 8 | "coordinates": [102.0, 0.5]}, 9 | "properties": {"prop0": "value0"}, 10 | "custom": true 11 | }, 12 | { "type": "Feature", 13 | "geometry": { 14 | "type": "LineString", 15 | "custom": true, 16 | "coordinates": [ 17 | [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0] 18 | ] 19 | }, 20 | "properties": { 21 | "prop0": "value0", 22 | "prop1": 0.0 23 | } 24 | }, 25 | { "type": "Feature", 26 | "geometry": { 27 | "type": "Polygon", 28 | "custom": true, 29 | "coordinates": [ 30 | [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], 31 | [100.0, 1.0], [100.0, 0.0] ] 32 | ] 33 | }, 34 | "properties": { 35 | "prop0": "value0", 36 | "prop1": {"this": "that"} 37 | } 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-featurecollection.geojson: -------------------------------------------------------------------------------- 1 | { "type": "FeatureCollection", 2 | "features": [ 3 | { "type": "Feature", 4 | "geometry": {"type": "Point", "coordinates": [102.0, 0.5]}, 5 | "properties": {"prop0": "value0"} 6 | }, 7 | { "type": "Feature", 8 | "geometry": { 9 | "type": "LineString", 10 | "coordinates": [ 11 | [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0] 12 | ] 13 | }, 14 | "properties": { 15 | "prop0": "value0", 16 | "prop1": 0.0 17 | } 18 | }, 19 | { "type": "Feature", 20 | "geometry": { 21 | "type": "Polygon", 22 | "coordinates": [ 23 | [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] 24 | ] 25 | }, 26 | "properties": { 27 | "prop0": "value0", 28 | "prop1": {"this": "that"} 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-geometrycollection.geojson: -------------------------------------------------------------------------------- 1 | { "type": "GeometryCollection", 2 | "geometries": [ 3 | { "type": "Point", 4 | "coordinates": [100.0, 0.0] 5 | }, 6 | { "type": "LineString", 7 | "coordinates": [ [101.0, 0.0], [102.0, 1.0] ] 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-linestring.geojson: -------------------------------------------------------------------------------- 1 | { "type": "LineString", 2 | "coordinates": [ [100.0, 0.0], [101.0, 1.0] ] 3 | } 4 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-multilinestring.geojson: -------------------------------------------------------------------------------- 1 | { "type": "MultiLineString", 2 | "coordinates": [ 3 | [ [100.0, 0.0], [101.0, 1.0] ], 4 | [ [102.0, 2.0], [103.0, 3.0] ] 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-multipoint.geojson: -------------------------------------------------------------------------------- 1 | { "type": "MultiPoint", "coordinates": [[100.0, 0.0]] } 2 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-point-3d.geojson: -------------------------------------------------------------------------------- 1 | { "type": "Point", "coordinates": [100.0, 0.0, 15.0] } 2 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-point.geojson: -------------------------------------------------------------------------------- 1 | { "type": "Point", "coordinates": [100.0, 0.0] } 2 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/good-polygon.geojson: -------------------------------------------------------------------------------- 1 | { "type": "Polygon", 2 | "coordinates": [ 3 | [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/multipolygon.geojson: -------------------------------------------------------------------------------- 1 | { "type": "MultiPolygon", 2 | "coordinates": [ 3 | [[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], 4 | [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], 5 | [[100.2, 0.2], [100.2, 0.8], [100.8, 0.8], [100.8, 0.2], [100.2, 0.2]]] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tests/fixtures/canonical/nullgeometry.geojson: -------------------------------------------------------------------------------- 1 | { "type": "FeatureCollection", "features": [{ 2 | "type": "Feature", 3 | "properties": {}, 4 | "geometry": null 5 | }] } 6 | -------------------------------------------------------------------------------- /tests/fixtures/geometry_collection.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "GeometryCollection", 3 | "geometries": [ 4 | { 5 | "type": "Point", 6 | "coordinates": [100.0, 0.0] 7 | }, 8 | { 9 | "type": "LineString", 10 | "coordinates": [ 11 | [101.0, 0.0], [102.0, 1.0] 12 | ] 13 | }, 14 | { 15 | "type": "Point", 16 | "coordinates": [100.0, 0.0] 17 | }, 18 | { 19 | "type": "LineString", 20 | "coordinates": [ 21 | [101.0, 0.0], [102.0, 1.0] 22 | ] 23 | }, 24 | { 25 | "type": "Point", 26 | "coordinates": [100.0, 0.0] 27 | }, 28 | { 29 | "type": "LineString", 30 | "coordinates": [ 31 | [101.0, 0.0], [102.0, 1.0] 32 | ] 33 | }, 34 | { 35 | "type": "Point", 36 | "coordinates": [100.0, 0.0] 37 | }, 38 | { 39 | "type": "LineString", 40 | "coordinates": [ 41 | [101.0, 0.0], [102.0, 1.0] 42 | ] 43 | }, 44 | { 45 | "type": "Point", 46 | "coordinates": [100.0, 0.0] 47 | }, 48 | { 49 | "type": "LineString", 50 | "coordinates": [ 51 | [101.0, 0.0], [102.0, 1.0] 52 | ] 53 | }, 54 | { 55 | "type": "Point", 56 | "coordinates": [100.0, 0.0] 57 | }, 58 | { 59 | "type": "LineString", 60 | "coordinates": [ 61 | [101.0, 0.0], [102.0, 1.0] 62 | ] 63 | }, 64 | { 65 | "type": "Point", 66 | "coordinates": [100.0, 0.0] 67 | }, 68 | { 69 | "type": "LineString", 70 | "coordinates": [ 71 | [101.0, 0.0], [102.0, 1.0] 72 | ] 73 | }, 74 | { 75 | "type": "Point", 76 | "coordinates": [100.0, 0.0] 77 | }, 78 | { 79 | "type": "LineString", 80 | "coordinates": [ 81 | [101.0, 0.0], [102.0, 1.0] 82 | ] 83 | }, 84 | { 85 | "type": "Point", 86 | "coordinates": [100.0, 0.0] 87 | }, 88 | { 89 | "type": "LineString", 90 | "coordinates": [ 91 | [101.0, 0.0], [102.0, 1.0] 92 | ] 93 | }, 94 | { 95 | "type": "Point", 96 | "coordinates": [100.0, 0.0] 97 | }, 98 | { 99 | "type": "LineString", 100 | "coordinates": [ 101 | [101.0, 0.0], [102.0, 1.0] 102 | ] 103 | }, 104 | { 105 | "type": "Point", 106 | "coordinates": [100.0, 0.0] 107 | }, 108 | { 109 | "type": "LineString", 110 | "coordinates": [ 111 | [101.0, 0.0], [102.0, 1.0] 112 | ] 113 | }, 114 | { 115 | "type": "Point", 116 | "coordinates": [100.0, 0.0] 117 | }, 118 | { 119 | "type": "LineString", 120 | "coordinates": [ 121 | [101.0, 0.0], [102.0, 1.0] 122 | ] 123 | }, 124 | { 125 | "type": "Point", 126 | "coordinates": [100.0, 0.0] 127 | }, 128 | { 129 | "type": "LineString", 130 | "coordinates": [ 131 | [101.0, 0.0], [102.0, 1.0] 132 | ] 133 | }, 134 | { 135 | "type": "Point", 136 | "coordinates": [100.0, 0.0] 137 | }, 138 | { 139 | "type": "LineString", 140 | "coordinates": [ 141 | [101.0, 0.0], [102.0, 1.0] 142 | ] 143 | }, 144 | { 145 | "type": "Point", 146 | "coordinates": [100.0, 0.0] 147 | }, 148 | { 149 | "type": "LineString", 150 | "coordinates": [ 151 | [101.0, 0.0], [102.0, 1.0] 152 | ] 153 | }, 154 | { 155 | "type": "Point", 156 | "coordinates": [100.0, 0.0] 157 | }, 158 | { 159 | "type": "LineString", 160 | "coordinates": [ 161 | [101.0, 0.0], [102.0, 1.0] 162 | ] 163 | } 164 | ] 165 | } -------------------------------------------------------------------------------- /tests/roundtrip.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod roundtrip_tests { 3 | use geojson::GeoJson; 4 | use std::fs::File; 5 | use std::io::prelude::*; 6 | 7 | macro_rules! roundtrip_test { 8 | ($name:ident : $file_name:expr) => { 9 | #[test] 10 | fn $name() { 11 | let fixture_dir_path = "tests/fixtures/canonical/"; 12 | let mut file_path = fixture_dir_path.to_owned(); 13 | file_path.push_str($file_name.to_owned().as_str()); 14 | 15 | test_round_trip(&file_path); 16 | } 17 | }; 18 | } 19 | 20 | macro_rules! roundtrip_tests { 21 | ( $($name:ident: $file_name:expr,)* ) => { 22 | $( 23 | roundtrip_test!($name: $file_name); 24 | )* 25 | } 26 | } 27 | 28 | roundtrip_tests! { 29 | test_good_feature_with_id: "good-feature-with-id.geojson", 30 | test_good_feature_with_string_id: "good-feature-with-string-id.geojson", 31 | test_good_feature: "good-feature.geojson", 32 | test_good_feature_collection_bbox: "good-featurecollection-bbox.geojson", 33 | test_good_feature_collection_bbox3d: "good-featurecollection-bbox3d.geojson", 34 | test_good_feature_collection_extensions: "good-featurecollection-extensions.geojson", 35 | test_good_feature_collection: "good-featurecollection.geojson", 36 | test_good_geometry_collection: "good-geometrycollection.geojson", 37 | test_good_linestring: "good-linestring.geojson", 38 | test_good_multilinestring: "good-multilinestring.geojson", 39 | test_good_multipoint: "good-multipoint.geojson", 40 | test_good_point_3d: "good-point-3d.geojson", 41 | test_good_point: "good-point.geojson", 42 | test_good_polygon: "good-polygon.geojson", 43 | test_multipolygon: "multipolygon.geojson", 44 | test_null_geometry: "nullgeometry.geojson", 45 | } 46 | 47 | /// Verifies that we can parse and then re-encode geojson back to the same representation 48 | /// without losing any data. 49 | fn test_round_trip(file_path: &str) { 50 | let mut file = File::open(file_path).unwrap(); 51 | let mut file_contents = String::new(); 52 | let _ = file.read_to_string(&mut file_contents); 53 | 54 | // Read and parse the geojson from the file's contents 55 | let geojson = file_contents.parse::().expect("unable to parse"); 56 | 57 | // Now that we've successfully decoded the geojson, re-encode it and compare to the 58 | // original to make sure nothing was lost. 59 | let geojson_string = geojson.to_string(); 60 | 61 | let original_json: serde_json::Value = serde_json::from_str(&file_contents).unwrap(); 62 | let roundtrip_json: serde_json::Value = serde_json::from_str(&geojson_string).unwrap(); 63 | 64 | assert_eq!(original_json, roundtrip_json) 65 | } 66 | } 67 | --------------------------------------------------------------------------------