├── .github └── workflows │ └── CI.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE.md ├── README.md ├── examples ├── create.rs ├── geotype-example │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ └── main.rs │ └── tests │ │ └── data │ │ ├── points.cpg │ │ ├── points.dbf │ │ ├── points.prj │ │ ├── points.shp │ │ ├── points.shx │ │ ├── polygons.cpg │ │ ├── polygons.dbf │ │ ├── polygons.prj │ │ ├── polygons.shp │ │ └── polygons.shx └── print-content.rs ├── src ├── geo_traits_impl.rs ├── header.rs ├── lib.rs ├── reader.rs ├── record │ ├── bbox.rs │ ├── io.rs │ ├── macros.rs │ ├── mod.rs │ ├── multipatch.rs │ ├── multipoint.rs │ ├── point.rs │ ├── polygon.rs │ ├── polyline.rs │ └── traits.rs └── writer.rs └── tests ├── data ├── line.shp ├── line.shx ├── linem.shp ├── linez.shp ├── multi_polygon.shp ├── multipatch.dbf ├── multipatch.shp ├── multipoint.shp ├── multipointz.shp ├── point.shp ├── point.shx ├── pointm.shp ├── pointz.shp ├── polygon.shp ├── polygon_hole.shp ├── polygon_hole.shx ├── polygonm.shp └── polygonz.shp ├── read_tests.rs ├── read_with_index.rs ├── testfiles.rs └── write_tests.rs /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | features: ["", "--features geo-types", "--features yore", "--features encoding_rs", "--all-features"] 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Install latest stable 18 | uses: dtolnay/rust-toolchain@888c2e1ea69ab0d4330cbf0af1ecc7b68f368cc1 19 | with: 20 | toolchain: stable 21 | 22 | - name: check fmt 23 | run: cargo fmt --check 24 | 25 | - name: clippy ${{ matrix.features }} 26 | run: cargo clippy ${{ matrix.features }} 27 | 28 | 29 | test: 30 | name: Test Suite 31 | needs: [check] 32 | runs-on: ubuntu-latest 33 | 34 | strategy: 35 | matrix: 36 | features: [ "", "--features geo-types", "--features yore", "--features encoding_rs", "--all-features" ] 37 | 38 | steps: 39 | - uses: actions/checkout@v4 40 | 41 | - name: Install latest stable 42 | uses: dtolnay/rust-toolchain@888c2e1ea69ab0d4330cbf0af1ecc7b68f368cc1 43 | with: 44 | toolchain: stable 45 | 46 | - name: test ${{ matrix.features }} 47 | run: cargo test ${{ matrix.features }} 48 | 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | .idea/ 6 | 7 | \.vs/ 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.7.0 2 | - Bumped dbase to 0.6 3 | - Added `yore` and `encoding_rs` features which are forwarded to `dbase` 4 | allowing to read dbf files with special encodings. 5 | - Added `finalize` method to writer, to be able to explicitly handle errors when the file 6 | is finalized (instead of silently ignoring errors by relying on the drop mechanism) 7 | - Fixed overflow that could happen on large files 8 | - Fixes performance issue for shapefiles which had a .shx index file 9 | 10 | # 0.6.0 11 | - Bumped dbase to 0.5.0 12 | 13 | # 0.5.0 14 | - Bumped dbase to 0.4.0 15 | 16 | # 0.4.0 17 | - Added `shape_count` to the reader 18 | - Bumped dbase to 0.3.0 to bring code page support 19 | - Fixed: Use the .shx (index file, if present) when iterating over the shapes 20 | contained in the file, as some files may have padding bytes between shapes. 21 | - Changed, the `Reader::with_shx` now can work when the source for the shx file 22 | can is a different type than the shp source type (can mix io::Cursor and fs::File for example). 23 | - Changed the `Reader` to be able to use different type for the sources of the dbase and shape file 24 | (e.g. dbase source could be a fs::File whil the shape is a io::Cursor) 25 | 26 | # 0.3.0 27 | - Updated dbase dependency to 0.2.x 28 | - Added `Writer::write_shape` to write one shape at a time 29 | - Changed `Write` the `T` now must implement `std::io::Seek` and `std::io::Write`. 30 | `std::fs::File` and `std::io::Cursor` are valid `T`. 31 | - Changed `ShapeWriter::write_shapes` to take as input any type that implements 32 | `IntoIterator`. 33 | - Fixed `ShapeType::Multipatch` wasn't considered as a type with Z coordinates. 34 | - Added a `ShapeReader` &`ShapeWriter` struct that only read/write the .shp and .shx 35 | - Changed the `Reader`, it now requires the .dbf to exist 36 | - Changed the `Writer`, it requires more information to be able to write the .dbf file 37 | (Examples are in the docs) 38 | - Changed the `Reader` `iter_*` & `read` to take `&mut self` instead of `self` 39 | - Changed `shapefile::read` now returns a `Vec<(Shape, Record)>` 40 | `shapefile::read_shapes` returns `Vec` 41 | 42 | # 0.2.2 43 | - Bumped geo-types optional dependency to allow up to 0.8.0 44 | 45 | # 0.1.1 46 | - Fixed a problem in the Multipatch/Polygon/Polyline::with_parts ctor which resulted in 47 | wrong parts creation (Github PR #10) 48 | - Fixed another index file bug (Github PR #8) 49 | - Fixed a bug in the Polygon::with_parts that would result in inner ring points 50 | being reordered to outer ring point order (Github PR #12) 51 | - Added #[derive(Debug, PartialEq, Clone)] for Polylines, Polygons, Multipoints 52 | 53 | # 0.1.0 54 | 55 | - Fix index file (.shx) that was incorrect (Github issue #6) 56 | - Fix reading PointZ shape where the 'M' value is not there at all 57 | - PointM, PointZ 'std::fmt::Display' implementation now prints 'NO_DATA' 58 | when the value is NO_DATA (instead of printing the f64 value) 59 | - Implement MultipointShape for Point, PointM, PointZ 60 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shapefile" 3 | version = "0.7.0" 4 | authors = ["tmontaigu "] 5 | description = "Read & Write shapefiles in Rust" 6 | license = "MIT" 7 | readme = "README.md" 8 | keywords = ["shapefile"] 9 | homepage = "https://github.com/tmontaigu/shapefile-rs" 10 | repository = "https://github.com/tmontaigu/shapefile-rs" 11 | exclude = ["tests/data/*"] 12 | edition = "2021" 13 | 14 | [dependencies] 15 | byteorder = "1.2.7" 16 | dbase = "0.6.0" 17 | geo-types = { version = ">=0.4.0, <0.8.0", optional = true } 18 | geo-traits = { version = "0.2", optional = true } 19 | 20 | [features] 21 | encoding_rs = ["dbase/encoding_rs"] 22 | yore = ["dbase/yore"] 23 | 24 | 25 | [package.metadata.docs.rs] 26 | features = ["geo-types", "geo-traits"] 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Thomas Montaigu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shapefile-rs 2 | Rust library to read & write shapefiles 3 | .dbf files supported via the [dbase](https://crates.io/crates/dbase) crate 4 | 5 | ```rust 6 | let mut reader = shapefile::Reader::from_path(filename).unwrap(); 7 | 8 | for result in reader.iter_shapes_and_records() { 9 | let (shape, record) = result.unwrap(); 10 | println ! ("Shape: {}, records: ", shape); 11 | for (name, value) in record { 12 | println ! ("\t{}: {:?}, ", name, value); 13 | } 14 | println ! (); 15 | } 16 | ``` 17 | You can check out examples in the [examples](https://github.com/tmontaigu/shapefile-rs/tree/master/examples/) folder 18 | 19 | -------------------------------------------------------------------------------- /examples/create.rs: -------------------------------------------------------------------------------- 1 | use dbase::{dbase_record, TableWriterBuilder}; 2 | use shapefile::{dbase, Point, Polyline, Writer}; 3 | 4 | fn example_1() { 5 | const FILE_NAME: &str = "hello_shape_1.shp"; 6 | 7 | let square = Polyline::new(vec![ 8 | Point::new(0.0, 0.0), 9 | Point::new(0.0, 1.0), 10 | Point::new(1.0, 1.0), 11 | Point::new(1.0, 0.0), 12 | ]); 13 | 14 | let bigger_square = Polyline::new(vec![ 15 | Point::new(0.0, 0.0), 16 | Point::new(0.0, 10.0), 17 | Point::new(10.0, 10.0), 18 | Point::new(10.0, 0.0), 19 | ]); 20 | 21 | // Create the builder for the accompanying dbase (.dbf) file 22 | // For this simple example we will only have a single field 23 | // "Name", with 55 chars max 24 | let table_builder = 25 | TableWriterBuilder::new().add_character_field("Name".try_into().unwrap(), 55); 26 | 27 | { 28 | let mut writer = Writer::from_path(FILE_NAME, table_builder) 29 | .expect("Failed to create the shapefile writer"); 30 | 31 | let mut first_record = dbase::Record::default(); 32 | first_record.insert("Name".to_string(), "Square".to_string().into()); 33 | writer 34 | .write_shape_and_record(&square, &first_record) 35 | .expect("Failed to write first record"); 36 | 37 | let mut second_record = dbase::Record::default(); 38 | second_record.insert("Name".to_string(), "Big Square".to_string().into()); 39 | writer 40 | .write_shape_and_record(&bigger_square, &second_record) 41 | .expect("Failed to write second record"); 42 | } 43 | 44 | println!("File created, you can use `cargo run --example print-content {FILE_NAME}`"); 45 | } 46 | 47 | fn example_2() { 48 | dbase_record!( 49 | #[derive(Debug)] 50 | struct UserRecord { 51 | first_name: String, 52 | last_name: String, 53 | } 54 | ); 55 | 56 | const FILE_NAME: &str = "hello_shape_2.shp"; 57 | 58 | let square = Polyline::new(vec![ 59 | Point::new(0.0, 0.0), 60 | Point::new(0.0, 1.0), 61 | Point::new(1.0, 1.0), 62 | Point::new(1.0, 0.0), 63 | ]); 64 | 65 | let bigger_square = Polyline::new(vec![ 66 | Point::new(0.0, 0.0), 67 | Point::new(0.0, 10.0), 68 | Point::new(10.0, 10.0), 69 | Point::new(10.0, 0.0), 70 | ]); 71 | 72 | // Create the builder for the accompanying dbase (.dbf) file 73 | let table_builder = TableWriterBuilder::new() 74 | .add_character_field("FirstName".try_into().unwrap(), 55) 75 | .add_character_field("LastName".try_into().unwrap(), 55); 76 | 77 | { 78 | let mut writer = Writer::from_path(FILE_NAME, table_builder) 79 | .expect("Failed to create the shapefile writer"); 80 | 81 | let first_record = UserRecord { 82 | first_name: "Yoshi".to_string(), 83 | last_name: "Green".to_string(), 84 | }; 85 | 86 | writer 87 | .write_shape_and_record(&square, &first_record) 88 | .expect("Failed to write first record"); 89 | 90 | let second_record = UserRecord { 91 | first_name: "Yoshi".to_string(), 92 | last_name: "Red".to_string(), 93 | }; 94 | writer 95 | .write_shape_and_record(&bigger_square, &second_record) 96 | .expect("Failed to write second record"); 97 | } 98 | 99 | println!("File created, you can use `cargo run --example print-content {FILE_NAME}`"); 100 | } 101 | 102 | fn main() { 103 | example_1(); 104 | example_2(); 105 | } 106 | -------------------------------------------------------------------------------- /examples/geotype-example/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /examples/geotype-example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "read-polygon-shapefile-and-convert-to-geo-types" 3 | version = "0.1.0" 4 | authors = ["Michel Kuhlmann "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | shapefile = {version = "0.3.0", features = ["geo-types"]} 11 | geo = "0.18.0" 12 | -------------------------------------------------------------------------------- /examples/geotype-example/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Example how to read a shapefile, convert it to a `geo` data structure and check for polygon-point intersection 3 | --- 4 | 5 | The example program in `./src/main.rs` 6 | 7 | 1. reads the polygons in `./tests/data/polygons.shp` (ESRI Shapefile) and the points in `./tests/data/points.shp` (ESRI Shapefile) 8 | 2. Converts them to the `geo` data-structure 9 | 3. Checks which polygons contain which points and 10 | 4. Emit a message with the corresponding feature-ids. 11 | 12 | **Requirements** 13 | 14 | Enable shapefiles `geo-types`-feature 15 | 16 | `Cargo.toml` 17 | 18 | ```toml 19 | [dependencies] 20 | shapefile = {version = "0.3.0", features = ["geo-types"]} 21 | ... 22 | ``` 23 | -------------------------------------------------------------------------------- /examples/geotype-example/src/main.rs: -------------------------------------------------------------------------------- 1 | use geo::prelude::Contains; 2 | // shapefile re-exports dbase so you can use it 3 | use shapefile::dbase; 4 | 5 | 6 | fn main() { 7 | let polygons = shapefile::read_as::<_, shapefile::Polygon, shapefile::dbase::Record>( 8 | "./tests/data/polygons.shp", 9 | ) 10 | .expect("Could not open polygon-shapefile"); 11 | let points = shapefile::read_as::<_, shapefile::Point, shapefile::dbase::Record>( 12 | "./tests/data/points.shp", 13 | ) 14 | .expect("Could not open point shapefiles"); 15 | for (polygon, polygon_record) in polygons { 16 | let geo_polygon: geo::MultiPolygon = polygon.into(); 17 | for (point, point_record) in points.iter() { 18 | let geo_point: geo::Point = point.clone().into(); 19 | if geo_polygon.contains(&geo_point) { 20 | let point_id = match point_record.get("id") { 21 | Some(dbase::FieldValue::Numeric(Some(x))) => x, 22 | Some(_) => panic!("Expected 'id' to be a numeric in point-dataset"), 23 | None => panic!("Field 'id' is not within point-dataset"), 24 | }; 25 | let polygon_id = match polygon_record.get("id") { 26 | Some(dbase::FieldValue::Numeric(Some(x))) => x, 27 | Some(_) => panic!("Expected 'id' to be a numeric in polygon-dataset"), 28 | None => panic!("Field 'id' is not within polygon-dataset"), 29 | }; 30 | println!( 31 | "Point with id {} is within polygon with id {}", 32 | point_id, polygon_id 33 | ); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/geotype-example/tests/data/points.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /examples/geotype-example/tests/data/points.dbf: -------------------------------------------------------------------------------- 1 | yaidN 2 | valueN  1 2.500 2 3.000 3 3.000 7 5.000 4 6.000 5 8.000 6 9.000 -------------------------------------------------------------------------------- /examples/geotype-example/tests/data/points.prj: -------------------------------------------------------------------------------- 1 | PROJCS["CH1903+_LV95",GEOGCS["GCS_CH1903+",DATUM["D_CH1903+",SPHEROID["Bessel_1841",6377397.155,299.1528128]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Hotine_Oblique_Mercator_Azimuth_Center"],PARAMETER["False_Easting",2600000.0],PARAMETER["False_Northing",1200000.0],PARAMETER["Scale_Factor",1.0],PARAMETER["Azimuth",90.0],PARAMETER["Longitude_Of_Center",7.43958333333333],PARAMETER["Latitude_Of_Center",46.9524055555556],UNIT["Meter",1.0]] -------------------------------------------------------------------------------- /examples/geotype-example/tests/data/points.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/examples/geotype-example/tests/data/points.shp -------------------------------------------------------------------------------- /examples/geotype-example/tests/data/points.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/examples/geotype-example/tests/data/points.shx -------------------------------------------------------------------------------- /examples/geotype-example/tests/data/polygons.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /examples/geotype-example/tests/data/polygons.dbf: -------------------------------------------------------------------------------- 1 | ya[idN 2 | TypeCP 1Forest 2Settlement  -------------------------------------------------------------------------------- /examples/geotype-example/tests/data/polygons.prj: -------------------------------------------------------------------------------- 1 | PROJCS["CH1903+_LV95",GEOGCS["GCS_CH1903+",DATUM["D_CH1903+",SPHEROID["Bessel_1841",6377397.155,299.1528128]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Hotine_Oblique_Mercator_Azimuth_Center"],PARAMETER["False_Easting",2600000.0],PARAMETER["False_Northing",1200000.0],PARAMETER["Scale_Factor",1.0],PARAMETER["Azimuth",90.0],PARAMETER["Longitude_Of_Center",7.43958333333333],PARAMETER["Latitude_Of_Center",46.9524055555556],UNIT["Meter",1.0]] -------------------------------------------------------------------------------- /examples/geotype-example/tests/data/polygons.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/examples/geotype-example/tests/data/polygons.shp -------------------------------------------------------------------------------- /examples/geotype-example/tests/data/polygons.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/examples/geotype-example/tests/data/polygons.shx -------------------------------------------------------------------------------- /examples/print-content.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::process::exit; 3 | 4 | fn main() { 5 | let args: Vec = env::args().collect(); 6 | let filename = match args.get(1) { 7 | Some(arg) => arg, 8 | None => { 9 | println!("Expected a path to a file as first argument."); 10 | exit(-1); 11 | } 12 | }; 13 | 14 | let mut reader = shapefile::Reader::from_path(filename).unwrap(); 15 | 16 | for result in reader.iter_shapes_and_records() { 17 | let (shape, record) = result.unwrap(); 18 | println!("Shape: {}, records: ", shape); 19 | for (name, value) in record { 20 | println!("\t{}: {:?}, ", name, value); 21 | } 22 | println!(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/geo_traits_impl.rs: -------------------------------------------------------------------------------- 1 | use std::hint::unreachable_unchecked; 2 | 3 | use crate::{ 4 | Multipoint, MultipointM, MultipointZ, Point, PointM, PointZ, PolygonRing, Polyline, PolylineM, 5 | PolylineZ, NO_DATA, 6 | }; 7 | use geo_traits::{ 8 | CoordTrait, LineStringTrait, MultiLineStringTrait, MultiPointTrait, MultiPolygonTrait, 9 | PointTrait, PolygonTrait, 10 | }; 11 | 12 | // Shapefile points can't be null, so we implement both traits on them 13 | impl CoordTrait for Point { 14 | type T = f64; 15 | 16 | fn dim(&self) -> geo_traits::Dimensions { 17 | geo_traits::Dimensions::Xy 18 | } 19 | 20 | fn nth_or_panic(&self, n: usize) -> Self::T { 21 | match n { 22 | 0 => self.x(), 23 | 1 => self.y(), 24 | _ => panic!("invalid dimension index"), 25 | } 26 | } 27 | 28 | unsafe fn nth_unchecked(&self, n: usize) -> Self::T { 29 | match n { 30 | 0 => self.x(), 31 | 1 => self.y(), 32 | _ => unreachable_unchecked(), 33 | } 34 | } 35 | 36 | fn x(&self) -> Self::T { 37 | self.x 38 | } 39 | 40 | fn y(&self) -> Self::T { 41 | self.y 42 | } 43 | } 44 | 45 | impl CoordTrait for &Point { 46 | type T = f64; 47 | 48 | fn dim(&self) -> geo_traits::Dimensions { 49 | geo_traits::Dimensions::Xy 50 | } 51 | 52 | fn nth_or_panic(&self, n: usize) -> Self::T { 53 | match n { 54 | 0 => self.x(), 55 | 1 => self.y(), 56 | _ => panic!("invalid dimension index"), 57 | } 58 | } 59 | 60 | unsafe fn nth_unchecked(&self, n: usize) -> Self::T { 61 | match n { 62 | 0 => self.x(), 63 | 1 => self.y(), 64 | _ => unreachable_unchecked(), 65 | } 66 | } 67 | 68 | fn x(&self) -> Self::T { 69 | self.x 70 | } 71 | 72 | fn y(&self) -> Self::T { 73 | self.y 74 | } 75 | } 76 | 77 | impl PointTrait for Point { 78 | type T = f64; 79 | type CoordType<'a> 80 | = &'a Point 81 | where 82 | Self: 'a; 83 | 84 | fn dim(&self) -> geo_traits::Dimensions { 85 | geo_traits::Dimensions::Xy 86 | } 87 | 88 | fn coord(&self) -> Option> { 89 | Some(self) 90 | } 91 | } 92 | 93 | impl PointTrait for &Point { 94 | type T = f64; 95 | type CoordType<'a> 96 | = &'a Point 97 | where 98 | Self: 'a; 99 | 100 | fn dim(&self) -> geo_traits::Dimensions { 101 | geo_traits::Dimensions::Xy 102 | } 103 | 104 | fn coord(&self) -> Option> { 105 | Some(self) 106 | } 107 | } 108 | 109 | // Shapefile points can't be null, so we implement both traits on them 110 | impl CoordTrait for PointM { 111 | type T = f64; 112 | 113 | fn dim(&self) -> geo_traits::Dimensions { 114 | if self.m <= NO_DATA { 115 | geo_traits::Dimensions::Xy 116 | } else { 117 | geo_traits::Dimensions::Xym 118 | } 119 | } 120 | 121 | fn nth_or_panic(&self, n: usize) -> Self::T { 122 | match n { 123 | 0 => self.x(), 124 | 1 => self.y(), 125 | 2 => self.m, 126 | _ => panic!("invalid dimension index"), 127 | } 128 | } 129 | 130 | fn x(&self) -> Self::T { 131 | self.x 132 | } 133 | 134 | fn y(&self) -> Self::T { 135 | self.y 136 | } 137 | } 138 | 139 | impl CoordTrait for &PointM { 140 | type T = f64; 141 | 142 | fn dim(&self) -> geo_traits::Dimensions { 143 | if self.m <= NO_DATA { 144 | geo_traits::Dimensions::Xy 145 | } else { 146 | geo_traits::Dimensions::Xym 147 | } 148 | } 149 | 150 | fn nth_or_panic(&self, n: usize) -> Self::T { 151 | match n { 152 | 0 => self.x(), 153 | 1 => self.y(), 154 | 2 => self.m, 155 | _ => panic!("invalid dimension index"), 156 | } 157 | } 158 | 159 | fn x(&self) -> Self::T { 160 | self.x 161 | } 162 | 163 | fn y(&self) -> Self::T { 164 | self.y 165 | } 166 | } 167 | 168 | impl PointTrait for PointM { 169 | type T = f64; 170 | type CoordType<'a> 171 | = &'a PointM 172 | where 173 | Self: 'a; 174 | 175 | fn dim(&self) -> geo_traits::Dimensions { 176 | if self.m <= NO_DATA { 177 | geo_traits::Dimensions::Xy 178 | } else { 179 | geo_traits::Dimensions::Xym 180 | } 181 | } 182 | 183 | fn coord(&self) -> Option> { 184 | Some(self) 185 | } 186 | } 187 | 188 | impl PointTrait for &PointM { 189 | type T = f64; 190 | type CoordType<'a> 191 | = &'a PointM 192 | where 193 | Self: 'a; 194 | 195 | fn dim(&self) -> geo_traits::Dimensions { 196 | if self.m <= NO_DATA { 197 | geo_traits::Dimensions::Xy 198 | } else { 199 | geo_traits::Dimensions::Xym 200 | } 201 | } 202 | 203 | fn coord(&self) -> Option> { 204 | Some(self) 205 | } 206 | } 207 | 208 | // Shapefile points can't be null, so we implement both traits on them 209 | impl CoordTrait for PointZ { 210 | type T = f64; 211 | 212 | fn dim(&self) -> geo_traits::Dimensions { 213 | if self.m <= NO_DATA { 214 | geo_traits::Dimensions::Xyz 215 | } else { 216 | geo_traits::Dimensions::Xyzm 217 | } 218 | } 219 | 220 | fn nth_or_panic(&self, n: usize) -> Self::T { 221 | match n { 222 | 0 => self.x(), 223 | 1 => self.y(), 224 | 2 => self.z, 225 | 3 => { 226 | if self.m > NO_DATA { 227 | self.m 228 | } else { 229 | panic!("asked for 4th item from coordinate but this coordinate does not have 4 dimensions.") 230 | } 231 | } 232 | _ => panic!("invalid dimension index"), 233 | } 234 | } 235 | 236 | fn x(&self) -> Self::T { 237 | self.x 238 | } 239 | 240 | fn y(&self) -> Self::T { 241 | self.y 242 | } 243 | } 244 | 245 | impl CoordTrait for &PointZ { 246 | type T = f64; 247 | 248 | fn dim(&self) -> geo_traits::Dimensions { 249 | if self.m <= NO_DATA { 250 | geo_traits::Dimensions::Xyz 251 | } else { 252 | geo_traits::Dimensions::Xyzm 253 | } 254 | } 255 | 256 | fn nth_or_panic(&self, n: usize) -> Self::T { 257 | match n { 258 | 0 => self.x(), 259 | 1 => self.y(), 260 | 2 => self.z, 261 | 3 => { 262 | if self.m > NO_DATA { 263 | self.m 264 | } else { 265 | panic!("asked for 4th item from coordinate but this coordinate does not have 4 dimensions.") 266 | } 267 | } 268 | _ => panic!("invalid dimension index"), 269 | } 270 | } 271 | 272 | fn x(&self) -> Self::T { 273 | self.x 274 | } 275 | 276 | fn y(&self) -> Self::T { 277 | self.y 278 | } 279 | } 280 | 281 | impl PointTrait for PointZ { 282 | type T = f64; 283 | type CoordType<'a> 284 | = &'a PointZ 285 | where 286 | Self: 'a; 287 | 288 | fn dim(&self) -> geo_traits::Dimensions { 289 | if self.m <= NO_DATA { 290 | geo_traits::Dimensions::Xyz 291 | } else { 292 | geo_traits::Dimensions::Xyzm 293 | } 294 | } 295 | 296 | fn coord(&self) -> Option> { 297 | Some(self) 298 | } 299 | } 300 | 301 | impl PointTrait for &PointZ { 302 | type T = f64; 303 | type CoordType<'a> 304 | = &'a PointZ 305 | where 306 | Self: 'a; 307 | 308 | fn dim(&self) -> geo_traits::Dimensions { 309 | if self.m <= NO_DATA { 310 | geo_traits::Dimensions::Xyz 311 | } else { 312 | geo_traits::Dimensions::Xyzm 313 | } 314 | } 315 | 316 | fn coord(&self) -> Option> { 317 | Some(self) 318 | } 319 | } 320 | 321 | pub struct LineString<'a>(&'a [Point]); 322 | 323 | impl LineStringTrait for LineString<'_> { 324 | type T = f64; 325 | type CoordType<'b> 326 | = &'b Point 327 | where 328 | Self: 'b; 329 | 330 | fn dim(&self) -> geo_traits::Dimensions { 331 | geo_traits::Dimensions::Xy 332 | } 333 | 334 | fn num_coords(&self) -> usize { 335 | self.0.len() 336 | } 337 | 338 | unsafe fn coord_unchecked(&self, i: usize) -> Self::CoordType<'_> { 339 | self.0.get_unchecked(i) 340 | } 341 | } 342 | 343 | pub struct LineStringM<'a>(&'a [PointM]); 344 | 345 | impl LineStringTrait for LineStringM<'_> { 346 | type T = f64; 347 | type CoordType<'b> 348 | = &'b PointM 349 | where 350 | Self: 'b; 351 | 352 | fn dim(&self) -> geo_traits::Dimensions { 353 | geo_traits::Dimensions::Xym 354 | } 355 | 356 | fn num_coords(&self) -> usize { 357 | self.0.len() 358 | } 359 | 360 | unsafe fn coord_unchecked(&self, i: usize) -> Self::CoordType<'_> { 361 | self.0.get_unchecked(i) 362 | } 363 | } 364 | 365 | pub struct LineStringZ<'a>(&'a [PointZ]); 366 | 367 | impl LineStringTrait for LineStringZ<'_> { 368 | type T = f64; 369 | type CoordType<'b> 370 | = &'b PointZ 371 | where 372 | Self: 'b; 373 | 374 | fn dim(&self) -> geo_traits::Dimensions { 375 | // Check the first underlying coordinate to check if it's XYZ or XYZM 376 | self.0 377 | .first() 378 | .map(CoordTrait::dim) 379 | .unwrap_or(geo_traits::Dimensions::Xyz) 380 | } 381 | 382 | fn num_coords(&self) -> usize { 383 | self.0.len() 384 | } 385 | 386 | unsafe fn coord_unchecked(&self, i: usize) -> Self::CoordType<'_> { 387 | self.0.get_unchecked(i) 388 | } 389 | } 390 | 391 | pub struct Polygon { 392 | outer: Vec, 393 | inner: Vec>, 394 | } 395 | 396 | impl<'a> PolygonTrait for &'a Polygon { 397 | type T = f64; 398 | type RingType<'b> 399 | = LineString<'a> 400 | where 401 | Self: 'b; 402 | 403 | fn dim(&self) -> geo_traits::Dimensions { 404 | geo_traits::Dimensions::Xy 405 | } 406 | 407 | fn num_interiors(&self) -> usize { 408 | self.inner.len() 409 | } 410 | 411 | fn exterior(&self) -> Option> { 412 | Some(LineString(&self.outer)) 413 | } 414 | 415 | unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> { 416 | LineString(&self.inner[i]) 417 | } 418 | } 419 | 420 | pub struct PolygonM { 421 | outer: Vec, 422 | inner: Vec>, 423 | } 424 | 425 | impl<'a> PolygonTrait for &'a PolygonM { 426 | type T = f64; 427 | type RingType<'b> 428 | = LineStringM<'a> 429 | where 430 | Self: 'b; 431 | 432 | fn dim(&self) -> geo_traits::Dimensions { 433 | geo_traits::Dimensions::Xym 434 | } 435 | 436 | fn num_interiors(&self) -> usize { 437 | self.inner.len() 438 | } 439 | 440 | fn exterior(&self) -> Option> { 441 | Some(LineStringM(&self.outer)) 442 | } 443 | 444 | unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> { 445 | LineStringM(&self.inner[i]) 446 | } 447 | } 448 | 449 | pub struct PolygonZ { 450 | outer: Vec, 451 | inner: Vec>, 452 | } 453 | 454 | impl<'a> PolygonTrait for &'a PolygonZ { 455 | type T = f64; 456 | type RingType<'b> 457 | = LineStringZ<'a> 458 | where 459 | Self: 'b; 460 | 461 | fn dim(&self) -> geo_traits::Dimensions { 462 | // Check the first coord of the outer ring to check if it's XYZ or XYZM 463 | self.outer 464 | .first() 465 | .map(CoordTrait::dim) 466 | .unwrap_or(geo_traits::Dimensions::Xyz) 467 | } 468 | 469 | fn num_interiors(&self) -> usize { 470 | self.inner.len() 471 | } 472 | 473 | fn exterior(&self) -> Option> { 474 | Some(LineStringZ(&self.outer)) 475 | } 476 | 477 | unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> { 478 | LineStringZ(&self.inner[i]) 479 | } 480 | } 481 | 482 | impl MultiPointTrait for Multipoint { 483 | type T = f64; 484 | type PointType<'b> 485 | = &'b Point 486 | where 487 | Self: 'b; 488 | 489 | fn dim(&self) -> geo_traits::Dimensions { 490 | geo_traits::Dimensions::Xy 491 | } 492 | 493 | fn num_points(&self) -> usize { 494 | self.points().len() 495 | } 496 | 497 | unsafe fn point_unchecked(&self, i: usize) -> Self::PointType<'_> { 498 | self.point(i).unwrap() 499 | } 500 | } 501 | 502 | impl MultiPointTrait for MultipointM { 503 | type T = f64; 504 | type PointType<'b> 505 | = &'b PointM 506 | where 507 | Self: 'b; 508 | 509 | fn dim(&self) -> geo_traits::Dimensions { 510 | geo_traits::Dimensions::Xym 511 | } 512 | 513 | fn num_points(&self) -> usize { 514 | self.points().len() 515 | } 516 | 517 | unsafe fn point_unchecked(&self, i: usize) -> Self::PointType<'_> { 518 | self.point(i).unwrap() 519 | } 520 | } 521 | 522 | impl MultiPointTrait for MultipointZ { 523 | type T = f64; 524 | type PointType<'b> 525 | = &'b PointZ 526 | where 527 | Self: 'b; 528 | 529 | fn dim(&self) -> geo_traits::Dimensions { 530 | // Check the first point to check if it's XYZ or XYZM 531 | self.points 532 | .first() 533 | .map(CoordTrait::dim) 534 | .unwrap_or(geo_traits::Dimensions::Xyz) 535 | } 536 | 537 | fn num_points(&self) -> usize { 538 | self.points().len() 539 | } 540 | 541 | unsafe fn point_unchecked(&self, i: usize) -> Self::PointType<'_> { 542 | self.point(i).unwrap() 543 | } 544 | } 545 | 546 | impl MultiLineStringTrait for Polyline { 547 | type T = f64; 548 | type LineStringType<'a> 549 | = LineString<'a> 550 | where 551 | Self: 'a; 552 | 553 | fn dim(&self) -> geo_traits::Dimensions { 554 | geo_traits::Dimensions::Xy 555 | } 556 | 557 | fn num_line_strings(&self) -> usize { 558 | self.parts().len() 559 | } 560 | 561 | unsafe fn line_string_unchecked(&self, i: usize) -> Self::LineStringType<'_> { 562 | LineString(self.part(i).unwrap()) 563 | } 564 | } 565 | 566 | impl MultiLineStringTrait for PolylineM { 567 | type T = f64; 568 | type LineStringType<'a> 569 | = LineStringM<'a> 570 | where 571 | Self: 'a; 572 | 573 | fn dim(&self) -> geo_traits::Dimensions { 574 | geo_traits::Dimensions::Xym 575 | } 576 | 577 | fn num_line_strings(&self) -> usize { 578 | self.parts().len() 579 | } 580 | 581 | unsafe fn line_string_unchecked(&self, i: usize) -> Self::LineStringType<'_> { 582 | LineStringM(self.part(i).unwrap()) 583 | } 584 | } 585 | 586 | impl MultiLineStringTrait for PolylineZ { 587 | type T = f64; 588 | type LineStringType<'a> 589 | = LineStringZ<'a> 590 | where 591 | Self: 'a; 592 | 593 | fn dim(&self) -> geo_traits::Dimensions { 594 | // Check the first point to check if it's XYZ or XYZM 595 | self.parts 596 | .first() 597 | .and_then(|line_string| line_string.first().map(CoordTrait::dim)) 598 | .unwrap_or(geo_traits::Dimensions::Xyz) 599 | } 600 | 601 | fn num_line_strings(&self) -> usize { 602 | self.parts().len() 603 | } 604 | 605 | unsafe fn line_string_unchecked(&self, i: usize) -> Self::LineStringType<'_> { 606 | LineStringZ(self.part(i).unwrap()) 607 | } 608 | } 609 | 610 | pub struct MultiPolygon(Vec); 611 | 612 | impl From for MultiPolygon { 613 | fn from(geom: crate::Polygon) -> Self { 614 | let mut last_poly = None; 615 | let mut polygons = Vec::new(); 616 | for ring in geom.into_inner() { 617 | match ring { 618 | PolygonRing::Outer(points) => { 619 | if let Some(poly) = last_poly.take() { 620 | polygons.push(poly); 621 | } 622 | last_poly = Some(Polygon { 623 | outer: points, 624 | inner: vec![], 625 | }); 626 | } 627 | PolygonRing::Inner(points) => { 628 | if let Some(poly) = last_poly.as_mut() { 629 | poly.inner.push(points); 630 | } else { 631 | panic!("inner ring without a previous outer ring"); 632 | // This is the strange (?) case: inner ring without a previous outer ring 633 | // polygons.push(geo_types::Polygon::::new( 634 | // LineString::::from(Vec::>::new()), 635 | // vec![LineString::from(interior)], 636 | // )); 637 | } 638 | } 639 | } 640 | } 641 | 642 | if let Some(poly) = last_poly.take() { 643 | polygons.push(poly); 644 | } 645 | 646 | Self(polygons) 647 | } 648 | } 649 | 650 | impl MultiPolygonTrait for MultiPolygon { 651 | type T = f64; 652 | type PolygonType<'a> = &'a Polygon; 653 | 654 | fn dim(&self) -> geo_traits::Dimensions { 655 | geo_traits::Dimensions::Xy 656 | } 657 | 658 | fn num_polygons(&self) -> usize { 659 | self.0.len() 660 | } 661 | 662 | unsafe fn polygon_unchecked(&self, i: usize) -> Self::PolygonType<'_> { 663 | &self.0[i] 664 | } 665 | } 666 | 667 | pub struct MultiPolygonM(Vec); 668 | 669 | impl From for MultiPolygonM { 670 | fn from(geom: crate::Polygon) -> Self { 671 | let mut last_poly = None; 672 | let mut polygons = Vec::new(); 673 | for ring in geom.into_inner() { 674 | match ring { 675 | PolygonRing::Outer(points) => { 676 | if let Some(poly) = last_poly.take() { 677 | polygons.push(poly); 678 | } 679 | last_poly = Some(Polygon { 680 | outer: points, 681 | inner: vec![], 682 | }); 683 | } 684 | PolygonRing::Inner(points) => { 685 | if let Some(poly) = last_poly.as_mut() { 686 | poly.inner.push(points); 687 | } else { 688 | panic!("inner ring without a previous outer ring"); 689 | // This is the strange (?) case: inner ring without a previous outer ring 690 | // polygons.push(geo_types::Polygon::::new( 691 | // LineString::::from(Vec::>::new()), 692 | // vec![LineString::from(interior)], 693 | // )); 694 | } 695 | } 696 | } 697 | } 698 | 699 | if let Some(poly) = last_poly.take() { 700 | polygons.push(poly); 701 | } 702 | 703 | Self(polygons) 704 | } 705 | } 706 | 707 | impl MultiPolygonTrait for MultiPolygonM { 708 | type T = f64; 709 | type PolygonType<'a> = &'a Polygon; 710 | 711 | fn dim(&self) -> geo_traits::Dimensions { 712 | geo_traits::Dimensions::Xym 713 | } 714 | 715 | fn num_polygons(&self) -> usize { 716 | self.0.len() 717 | } 718 | 719 | unsafe fn polygon_unchecked(&self, i: usize) -> Self::PolygonType<'_> { 720 | &self.0[i] 721 | } 722 | } 723 | 724 | pub struct MultiPolygonZ(Vec); 725 | 726 | impl From for MultiPolygonZ { 727 | fn from(geom: crate::PolygonZ) -> Self { 728 | let mut last_poly = None; 729 | let mut polygons = Vec::new(); 730 | for ring in geom.into_inner() { 731 | match ring { 732 | PolygonRing::Outer(points) => { 733 | if let Some(poly) = last_poly.take() { 734 | polygons.push(poly); 735 | } 736 | last_poly = Some(PolygonZ { 737 | outer: points, 738 | inner: vec![], 739 | }); 740 | } 741 | PolygonRing::Inner(points) => { 742 | if let Some(poly) = last_poly.as_mut() { 743 | poly.inner.push(points); 744 | } else { 745 | panic!("inner ring without a previous outer ring"); 746 | // This is the strange (?) case: inner ring without a previous outer ring 747 | // polygons.push(geo_types::Polygon::::new( 748 | // LineString::::from(Vec::>::new()), 749 | // vec![LineString::from(interior)], 750 | // )); 751 | } 752 | } 753 | } 754 | } 755 | 756 | if let Some(poly) = last_poly.take() { 757 | polygons.push(poly); 758 | } 759 | 760 | Self(polygons) 761 | } 762 | } 763 | 764 | impl MultiPolygonTrait for MultiPolygonZ { 765 | type T = f64; 766 | type PolygonType<'a> = &'a PolygonZ; 767 | 768 | fn dim(&self) -> geo_traits::Dimensions { 769 | // Check the first polygon to check if it's XYZ or XYZM 770 | self.0 771 | .first() 772 | .map(|polygon| polygon.dim()) 773 | .unwrap_or(geo_traits::Dimensions::Xyz) 774 | } 775 | 776 | fn num_polygons(&self) -> usize { 777 | self.0.len() 778 | } 779 | 780 | unsafe fn polygon_unchecked(&self, i: usize) -> Self::PolygonType<'_> { 781 | &self.0[i] 782 | } 783 | } 784 | -------------------------------------------------------------------------------- /src/header.rs: -------------------------------------------------------------------------------- 1 | use super::{Error, ShapeType}; 2 | 3 | use crate::record::BBoxZ; 4 | use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; 5 | use std::io::{Read, Write}; 6 | 7 | pub(crate) const HEADER_SIZE: i32 = 100; 8 | const FILE_CODE: i32 = 9994; 9 | /// Size of reserved bytes in the header, that have do defined use 10 | const SIZE_OF_SKIP: usize = size_of::() * 5; 11 | 12 | /// struct representing the Header of a shapefile 13 | /// can be retrieved via the reader used to read 14 | #[derive(Copy, Clone, PartialEq)] 15 | pub struct Header { 16 | /// Total file length (Header + Shapes) in 16bit word 17 | pub file_length: i32, 18 | /// The bbox contained all the shapes in this shapefile 19 | /// 20 | /// For shapefiles where the shapes do not have `m` or `z` values 21 | /// the associated min and max will be `0`s. 22 | pub bbox: BBoxZ, 23 | /// Type of all the shapes in the file 24 | /// (as mixing shapes is not allowed) 25 | pub shape_type: ShapeType, 26 | /// Version of the shapefile specification 27 | pub version: i32, 28 | } 29 | 30 | impl Default for Header { 31 | fn default() -> Self { 32 | Header { 33 | bbox: BBoxZ::default(), 34 | shape_type: ShapeType::NullShape, 35 | file_length: HEADER_SIZE / 2, 36 | version: 1000, 37 | } 38 | } 39 | } 40 | 41 | impl Header { 42 | pub fn read_from(mut source: &mut T) -> Result { 43 | let file_code = source.read_i32::()?; 44 | 45 | if file_code != FILE_CODE { 46 | return Err(Error::InvalidFileCode(file_code)); 47 | } 48 | 49 | let mut skip: [u8; SIZE_OF_SKIP] = [0; SIZE_OF_SKIP]; 50 | source.read_exact(&mut skip)?; 51 | 52 | let file_length = source.read_i32::()?; 53 | let version = source.read_i32::()?; 54 | let shape_type = ShapeType::read_from(&mut source)?; 55 | 56 | let mut hdr = Header { 57 | shape_type, 58 | version, 59 | file_length, 60 | ..Default::default() 61 | }; 62 | 63 | hdr.bbox.min.x = source.read_f64::()?; 64 | hdr.bbox.min.y = source.read_f64::()?; 65 | hdr.bbox.max.x = source.read_f64::()?; 66 | hdr.bbox.max.y = source.read_f64::()?; 67 | hdr.bbox.min.z = source.read_f64::()?; 68 | hdr.bbox.max.z = source.read_f64::()?; 69 | hdr.bbox.min.m = source.read_f64::()?; 70 | hdr.bbox.max.m = source.read_f64::()?; 71 | 72 | Ok(hdr) 73 | } 74 | 75 | pub(crate) fn write_to(&self, dest: &mut T) -> Result<(), std::io::Error> { 76 | dest.write_i32::(FILE_CODE)?; 77 | 78 | let skip: [u8; SIZE_OF_SKIP] = [0; SIZE_OF_SKIP]; 79 | dest.write_all(&skip)?; 80 | 81 | dest.write_i32::(self.file_length)?; 82 | dest.write_i32::(self.version)?; 83 | dest.write_i32::(self.shape_type as i32)?; 84 | 85 | dest.write_f64::(self.bbox.min.x)?; 86 | dest.write_f64::(self.bbox.min.y)?; 87 | dest.write_f64::(self.bbox.max.x)?; 88 | dest.write_f64::(self.bbox.max.y)?; 89 | dest.write_f64::(self.bbox.min.z)?; 90 | dest.write_f64::(self.bbox.max.z)?; 91 | dest.write_f64::(self.bbox.min.m)?; 92 | dest.write_f64::(self.bbox.max.m)?; 93 | 94 | Ok(()) 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | 102 | use std::io::{Seek, SeekFrom}; 103 | 104 | #[test] 105 | fn wrong_file_code() { 106 | use std::io::Cursor; 107 | 108 | let mut src = Cursor::new(vec![]); 109 | src.write_i32::(42).unwrap(); 110 | 111 | src.seek(SeekFrom::Start(0)).unwrap(); 112 | assert!(Header::read_from(&mut src).is_err()); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Read & Write [Shapefile](http://downloads.esri.com/support/whitepapers/mo_/shapefile.pdf) in Rust 2 | //! 3 | //! A _shapefile_ is in reality a collection of 3 mandatory files: 4 | //! - .shp (feature geometry aka shapes) 5 | //! - .shx (index of feature geometry) 6 | //! - .dbf (attribute information, aka records) 7 | //! 8 | //! As different shapefiles can store different type of shapes 9 | //! (but one shapefile can only store the same type of shapes) 10 | //! This library provide two ways of reading the shapes: 11 | //! 12 | //! 1) Reading as [Shape](record/enum.Shape.html) and then do a `match` to handle the different shapes 13 | //! 2) Reading directly as concrete shapes (ie Polyline, PolylineZ, Point, etc) this of course only 14 | //! works if the file actually contains shapes that matches the requested type 15 | //! 16 | //! # dBase 17 | //! 18 | //! The attributes (stored in the .dbg) files are read and written using the dbase crate 19 | //! which is re-exported so you can use `use shapefile::dbase`. 20 | //! dBase files may have different encoding which may only be supported if either one of the 21 | //! following features is enabled: 22 | //! - `encoding_rs` (notably supports GBK encoding) 23 | //! - `yore` 24 | //! 25 | //! # Shapefiles shapes 26 | //! 27 | //! The [`Point`], [`PointM`] and [`PointZ`] are the base data types of shapefiles, 28 | //! the other shapes (`Polyline, Multipoint`, ...) are collections of these type of points 29 | //! with different semantics (multiple parts or no, closed parts or no, ...) 30 | //! 31 | //! With the exception of the [`Multipatch`] shape, each shape as a variant for each type 32 | //! of point. ([`Multipatch`] always uses [`PointZ`]) 33 | //! Eg: For the polyline, there is [`Polyline`], [`PolylineM`], [`PolylineZ`] 34 | //! 35 | //! # Reading 36 | //! 37 | //! For more details see the [reader](reader/index.html) module 38 | //! 39 | //! # Writing 40 | //! 41 | //! To write a file see the [writer](writer/index.html) module 42 | //! 43 | //! # Features 44 | //! 45 | //! The `geo-types` feature can be enabled to have access to `From` and `TryFrom` 46 | //! implementations allowing to convert (or try to) back and forth between shapefile's type and 47 | //! the one in `geo_types` 48 | //! 49 | //! The `yore` or `encoding_rs` feature can be activated to allows the dbase crate 50 | //! to handle files with special encodings. 51 | //! 52 | //! [`Point`]: record/point/struct.Point.html 53 | //! [`PointM`]: record/point/struct.PointM.html 54 | //! [`PointZ`]: record/point/struct.PointZ.html 55 | //! [`Polyline`]: record/polyline/type.Polyline.html 56 | //! [`PolylineM`]: record/polyline/type.PolylineM.html 57 | //! [`PolylineZ`]: record/polyline/type.PolylineZ.html 58 | //! [`Multipatch`]: record/multipatch/struct.Multipatch.html 59 | extern crate byteorder; 60 | pub extern crate dbase; 61 | 62 | pub mod header; 63 | pub mod reader; 64 | pub mod record; 65 | pub mod writer; 66 | 67 | #[cfg(feature = "geo-traits")] 68 | mod geo_traits_impl; 69 | 70 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; 71 | use std::fmt; 72 | use std::io::{Read, Write}; 73 | 74 | pub use reader::{read, read_as, read_shapes, read_shapes_as, Reader, ShapeReader}; 75 | pub use record::Multipatch; 76 | pub use record::{convert_shapes_to_vec_of, HasShapeType, ReadableShape}; 77 | pub use record::{Multipoint, MultipointM, MultipointZ}; 78 | pub use record::{Patch, Shape, NO_DATA}; 79 | pub use record::{Point, PointM, PointZ}; 80 | pub use record::{Polygon, PolygonM, PolygonRing, PolygonZ}; 81 | pub use record::{Polyline, PolylineM, PolylineZ}; 82 | pub use writer::{ShapeWriter, Writer}; 83 | 84 | extern crate core; 85 | #[cfg(feature = "geo-types")] 86 | extern crate geo_types; 87 | 88 | /// All Errors that can happen when using this library 89 | #[derive(Debug)] 90 | pub enum Error { 91 | /// Wrapper around standard io::Error that might occur when reading/writing 92 | IoError(std::io::Error), 93 | /// The file read had an invalid File code (meaning it's not a Shapefile) 94 | InvalidFileCode(i32), 95 | /// The file read had an invalid [ShapeType](enum.ShapeType.html) code 96 | /// (either in the file header or any record type) 97 | InvalidShapeType(i32), 98 | /// The Multipatch shape read from the file had an invalid [PatchType](enum.PatchType.html) code 99 | InvalidPatchType(i32), 100 | /// Error returned when trying to read the shape records as a certain shape type 101 | /// but the actual shape type does not correspond to the one asked 102 | MismatchShapeType { 103 | /// The requested ShapeType 104 | requested: ShapeType, 105 | /// The actual type of the shape 106 | actual: ShapeType, 107 | }, 108 | InvalidShapeRecordSize, 109 | DbaseError(dbase::Error), 110 | MissingDbf, 111 | MissingIndexFile, 112 | } 113 | 114 | impl From for Error { 115 | fn from(error: std::io::Error) -> Error { 116 | Error::IoError(error) 117 | } 118 | } 119 | 120 | impl From for Error { 121 | fn from(e: dbase::Error) -> Error { 122 | Error::DbaseError(e) 123 | } 124 | } 125 | 126 | impl fmt::Display for Error { 127 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 128 | match self { 129 | Error::IoError(e) => write!(f, "{}", e), 130 | Error::InvalidFileCode(code) => write!( 131 | f, 132 | "The file code ' {} ' is invalid, is this a Shapefile ?", 133 | code 134 | ), 135 | Error::InvalidShapeType(code) => write!( 136 | f, 137 | "The code ' {} ' does not correspond to any of the ShapeType code defined by ESRI", 138 | code 139 | ), 140 | Error::MismatchShapeType { requested, actual } => write!( 141 | f, 142 | "The requested type: '{}' does not correspond to the actual shape type: '{}'", 143 | requested, actual 144 | ), 145 | e => write!(f, "{:?}", e), 146 | } 147 | } 148 | } 149 | 150 | impl std::error::Error for Error {} 151 | 152 | /// The enum for the ShapeType as defined in the 153 | /// specification 154 | #[derive(Debug, PartialEq, Copy, Clone)] 155 | pub enum ShapeType { 156 | NullShape = 0, 157 | Point = 1, 158 | Polyline = 3, 159 | Polygon = 5, 160 | Multipoint = 8, 161 | 162 | PointZ = 11, 163 | PolylineZ = 13, 164 | PolygonZ = 15, 165 | MultipointZ = 18, 166 | 167 | PointM = 21, 168 | PolylineM = 23, 169 | PolygonM = 25, 170 | MultipointM = 28, 171 | 172 | Multipatch = 31, 173 | } 174 | 175 | impl ShapeType { 176 | pub(crate) fn read_from(source: &mut T) -> Result { 177 | let code = source.read_i32::()?; 178 | Self::from(code).ok_or(Error::InvalidShapeType(code)) 179 | } 180 | 181 | pub(crate) fn write_to(self, dest: &mut T) -> Result<(), std::io::Error> { 182 | dest.write_i32::(self as i32)?; 183 | Ok(()) 184 | } 185 | 186 | /// Returns the ShapeType corresponding to the input code 187 | /// if the code is valid 188 | /// ``` 189 | /// use shapefile::ShapeType; 190 | /// 191 | /// assert_eq!(ShapeType::from(25), Some(ShapeType::PolygonM)); 192 | /// assert_eq!(ShapeType::from(60), None); 193 | /// ``` 194 | pub fn from(code: i32) -> Option { 195 | match code { 196 | 0 => Some(ShapeType::NullShape), 197 | 1 => Some(ShapeType::Point), 198 | 3 => Some(ShapeType::Polyline), 199 | 5 => Some(ShapeType::Polygon), 200 | 8 => Some(ShapeType::Multipoint), 201 | 11 => Some(ShapeType::PointZ), 202 | 13 => Some(ShapeType::PolylineZ), 203 | 15 => Some(ShapeType::PolygonZ), 204 | 18 => Some(ShapeType::MultipointZ), 205 | 21 => Some(ShapeType::PointM), 206 | 23 => Some(ShapeType::PolylineM), 207 | 25 => Some(ShapeType::PolygonM), 208 | 28 => Some(ShapeType::MultipointM), 209 | 31 => Some(ShapeType::Multipatch), 210 | _ => None, 211 | } 212 | } 213 | 214 | /// Returns whether the ShapeType has the third dimension Z 215 | pub fn has_z(self) -> bool { 216 | matches!( 217 | self, 218 | ShapeType::PointZ 219 | | ShapeType::PolylineZ 220 | | ShapeType::PolygonZ 221 | | ShapeType::MultipointZ 222 | | ShapeType::Multipatch 223 | ) 224 | } 225 | 226 | /// Returns whether the ShapeType has the optional measure dimension 227 | pub fn has_m(self) -> bool { 228 | matches!( 229 | self, 230 | ShapeType::PointZ 231 | | ShapeType::PolylineZ 232 | | ShapeType::PolygonZ 233 | | ShapeType::MultipointZ 234 | | ShapeType::PointM 235 | | ShapeType::PolylineM 236 | | ShapeType::PolygonM 237 | | ShapeType::MultipointM 238 | ) 239 | } 240 | 241 | /// Returns true if the shape may have multiple parts 242 | pub fn is_multipart(self) -> bool { 243 | !matches!( 244 | self, 245 | ShapeType::Point 246 | | ShapeType::PointM 247 | | ShapeType::PointZ 248 | | ShapeType::Multipoint 249 | | ShapeType::MultipointM 250 | | ShapeType::MultipointZ 251 | ) 252 | } 253 | } 254 | 255 | impl fmt::Display for ShapeType { 256 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 257 | match self { 258 | ShapeType::NullShape => write!(f, "NullShape"), 259 | ShapeType::Point => write!(f, "Point"), 260 | ShapeType::Polyline => write!(f, "Polyline"), 261 | ShapeType::Polygon => write!(f, "Polygon"), 262 | ShapeType::Multipoint => write!(f, "Multipoint"), 263 | ShapeType::PointZ => write!(f, "PointZ"), 264 | ShapeType::PolylineZ => write!(f, "PolylineZ"), 265 | ShapeType::PolygonZ => write!(f, "PolygonZ"), 266 | ShapeType::MultipointZ => write!(f, "MultipointZ"), 267 | ShapeType::PointM => write!(f, "PointM"), 268 | ShapeType::PolylineM => write!(f, "PolylineM"), 269 | ShapeType::PolygonM => write!(f, "PolygonM"), 270 | ShapeType::MultipointM => write!(f, "MultipointM"), 271 | ShapeType::Multipatch => write!(f, "Multipatch"), 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/record/bbox.rs: -------------------------------------------------------------------------------- 1 | //! Bounding Boxes 2 | use super::traits::{GrowablePoint, HasM, HasXY, HasZ, ShrinkablePoint}; 3 | use super::EsriShape; 4 | use super::PointZ; 5 | use crate::writer::{f64_max, f64_min}; 6 | 7 | /// The Bounding Box type used in this crate. 8 | /// 9 | /// Each shape that is a collection of points have a bounding box 10 | /// associated to it generally accessible using the `bbox()` method. 11 | /// 12 | /// # Example 13 | /// 14 | /// ``` 15 | /// use shapefile::{PointM, PolylineM}; 16 | /// let poly = PolylineM::new(vec![ 17 | /// PointM::new(1.0, 2.0, 13.42), 18 | /// PointM::new(2.0, 1.0, 42.3713), 19 | /// ]); 20 | /// 21 | /// let bbox = poly.bbox(); 22 | /// assert_eq!(bbox.min, PointM::new(1.0, 1.0, 13.42)); 23 | /// assert_eq!(bbox.max, PointM::new(2.0, 2.0, 42.3713)); 24 | /// ``` 25 | #[derive(Debug, Copy, Clone, PartialEq)] 26 | pub struct GenericBBox { 27 | pub max: PointType, 28 | pub min: PointType, 29 | } 30 | 31 | impl GenericBBox { 32 | pub(crate) fn from_points(points: &[PointType]) -> Self 33 | where 34 | PointType: Copy + ShrinkablePoint + GrowablePoint, 35 | { 36 | let mut min_point = points[0]; 37 | let mut max_point = points[0]; 38 | 39 | for point in &points[1..] { 40 | min_point.shrink(point); 41 | max_point.grow(point); 42 | } 43 | 44 | Self { 45 | max: max_point, 46 | min: min_point, 47 | } 48 | } 49 | 50 | pub(crate) fn grow_from_points(&mut self, points: &[PointType]) 51 | where 52 | PointType: ShrinkablePoint + GrowablePoint, 53 | { 54 | for point in points { 55 | self.min.shrink(point); 56 | self.max.grow(point); 57 | } 58 | } 59 | 60 | pub(crate) fn from_parts(parts: &[Vec]) -> Self 61 | where 62 | PointType: ShrinkablePoint + GrowablePoint + Copy, 63 | { 64 | let mut bbox = Self::from_points(&parts[0]); 65 | for part in &parts[1..] { 66 | bbox.grow_from_points(part); 67 | } 68 | bbox 69 | } 70 | } 71 | 72 | impl GenericBBox { 73 | pub fn x_range(&self) -> [f64; 2] { 74 | [self.min.x(), self.max.x()] 75 | } 76 | 77 | pub fn y_range(&self) -> [f64; 2] { 78 | [self.min.y(), self.max.y()] 79 | } 80 | } 81 | 82 | impl GenericBBox { 83 | pub fn z_range(&self) -> [f64; 2] { 84 | [self.min.z(), self.max.z()] 85 | } 86 | } 87 | 88 | impl GenericBBox { 89 | pub fn m_range(&self) -> [f64; 2] { 90 | [self.min.m(), self.max.m()] 91 | } 92 | } 93 | 94 | impl Default for GenericBBox { 95 | fn default() -> Self { 96 | Self { 97 | max: PointType::default(), 98 | min: PointType::default(), 99 | } 100 | } 101 | } 102 | 103 | pub type BBoxZ = GenericBBox; 104 | 105 | impl BBoxZ { 106 | pub(crate) fn grow_from_shape(&mut self, shape: &S) { 107 | let x_range = shape.x_range(); 108 | let y_range = shape.y_range(); 109 | let z_range = shape.z_range(); 110 | let m_range = shape.m_range(); 111 | 112 | self.min.x = f64_min(x_range[0], self.min.x); 113 | self.max.x = f64_max(x_range[1], self.max.x); 114 | self.min.y = f64_min(y_range[0], self.min.y); 115 | self.max.y = f64_max(y_range[1], self.max.y); 116 | 117 | if S::shapetype().has_m() { 118 | self.min.m = f64_min(m_range[0], self.min.m); 119 | self.max.m = f64_max(m_range[1], self.max.m); 120 | } 121 | 122 | if S::shapetype().has_z() { 123 | self.min.z = f64_min(z_range[0], self.min.z); 124 | self.max.z = f64_max(z_range[1], self.max.z); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/record/io.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | 3 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; 4 | 5 | use super::traits::{HasM, HasMutM, HasMutXY, HasMutZ, HasXY, HasZ}; 6 | use super::{GenericBBox, PointZ, NO_DATA}; 7 | use super::{Point, PointM}; 8 | 9 | pub(crate) fn bbox_read_xy_from( 10 | bbox: &mut GenericBBox, 11 | src: &mut R, 12 | ) -> std::io::Result<()> { 13 | *bbox.min.x_mut() = src.read_f64::()?; 14 | *bbox.min.y_mut() = src.read_f64::()?; 15 | *bbox.max.x_mut() = src.read_f64::()?; 16 | *bbox.max.y_mut() = src.read_f64::()?; 17 | Ok(()) 18 | } 19 | 20 | pub(crate) fn bbox_write_xy_to( 21 | bbox: &GenericBBox, 22 | dst: &mut W, 23 | ) -> std::io::Result<()> { 24 | dst.write_f64::(bbox.min.x())?; 25 | dst.write_f64::(bbox.min.y())?; 26 | dst.write_f64::(bbox.max.x())?; 27 | dst.write_f64::(bbox.max.y())?; 28 | Ok(()) 29 | } 30 | 31 | pub(crate) fn bbox_read_m_range_from( 32 | bbox: &mut GenericBBox, 33 | src: &mut R, 34 | ) -> std::io::Result<()> { 35 | *bbox.min.m_mut() = src.read_f64::()?; 36 | *bbox.max.m_mut() = src.read_f64::()?; 37 | Ok(()) 38 | } 39 | 40 | pub(crate) fn bbox_read_z_range_from( 41 | bbox: &mut GenericBBox, 42 | src: &mut R, 43 | ) -> std::io::Result<()> { 44 | *bbox.min.z_mut() = src.read_f64::()?; 45 | *bbox.max.z_mut() = src.read_f64::()?; 46 | Ok(()) 47 | } 48 | 49 | pub(crate) fn bbox_write_m_range_to( 50 | bbox: &GenericBBox, 51 | dst: &mut W, 52 | ) -> std::io::Result<()> { 53 | dst.write_f64::(bbox.min.m())?; 54 | dst.write_f64::(bbox.max.m())?; 55 | Ok(()) 56 | } 57 | 58 | pub(crate) fn bbox_write_z_range_to( 59 | bbox: &GenericBBox, 60 | dst: &mut W, 61 | ) -> std::io::Result<()> { 62 | dst.write_f64::(bbox.min.z())?; 63 | dst.write_f64::(bbox.max.z())?; 64 | Ok(()) 65 | } 66 | 67 | pub(crate) fn read_xy_in_vec_of( 68 | source: &mut T, 69 | num_points: i32, 70 | ) -> Result, std::io::Error> 71 | where 72 | PointType: HasMutXY + Default, 73 | T: Read, 74 | { 75 | let mut points = Vec::::with_capacity(num_points as usize); 76 | for _ in 0..num_points { 77 | let mut p = PointType::default(); 78 | *p.x_mut() = source.read_f64::()?; 79 | *p.y_mut() = source.read_f64::()?; 80 | points.push(p); 81 | } 82 | Ok(points) 83 | } 84 | 85 | pub(crate) fn read_ms_into( 86 | source: &mut T, 87 | points: &mut [D], 88 | ) -> Result<(), std::io::Error> { 89 | for point in points { 90 | *point.m_mut() = f64::max(source.read_f64::()?, NO_DATA); 91 | } 92 | Ok(()) 93 | } 94 | 95 | pub(crate) fn read_zs_into( 96 | source: &mut T, 97 | points: &mut [PointZ], 98 | ) -> Result<(), std::io::Error> { 99 | for point in points { 100 | point.z = source.read_f64::()?; 101 | } 102 | Ok(()) 103 | } 104 | 105 | pub(crate) fn read_parts( 106 | source: &mut T, 107 | num_parts: i32, 108 | ) -> Result, std::io::Error> { 109 | let mut parts = Vec::::with_capacity(num_parts as usize); 110 | for _ in 0..num_parts { 111 | parts.push(source.read_i32::()?); 112 | } 113 | Ok(parts) 114 | } 115 | 116 | pub(crate) fn write_points( 117 | dest: &mut T, 118 | points: &[PointType], 119 | ) -> Result<(), std::io::Error> { 120 | for point in points { 121 | dest.write_f64::(point.x())?; 122 | dest.write_f64::(point.y())?; 123 | } 124 | Ok(()) 125 | } 126 | 127 | pub(crate) fn write_ms( 128 | dest: &mut T, 129 | points: &[PointType], 130 | ) -> Result<(), std::io::Error> { 131 | for point in points { 132 | dest.write_f64::(point.m())?; 133 | } 134 | Ok(()) 135 | } 136 | 137 | pub(crate) fn write_zs(dest: &mut T, points: &[PointZ]) -> Result<(), std::io::Error> { 138 | for point in points { 139 | dest.write_f64::(point.z)?; 140 | } 141 | Ok(()) 142 | } 143 | 144 | struct PartIndexIter<'a> { 145 | parts_indices: &'a Vec, 146 | current_part_index: usize, 147 | num_points: i32, 148 | } 149 | 150 | impl<'a> PartIndexIter<'a> { 151 | fn new(parts_indices: &'a Vec, num_points: i32) -> Self { 152 | Self { 153 | parts_indices, 154 | current_part_index: 0, 155 | num_points, 156 | } 157 | } 158 | } 159 | 160 | impl Iterator for PartIndexIter<'_> { 161 | type Item = (i32, i32); 162 | 163 | fn next(&mut self) -> Option { 164 | if self.current_part_index < self.parts_indices.len() { 165 | let start_of_part_index = self.parts_indices[self.current_part_index]; 166 | let end_of_part_index = self 167 | .parts_indices 168 | .get(self.current_part_index + 1) 169 | .copied() 170 | .unwrap_or(self.num_points); 171 | self.current_part_index += 1; 172 | debug_assert!(end_of_part_index >= start_of_part_index); 173 | Some((start_of_part_index, end_of_part_index)) 174 | } else { 175 | None 176 | } 177 | } 178 | 179 | fn size_hint(&self) -> (usize, Option) { 180 | if self.num_points < 0 { 181 | (0, None) 182 | } else { 183 | let remaining = self.parts_indices.len() - self.current_part_index; 184 | (remaining, Some(remaining)) 185 | } 186 | } 187 | } 188 | 189 | pub(crate) struct MultiPartShapeReader<'a, PointType, R: Read> { 190 | pub(crate) num_points: i32, 191 | pub(crate) num_parts: i32, 192 | pub(crate) parts: Vec>, 193 | pub(crate) bbox: GenericBBox, 194 | pub(crate) source: &'a mut R, 195 | parts_array: Vec, 196 | } 197 | 198 | impl<'a, PointType: Default + HasMutXY, R: Read> MultiPartShapeReader<'a, PointType, R> { 199 | pub(crate) fn new(source: &'a mut R) -> std::io::Result { 200 | let mut bbox = GenericBBox::::default(); 201 | bbox_read_xy_from(&mut bbox, source)?; 202 | let num_parts = source.read_i32::()?; 203 | let num_points = source.read_i32::()?; 204 | let parts_array = read_parts(source, num_parts)?; 205 | let parts = Vec::>::with_capacity(num_parts as usize); 206 | Ok(Self { 207 | num_points, 208 | num_parts, 209 | parts_array, 210 | parts, 211 | source, 212 | bbox, 213 | }) 214 | } 215 | 216 | pub(crate) fn read_xy(mut self) -> std::io::Result { 217 | for (start_index, end_index) in PartIndexIter::new(&self.parts_array, self.num_points) { 218 | let num_points_in_part = end_index - start_index; 219 | self.parts 220 | .push(read_xy_in_vec_of(self.source, num_points_in_part)?); 221 | } 222 | Ok(self) 223 | } 224 | } 225 | 226 | impl MultiPartShapeReader<'_, PointType, R> { 227 | pub(crate) fn read_ms(mut self) -> std::io::Result { 228 | bbox_read_m_range_from(&mut self.bbox, &mut self.source)?; 229 | for part_points in self.parts.iter_mut() { 230 | read_ms_into(self.source, part_points)?; 231 | } 232 | Ok(self) 233 | } 234 | 235 | pub(crate) fn read_ms_if(self, condition: bool) -> std::io::Result { 236 | if condition { 237 | self.read_ms() 238 | } else { 239 | Ok(self) 240 | } 241 | } 242 | } 243 | 244 | impl MultiPartShapeReader<'_, PointZ, R> { 245 | pub(crate) fn read_zs(mut self) -> std::io::Result { 246 | bbox_read_z_range_from(&mut self.bbox, &mut self.source)?; 247 | for part_points in self.parts.iter_mut() { 248 | read_zs_into(self.source, part_points)?; 249 | } 250 | Ok(self) 251 | } 252 | } 253 | 254 | pub(crate) struct MultiPartShapeWriter<'a, PointType, T, W> 255 | where 256 | T: Iterator + Clone, 257 | W: Write, 258 | { 259 | pub(crate) dst: &'a mut W, 260 | parts_iter: T, 261 | bbox: &'a GenericBBox, 262 | } 263 | 264 | impl<'a, PointType, T, W> MultiPartShapeWriter<'a, PointType, T, W> 265 | where 266 | T: Iterator + Clone, 267 | W: Write, 268 | { 269 | pub(crate) fn new(bbox: &'a GenericBBox, parts_iter: T, dst: &'a mut W) -> Self { 270 | Self { 271 | parts_iter, 272 | bbox, 273 | dst, 274 | } 275 | } 276 | 277 | pub(crate) fn write_num_points(self) -> std::io::Result { 278 | let point_count: usize = self.parts_iter.clone().map(|points| points.len()).sum(); 279 | self.dst.write_i32::(point_count as i32)?; 280 | Ok(self) 281 | } 282 | 283 | pub(crate) fn write_num_parts(self) -> std::io::Result { 284 | let num_parts = self.parts_iter.clone().count(); 285 | self.dst.write_i32::(num_parts as i32)?; 286 | Ok(self) 287 | } 288 | 289 | pub(crate) fn write_parts_array(self) -> std::io::Result { 290 | let mut sum = 0; 291 | for i in self.parts_iter.clone().map(|points| points.len() as i32) { 292 | self.dst.write_i32::(sum)?; 293 | sum += i; 294 | } 295 | Ok(self) 296 | } 297 | } 298 | 299 | impl<'a, PointType, T, W> MultiPartShapeWriter<'a, PointType, T, W> 300 | where 301 | T: Iterator + Clone, 302 | W: Write, 303 | PointType: HasXY, 304 | { 305 | pub(crate) fn write_bbox_xy(self) -> std::io::Result { 306 | bbox_write_xy_to(self.bbox, self.dst)?; 307 | Ok(self) 308 | } 309 | 310 | pub(crate) fn write_xy(self) -> std::io::Result { 311 | for points in self.parts_iter.clone() { 312 | write_points(self.dst, points)?; 313 | } 314 | Ok(self) 315 | } 316 | } 317 | 318 | impl<'a, PointType, T, W> MultiPartShapeWriter<'a, PointType, T, W> 319 | where 320 | T: Iterator + Clone, 321 | W: Write, 322 | PointType: HasM, 323 | { 324 | pub(crate) fn write_bbox_m_range(self) -> std::io::Result { 325 | bbox_write_m_range_to(self.bbox, self.dst)?; 326 | Ok(self) 327 | } 328 | 329 | pub(crate) fn write_ms(self) -> std::io::Result { 330 | for points in self.parts_iter.clone() { 331 | write_ms(self.dst, points)?; 332 | } 333 | Ok(self) 334 | } 335 | } 336 | 337 | impl<'a, T, W> MultiPartShapeWriter<'a, PointZ, T, W> 338 | where 339 | T: Iterator + Clone, 340 | W: Write, 341 | { 342 | pub(crate) fn write_bbox_z_range(self) -> std::io::Result { 343 | bbox_write_z_range_to(self.bbox, self.dst)?; 344 | Ok(self) 345 | } 346 | 347 | pub(crate) fn write_zs(self) -> std::io::Result { 348 | for points in self.parts_iter.clone() { 349 | write_zs(self.dst, points)?; 350 | } 351 | Ok(self) 352 | } 353 | } 354 | 355 | impl<'a, T, W> MultiPartShapeWriter<'a, Point, T, W> 356 | where 357 | T: Iterator + Clone, 358 | W: Write, 359 | { 360 | pub(crate) fn write_point_shape(self) -> std::io::Result { 361 | self.write_bbox_xy() 362 | .and_then(|wrt| wrt.write_num_parts()) 363 | .and_then(|wrt| wrt.write_num_points()) 364 | .and_then(|wrt| wrt.write_parts_array()) 365 | .and_then(|wrt| wrt.write_xy()) 366 | } 367 | } 368 | 369 | impl<'a, T, W> MultiPartShapeWriter<'a, PointM, T, W> 370 | where 371 | T: Iterator + Clone, 372 | W: Write, 373 | { 374 | pub(crate) fn write_point_m_shape(self) -> std::io::Result { 375 | self.write_bbox_xy() 376 | .and_then(|wrt| wrt.write_num_parts()) 377 | .and_then(|wrt| wrt.write_num_points()) 378 | .and_then(|wrt| wrt.write_parts_array()) 379 | .and_then(|wrt| wrt.write_xy()) 380 | .and_then(|wrt| wrt.write_bbox_m_range()) 381 | .and_then(|wrt| wrt.write_ms()) 382 | } 383 | } 384 | 385 | impl<'a, T, W> MultiPartShapeWriter<'a, PointZ, T, W> 386 | where 387 | T: Iterator + Clone, 388 | W: Write, 389 | { 390 | pub(crate) fn write_point_z_shape(self) -> std::io::Result { 391 | self.write_bbox_xy() 392 | .and_then(|wrt| wrt.write_num_parts()) 393 | .and_then(|wrt| wrt.write_num_points()) 394 | .and_then(|wrt| wrt.write_parts_array()) 395 | .and_then(|wrt| wrt.write_xy()) 396 | .and_then(|wrt| wrt.write_bbox_z_range()) 397 | .and_then(|wrt| wrt.write_zs()) 398 | .and_then(|wrt| wrt.write_bbox_m_range()) 399 | .and_then(|wrt| wrt.write_ms()) 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /src/record/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! multipoint { 3 | ( 4 | $( {x: $x_value:expr, y: $y_value:expr} ),* $(,)? 5 | ) => { 6 | shapefile::Multipoint::new( 7 | vec![ 8 | $( 9 | shapefile::Point {x: $x_value, y: $y_value } 10 | ),+ 11 | ] 12 | ) 13 | }; 14 | ( 15 | $( {x: $x_value:expr, y: $y_value:expr, m: $m_value:expr}),* $(,)? 16 | ) => { 17 | shapefile::MultipointM::new( 18 | vec![ 19 | $( 20 | shapefile::PointM {x: $x_value, y: $y_value, m: $m_value } 21 | ),+ 22 | ] 23 | ) 24 | }; 25 | ( 26 | $( {x: $x_value:expr, y: $y_value:expr, z: $z_value:expr, m: $m_value:expr} ),* $(,)? 27 | ) => { 28 | shapefile::MultipointZ::new( 29 | vec![ 30 | $( 31 | shapefile::PointZ {x: $x_value, y: $y_value, z: $z_value, m: $m_value } 32 | ),+ 33 | ] 34 | ) 35 | }; 36 | ( 37 | $( ($x_value:expr, $y_value:expr) ),* $(,)? 38 | ) => { 39 | shapefile::Multipoint::new( 40 | vec![ 41 | $( 42 | shapefile::Point::new($x_value, $y_value) 43 | ),+ 44 | ] 45 | ) 46 | }; 47 | ( 48 | $( ($x_value:expr, $y_value:expr, $m_value:expr) ),* $(,)? 49 | ) => { 50 | shapefile::MultipointM::new( 51 | vec![ 52 | $( 53 | shapefile::PointM::new($x_value, $y_value, $m_value) 54 | ),+ 55 | ] 56 | ) 57 | }; 58 | ( 59 | $( ($x_value:expr, $y_value:expr, $z_value:expr, $m_value:expr) ),* $(,)? 60 | ) => { 61 | shapefile::MultipointZ::new( 62 | vec![ 63 | $( 64 | shapefile::PointZ::new($x_value, $y_value, $z_value, $m_value) 65 | ),+ 66 | ] 67 | ) 68 | }; 69 | } 70 | 71 | #[macro_export] 72 | macro_rules! multipatch { 73 | ( 74 | $( 75 | $patch_type:ident( $({x: $x_value:expr, y: $y_value:expr, z: $z_value:expr, m: $m_value:expr}),* $(,)? $(,)? ) 76 | ),* 77 | ) => { 78 | shapefile::Multipatch::with_parts( 79 | vec! [ 80 | $( 81 | shapefile::Patch::$patch_type( 82 | vec![ 83 | $(shapefile::PointZ {x: $x_value, y: $y_value, z: $z_value, m: $m_value}),* 84 | ] 85 | ) 86 | ),* 87 | ] 88 | ) 89 | }; 90 | ( 91 | $( 92 | $patch_type:ident( $(($x_value:expr, $y_value:expr, $z_value:expr, $m_value:expr)),* $(,)? $(,)? ) 93 | ),* 94 | ) => { 95 | shapefile::Multipatch::with_parts( 96 | vec! [ 97 | $( 98 | shapefile::Patch::$patch_type( 99 | vec![ 100 | $(shapefile::PointZ::new($x_value, $y_value, $z_value, $m_value)),* 101 | ] 102 | ) 103 | ),* 104 | ] 105 | ) 106 | }; 107 | } 108 | 109 | #[macro_export] 110 | macro_rules! polygon { 111 | // Polygon rules 112 | ( 113 | $( 114 | $ring_type:ident( $({x: $x_value:expr, y: $y_value:expr}),* $(,)? ) $(,)? 115 | ),* 116 | ) => { 117 | shapefile::Polygon::with_rings( 118 | vec! [ 119 | $( 120 | shapefile::PolygonRing::$ring_type( 121 | vec![ 122 | $(shapefile::Point {x: $x_value, y: $y_value}),* 123 | ] 124 | ) 125 | ),* 126 | ] 127 | ) 128 | }; 129 | ( 130 | $( 131 | $ring_type:ident( $(($x_value:expr, $y_value:expr)),* $(,)? ) $(,)? 132 | ),* 133 | ) => { 134 | polygon!{ 135 | $( 136 | $ring_type( $({x: $x_value, y: $y_value}),* ) 137 | ),+ 138 | } 139 | }; 140 | // Polygon M rules 141 | ( 142 | $( 143 | $ring_type:ident( $({x: $x_value:expr, y: $y_value:expr, m: $m_value:expr}),* $(,)? ) $(,)? 144 | ),* 145 | ) => { 146 | shapefile::PolygonM::with_rings( 147 | vec! [ 148 | $( 149 | shapefile::PolygonRing::$ring_type( 150 | vec![ 151 | $(shapefile::PointM {x: $x_value, y: $y_value, m: $m_value}),* 152 | ] 153 | ) 154 | ),* 155 | ] 156 | ) 157 | }; 158 | ( 159 | $( 160 | $ring_type:ident( $(($x_value:expr, $y_value:expr, $m_value:expr)),* $(,)? ) $(,)? 161 | ),* 162 | ) => { 163 | polygon!{ 164 | $( 165 | $ring_type( $({x: $x_value, y: $y_value, m: $m_value}),* ) 166 | ),+ 167 | } 168 | }; 169 | //Polygon Z rules 170 | ( 171 | $( 172 | $ring_type:ident( $({x: $x_value:expr, y: $y_value:expr, z: $z_value:expr, m: $m_value:expr}),* $(,)? ) $(,)? 173 | ),* 174 | ) => { 175 | shapefile::PolygonZ::with_rings( 176 | vec! [ 177 | $( 178 | shapefile::PolygonRing::$ring_type( 179 | vec![ 180 | $(shapefile::PointZ {x: $x_value, y: $y_value, z: $z_value, m: $m_value}),* 181 | ] 182 | ) 183 | ),* 184 | ] 185 | ) 186 | }; 187 | ( 188 | $( 189 | $ring_type:ident( $(($x_value:expr, $y_value:expr, $z_value:expr, $m_value:expr)),* $(,)? ) $(,)? 190 | ),* 191 | ) => { 192 | polygon!{ 193 | $( 194 | $ring_type( $({x: $x_value, y: $y_value, z: $z_value, m: $m_value}),* ) 195 | ),+ 196 | } 197 | }; 198 | } 199 | 200 | #[macro_export] 201 | macro_rules! polyline { 202 | // Polyline rules 203 | ( 204 | $( 205 | [ $({x: $x_value:expr, y: $y_value:expr}),* $(,)? ] 206 | ),* $(,)? 207 | ) => { 208 | shapefile::Polyline::with_parts( 209 | vec! [ 210 | $( 211 | vec![ 212 | $(shapefile::Point {x: $x_value, y: $y_value}),* 213 | ] 214 | ),* 215 | ] 216 | ) 217 | }; 218 | ( 219 | $( 220 | [ $(($x_value:expr, $y_value:expr)),* $(,)? ] 221 | ),* $(,)? 222 | ) => { 223 | polyline!{ 224 | $( 225 | [$({x: $x_value, y: $y_value}),*] 226 | ),+ 227 | } 228 | }; 229 | // Polyline M rules 230 | ( 231 | $( 232 | [ $({x: $x_value:expr, y: $y_value:expr, m: $m_value:expr}),* $(,)? ] 233 | ),* $(,)? 234 | ) => { 235 | shapefile::PolylineM::with_parts( 236 | vec! [ 237 | $( 238 | vec![ 239 | $(shapefile::PointM {x: $x_value, y: $y_value, m: $m_value}),* 240 | ] 241 | ),* 242 | ] 243 | ) 244 | }; 245 | ( 246 | $( 247 | [ $(($x_value:expr, $y_value:expr, $m_value:expr)),* $(,)? ] 248 | ),* $(,)? 249 | ) => { 250 | polyline!{ 251 | $( 252 | [$({x: $x_value, y: $y_value, m: $m_value}),*] 253 | ),+ 254 | } 255 | }; 256 | //Polyline Z rules 257 | ( 258 | $( 259 | [ $({x: $x_value:expr, y: $y_value:expr, z: $z_value:expr, m: $m_value:expr}),* $(,)? ] 260 | ),* $(,)? 261 | ) => { 262 | shapefile::PolylineZ::with_parts( 263 | vec! [ 264 | $( 265 | vec![ 266 | $(shapefile::PointZ {x: $x_value, y: $y_value, z: $z_value, m: $m_value}),* 267 | ] 268 | ),* 269 | ] 270 | ) 271 | }; 272 | ( 273 | $( 274 | [ $(($x_value:expr, $y_value:expr, $z_value:expr, $m_value:expr)),* $(,)? ] 275 | ),* $(,)? 276 | ) => { 277 | polyline!{ 278 | $( 279 | [ $({x: $x_value, y: $y_value, z: $z_value, m: $m_value}),* ] 280 | ),+ 281 | } 282 | }; 283 | } 284 | 285 | #[cfg(test)] 286 | mod test { 287 | // the macros expect the shapefile namespace to be in scope 288 | use crate as shapefile; 289 | use crate::record::{Point, PointM, PointZ, PolygonRing, Polyline, PolylineM, PolylineZ}; 290 | use crate::Patch; 291 | 292 | #[test] 293 | fn test_multipatch() { 294 | let multipatch = multipatch!( 295 | TriangleStrip( 296 | {x: 1.0, y: 1.0, z: 1.0, m: 1.0} 297 | ) 298 | ); 299 | let multipatch_2 = multipatch!(TriangleStrip((1.0, 1.0, 1.0, 1.0))); 300 | let expected_multipatch = 301 | shapefile::Multipatch::new(Patch::TriangleStrip(vec![shapefile::PointZ::new( 302 | 1.0, 1.0, 1.0, 1.0, 303 | )])); 304 | 305 | assert_eq!(multipatch, expected_multipatch); 306 | assert_eq!(multipatch_2, expected_multipatch); 307 | } 308 | 309 | #[test] 310 | fn test_multipoint_macro() { 311 | let multipoint = multipoint![ 312 | {x: 1.0, y: 1.0}, 313 | {x: 2.0, y: 2.0}, 314 | ]; 315 | let multipoint_2 = multipoint![(1.0, 1.0), (2.0, 2.0)]; 316 | let expected_multipoint = shapefile::Multipoint::new(vec![ 317 | shapefile::Point::new(1.0, 1.0), 318 | shapefile::Point::new(2.0, 2.0), 319 | ]); 320 | 321 | assert_eq!(multipoint, expected_multipoint); 322 | assert_eq!(multipoint, multipoint_2); 323 | } 324 | 325 | #[test] 326 | fn test_multipoint_m_macro() { 327 | let multipoint = multipoint![ 328 | {x: 1.0, y: 1.0, m: 42.1337}, 329 | {x: 2.0, y: 2.0, m: 1337.42} 330 | ]; 331 | 332 | let expected_multipoint = shapefile::MultipointM::new(vec![ 333 | shapefile::PointM::new(1.0, 1.0, 42.1337), 334 | shapefile::PointM::new(2.0, 2.0, 1337.42), 335 | ]); 336 | assert_eq!(multipoint, expected_multipoint); 337 | } 338 | 339 | #[test] 340 | fn test_multipoint_z_macro() { 341 | let multipoint = multipoint![ 342 | {x: 1.0, y: 1.0, z: 17.5, m: 42.1337}, 343 | {x: 2.0, y: 2.0, z: 14.021, m: 1337.42} 344 | ]; 345 | let expected_multipoint = shapefile::MultipointZ::new(vec![ 346 | shapefile::PointZ::new(1.0, 1.0, 17.5, 42.1337), 347 | shapefile::PointZ::new(2.0, 2.0, 14.021, 1337.42), 348 | ]); 349 | assert_eq!(multipoint, expected_multipoint); 350 | } 351 | 352 | #[test] 353 | fn test_polyline_macro() { 354 | let poly_1 = polyline!( 355 | [ 356 | {x: 1.0, y: 1.0}, 357 | {x: 2.0, y: 2.0} 358 | ], 359 | [ 360 | {x: 3.0, y: 3.0}, 361 | {x: 4.0, y: 4.0} 362 | ] 363 | ); 364 | 365 | let poly_2 = polyline!([(1.0, 1.0), (2.0, 2.0)], [(3.0, 3.0), (4.0, 4.0)]); 366 | 367 | let poly_3 = Polyline::with_parts(vec![ 368 | vec![Point::new(1.0, 1.0), Point::new(2.0, 2.0)], 369 | vec![Point::new(3.0, 3.0), Point::new(4.0, 4.0)], 370 | ]); 371 | assert_eq!(poly_1, poly_3); 372 | assert_eq!(poly_2, poly_3); 373 | } 374 | 375 | #[test] 376 | fn test_polyline_m_macro() { 377 | let poly_1 = polyline!( 378 | [ 379 | {x: 1.0, y: 1.0, m: 5.0}, 380 | {x: 2.0, y: 2.0, m: 42.1337} 381 | ], 382 | [ 383 | {x: 3.0, y: 3.0, m: 17.65}, 384 | {x: 4.0, y: 4.0, m: 454.4598} 385 | ] 386 | ); 387 | 388 | let poly_2 = polyline!( 389 | [(1.0, 1.0, 5.0), (2.0, 2.0, 42.1337)], 390 | [(3.0, 3.0, 17.65), (4.0, 4.0, 454.4598),] 391 | ); 392 | 393 | let poly_3 = PolylineM::with_parts(vec![ 394 | vec![PointM::new(1.0, 1.0, 5.0), PointM::new(2.0, 2.0, 42.1337)], 395 | vec![ 396 | PointM::new(3.0, 3.0, 17.65), 397 | PointM::new(4.0, 4.0, 454.4598), 398 | ], 399 | ]); 400 | 401 | assert_eq!(poly_1, poly_3); 402 | assert_eq!(poly_2, poly_3); 403 | } 404 | 405 | #[test] 406 | fn test_polyline_z_macro() { 407 | let poly_1 = polyline!( 408 | [ 409 | {x: 1.0, y: 1.0, z: 17.56, m: 5.0}, 410 | {x: 2.0, y: 2.0, z: 18.17, m: 42.1337} 411 | ], 412 | [ 413 | {x: 3.0, y: 3.0, z: 54.9, m: 17.65}, 414 | {x: 4.0, y: 4.0, z: 7.0, m: 454.4598} 415 | ] 416 | ); 417 | 418 | let poly_2 = polyline!( 419 | [(1.0, 1.0, 17.56, 5.0), (2.0, 2.0, 18.17, 42.1337)], 420 | [(3.0, 3.0, 54.9, 17.65), (4.0, 4.0, 7.0, 454.4598),] 421 | ); 422 | 423 | let poly_3 = PolylineZ::with_parts(vec![ 424 | vec![ 425 | PointZ::new(1.0, 1.0, 17.56, 5.0), 426 | PointZ::new(2.0, 2.0, 18.17, 42.1337), 427 | ], 428 | vec![ 429 | PointZ::new(3.0, 3.0, 54.9, 17.65), 430 | PointZ::new(4.0, 4.0, 7.0, 454.4598), 431 | ], 432 | ]); 433 | assert_eq!(poly_1, poly_3); 434 | assert_eq!(poly_2, poly_3); 435 | } 436 | 437 | #[test] 438 | fn test_polygon_macro() { 439 | let polygon_1 = polygon!( 440 | Outer( 441 | {x: 1.0, y: 1.0}, 442 | {x: 2.0, y: 2.0}, 443 | {x: 1.0, y: 1.0}, 444 | {x: 1.0, y: 0.0}, 445 | {x: 1.0, y: 1.0} 446 | ), 447 | Inner( 448 | {x: 1.0, y: 1.0}, 449 | {x: 1.0, y: 0.0}, 450 | {x: 1.0, y: 1.0}, 451 | {x: 2.0, y: 2.0}, 452 | {x: 1.0, y: 1.0}, 453 | ) 454 | ); 455 | 456 | let polygon_2 = polygon!( 457 | Outer((1.0, 1.0), (2.0, 2.0), (1.0, 1.0), (1.0, 0.0), (1.0, 1.0),), 458 | Inner((1.0, 1.0), (1.0, 0.0), (1.0, 1.0), (2.0, 2.0), (1.0, 1.0),) 459 | ); 460 | 461 | let polygon_3 = shapefile::Polygon::with_rings(vec![ 462 | PolygonRing::Outer(vec![ 463 | shapefile::Point::new(1.0, 1.0), 464 | shapefile::Point::new(2.0, 2.0), 465 | shapefile::Point::new(1.0, 1.0), 466 | shapefile::Point::new(1.0, 0.0), 467 | shapefile::Point::new(1.0, 1.0), 468 | ]), 469 | PolygonRing::Inner(vec![ 470 | shapefile::Point::new(1.0, 1.0), 471 | shapefile::Point::new(1.0, 0.0), 472 | shapefile::Point::new(1.0, 1.0), 473 | shapefile::Point::new(2.0, 2.0), 474 | shapefile::Point::new(1.0, 1.0), 475 | ]), 476 | ]); 477 | assert_eq!(polygon_1, polygon_3); 478 | assert_eq!(polygon_1, polygon_2); 479 | } 480 | 481 | #[test] 482 | fn test_polygon_m_macro() { 483 | let polygon_1 = polygon!( 484 | Outer( 485 | {x: 1.0, y: 1.0, m: 5.0}, 486 | {x: 2.0, y: 2.0, m: 42.1337}, 487 | {x: 1.0, y: 1.0, m: 5.0}, 488 | {x: 1.0, y: 0.0, m: 2.2}, 489 | {x: 1.0, y: 1.0, m: 5.0} 490 | ), 491 | Inner( 492 | {x: 1.0, y: 1.0, m: 1.0}, 493 | {x: 1.0, y: 0.0, m: 2.0}, 494 | {x: 1.0, y: 1.0, m: 1.1}, 495 | {x: 2.0, y: 2.0, m: 2.2}, 496 | {x: 1.0, y: 1.0, m: 1.0}, 497 | ) 498 | ); 499 | 500 | let polygon_2 = polygon!( 501 | Outer( 502 | (1.0, 1.0, 5.0), 503 | (2.0, 2.0, 42.1337), 504 | (1.0, 1.0, 5.0), 505 | (1.0, 0.0, 2.2), 506 | (1.0, 1.0, 5.0) 507 | ), 508 | Inner( 509 | (1.0, 1.0, 1.0), 510 | (1.0, 0.0, 2.0), 511 | (1.0, 1.0, 1.1), 512 | (2.0, 2.0, 2.2), 513 | (1.0, 1.0, 1.0), 514 | ) 515 | ); 516 | 517 | let polygon_3 = shapefile::PolygonM::with_rings(vec![ 518 | PolygonRing::Outer(vec![ 519 | shapefile::PointM::new(1.0, 1.0, 5.0), 520 | shapefile::PointM::new(2.0, 2.0, 42.1337), 521 | shapefile::PointM::new(1.0, 1.0, 5.0), 522 | shapefile::PointM::new(1.0, 0.0, 2.2), 523 | shapefile::PointM::new(1.0, 1.0, 5.0), 524 | ]), 525 | PolygonRing::Inner(vec![ 526 | shapefile::PointM::new(1.0, 1.0, 1.0), 527 | shapefile::PointM::new(1.0, 0.0, 2.0), 528 | shapefile::PointM::new(1.0, 1.0, 1.1), 529 | shapefile::PointM::new(2.0, 2.0, 2.2), 530 | shapefile::PointM::new(1.0, 1.0, 1.0), 531 | ]), 532 | ]); 533 | 534 | assert_eq!(polygon_1, polygon_3); 535 | assert_eq!(polygon_2, polygon_3); 536 | } 537 | 538 | #[test] 539 | fn test_polygon_z_macro() { 540 | let polygon_1 = polygon!( 541 | Outer( 542 | {x: 1.0, y: 1.0, z: 5.0, m: 5.0}, 543 | {x: 2.0, y: 2.0, z: 6.0, m: 42.1337}, 544 | {x: 1.0, y: 1.0, z: 7.0, m: 5.0}, 545 | {x: 1.0, y: 0.0, z: 8.0, m: 2.2}, 546 | {x: 1.0, y: 1.0, z: 5.0, m: 5.0} 547 | ), 548 | Inner( 549 | {x: 1.0, y: 1.0, z: 6.0, m: 1.0}, 550 | {x: 1.0, y: 0.0, z: 7.0,m: 2.0}, 551 | {x: 1.0, y: 1.0, z: 8.0, m: 1.1}, 552 | {x: 2.0, y: 2.0, z: 9.9, m: 2.2}, 553 | {x: 1.0, y: 1.0, z: 6.0, m: 1.0}, 554 | ) 555 | ); 556 | 557 | let polygon_2 = polygon!( 558 | Outer( 559 | (1.0, 1.0, 5.0, 5.0), 560 | (2.0, 2.0, 6.0, 42.1337), 561 | (1.0, 1.0, 7.0, 5.0), 562 | (1.0, 0.0, 8.0, 2.2), 563 | (1.0, 1.0, 5.0, 5.0) 564 | ), 565 | Inner( 566 | (1.0, 1.0, 6.0, 1.0), 567 | (1.0, 0.0, 7.0, 2.0), 568 | (1.0, 1.0, 8.0, 1.1), 569 | (2.0, 2.0, 9.9, 2.2), 570 | (1.0, 1.0, 6.0, 1.0), 571 | ) 572 | ); 573 | 574 | let polygon_3 = shapefile::PolygonZ::with_rings(vec![ 575 | PolygonRing::Outer(vec![ 576 | shapefile::PointZ::new(1.0, 1.0, 5.0, 5.0), 577 | shapefile::PointZ::new(2.0, 2.0, 6.0, 42.1337), 578 | shapefile::PointZ::new(1.0, 1.0, 7.0, 5.0), 579 | shapefile::PointZ::new(1.0, 0.0, 8.0, 2.2), 580 | shapefile::PointZ::new(1.0, 1.0, 5.0, 5.0), 581 | ]), 582 | PolygonRing::Inner(vec![ 583 | shapefile::PointZ::new(1.0, 1.0, 6.0, 1.0), 584 | shapefile::PointZ::new(1.0, 0.0, 7.0, 2.0), 585 | shapefile::PointZ::new(1.0, 1.0, 8.0, 1.1), 586 | shapefile::PointZ::new(2.0, 2.0, 9.9, 2.2), 587 | shapefile::PointZ::new(1.0, 1.0, 6.0, 1.0), 588 | ]), 589 | ]); 590 | 591 | assert_eq!(polygon_1, polygon_3); 592 | assert_eq!(polygon_2, polygon_3); 593 | } 594 | } 595 | -------------------------------------------------------------------------------- /src/record/mod.rs: -------------------------------------------------------------------------------- 1 | //! Shape records 2 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 3 | use std::fmt; 4 | use std::io::{Read, Write}; 5 | 6 | pub mod bbox; 7 | pub(crate) mod io; 8 | pub mod macros; 9 | pub mod multipatch; 10 | pub mod multipoint; 11 | pub mod point; 12 | pub mod polygon; 13 | pub mod polyline; 14 | pub mod traits; 15 | 16 | use super::{Error, ShapeType}; 17 | pub use bbox::{BBoxZ, GenericBBox}; 18 | pub use multipatch::{Multipatch, Patch}; 19 | pub use multipoint::{Multipoint, MultipointM, MultipointZ}; 20 | pub use point::{Point, PointM, PointZ}; 21 | pub use polygon::{Polygon, PolygonM, PolygonRing, PolygonZ}; 22 | pub use polyline::{Polyline, PolylineM, PolylineZ}; 23 | use traits::HasXY; 24 | 25 | #[cfg(feature = "geo-types")] 26 | use geo_types; 27 | 28 | /// Value inferior to this are considered as NO_DATA 29 | pub const NO_DATA: f64 = -10e38; 30 | 31 | fn is_no_data(val: f64) -> bool { 32 | val <= NO_DATA 33 | } 34 | 35 | /// Traits to be able to retrieve the ShapeType corresponding to the type 36 | pub trait HasShapeType { 37 | /// Returns the ShapeType 38 | fn shapetype() -> ShapeType; 39 | } 40 | 41 | /// Simple Trait to store the type of the shape 42 | pub trait ConcreteShape: Sized + HasShapeType {} 43 | 44 | pub trait ConcreteReadableShape: ConcreteShape { 45 | /// Function that actually reads the `ActualShape` from the source 46 | /// and returns it 47 | fn read_shape_content(source: &mut T, record_size: i32) -> Result; 48 | } 49 | 50 | /// Trait implemented by all the Shapes that can be read 51 | pub trait ReadableShape: Sized { 52 | fn read_from(source: &mut T, record_size: i32) -> Result; 53 | } 54 | 55 | impl ReadableShape for S { 56 | fn read_from(mut source: &mut T, mut record_size: i32) -> Result { 57 | let shapetype = ShapeType::read_from(&mut source)?; 58 | record_size -= std::mem::size_of::() as i32; 59 | if shapetype == Self::shapetype() { 60 | S::read_shape_content(&mut source, record_size) 61 | } else { 62 | Err(Error::MismatchShapeType { 63 | requested: Self::shapetype(), 64 | actual: shapetype, 65 | }) 66 | } 67 | } 68 | } 69 | 70 | /// Trait implemented by all Shapes that can be written 71 | pub trait WritableShape { 72 | /// Returns the size in bytes that the Shapes will take once written. 73 | /// Does _not_ include the shapetype 74 | fn size_in_bytes(&self) -> usize; 75 | 76 | /// Writes the shape to the dest 77 | fn write_to(&self, dest: &mut T) -> Result<(), Error>; 78 | } 79 | 80 | pub trait EsriShape: HasShapeType + WritableShape { 81 | fn x_range(&self) -> [f64; 2]; 82 | fn y_range(&self) -> [f64; 2]; 83 | /// Should return the Z range of this shape 84 | fn z_range(&self) -> [f64; 2] { 85 | [0.0, 0.0] 86 | } 87 | /// Should return the M range of this shape 88 | fn m_range(&self) -> [f64; 2] { 89 | [0.0, 0.0] 90 | } 91 | } 92 | 93 | pub(crate) fn is_part_closed(points: &[PointType]) -> bool { 94 | if let (Some(first), Some(last)) = (points.first(), points.last()) { 95 | first == last 96 | } else { 97 | false 98 | } 99 | } 100 | 101 | pub(crate) fn close_points_if_not_already( 102 | points: &mut Vec, 103 | ) { 104 | if !is_part_closed(points) { 105 | if let Some(point) = points.first().copied() { 106 | points.push(point) 107 | } 108 | } 109 | } 110 | 111 | #[derive(Eq, PartialEq, Debug)] 112 | pub(crate) enum RingType { 113 | OuterRing, 114 | InnerRing, 115 | } 116 | 117 | /// Given the points, check if they represent an outer ring of a polygon 118 | /// 119 | /// As per ESRI's Shapefile 1998 whitepaper: 120 | /// ` 121 | /// The order of vertices or orientation for a ring indicates which side of the ring 122 | /// is the interior of the polygon. 123 | /// The neighborhood to the right of an observer walking along 124 | /// the ring in vertex order is the neighborhood inside the polygon. 125 | /// Vertices of rings defining holes in polygons are in a counterclockwise direction. 126 | /// Vertices for a single, ringed polygon are, therefore, always in clockwise order. 127 | /// ` 128 | /// 129 | /// Inner Rings defines holes -> points are in counterclockwise order 130 | /// Outer Rings's points are un clockwise order 131 | /// 132 | /// https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order/1180256#1180256 133 | pub(crate) fn ring_type_from_points_ordering(points: &[PointType]) -> RingType { 134 | let area = points 135 | .windows(2) 136 | .map(|pts| (pts[1].x() - pts[0].x()) * (pts[1].y() + pts[0].y())) 137 | .sum::() 138 | / 2.0f64; 139 | 140 | if area < 0.0 { 141 | RingType::InnerRing 142 | } else { 143 | RingType::OuterRing 144 | } 145 | } 146 | 147 | /// enum of Shapes that can be read or written to a shapefile 148 | /// 149 | /// # geo-types 150 | /// 151 | /// `shapefile::Shape` and `geo_types::Geometry` can be converted from one to another, 152 | /// however this conversion is not infallible so it is done using `TryFrom` 153 | /// 154 | /// ``` 155 | /// # #[cfg(feature = "geo-types")] 156 | /// # fn main() -> Result<(), shapefile::Error>{ 157 | /// use std::convert::TryFrom; 158 | /// use shapefile::Shape; 159 | /// let mut shapes = shapefile::read_shapes("tests/data/line.shp")?; 160 | /// let last_shape = shapes.pop().unwrap(); 161 | /// let geometry = geo_types::Geometry::::try_from(last_shape); 162 | /// 163 | /// assert_eq!(geometry.is_ok(), true); 164 | /// assert_eq!(geo_types::Geometry::::try_from(Shape::NullShape).is_err(), true); 165 | /// # Ok(()) 166 | /// # } 167 | /// # #[cfg(not(feature = "geo-types"))] 168 | /// # fn main() {} 169 | /// ``` 170 | /// 171 | pub enum Shape { 172 | NullShape, 173 | Point(Point), 174 | PointM(PointM), 175 | PointZ(PointZ), 176 | Polyline(Polyline), 177 | PolylineM(PolylineM), 178 | PolylineZ(PolylineZ), 179 | Polygon(Polygon), 180 | PolygonM(PolygonM), 181 | PolygonZ(PolygonZ), 182 | Multipoint(Multipoint), 183 | MultipointM(MultipointM), 184 | MultipointZ(MultipointZ), 185 | Multipatch(Multipatch), 186 | } 187 | 188 | impl HasShapeType for Shape { 189 | fn shapetype() -> ShapeType { 190 | ShapeType::Point 191 | } 192 | } 193 | 194 | impl ReadableShape for Shape { 195 | fn read_from(mut source: &mut T, mut record_size: i32) -> Result { 196 | let shapetype = ShapeType::read_from(&mut source)?; 197 | record_size -= std::mem::size_of::() as i32; 198 | let shape = match shapetype { 199 | ShapeType::Polyline => { 200 | Shape::Polyline(Polyline::read_shape_content(&mut source, record_size)?) 201 | } 202 | ShapeType::PolylineM => { 203 | Shape::PolylineM(PolylineM::read_shape_content(&mut source, record_size)?) 204 | } 205 | ShapeType::PolylineZ => { 206 | Shape::PolylineZ(PolylineZ::read_shape_content(&mut source, record_size)?) 207 | } 208 | ShapeType::Point => Shape::Point(Point::read_shape_content(&mut source, record_size)?), 209 | ShapeType::PointM => { 210 | Shape::PointM(PointM::read_shape_content(&mut source, record_size)?) 211 | } 212 | ShapeType::PointZ => { 213 | Shape::PointZ(PointZ::read_shape_content(&mut source, record_size)?) 214 | } 215 | ShapeType::Polygon => { 216 | Shape::Polygon(Polygon::read_shape_content(&mut source, record_size)?) 217 | } 218 | ShapeType::PolygonM => { 219 | Shape::PolygonM(PolygonM::read_shape_content(&mut source, record_size)?) 220 | } 221 | ShapeType::PolygonZ => { 222 | Shape::PolygonZ(PolygonZ::read_shape_content(&mut source, record_size)?) 223 | } 224 | ShapeType::Multipoint => { 225 | Shape::Multipoint(Multipoint::read_shape_content(&mut source, record_size)?) 226 | } 227 | ShapeType::MultipointM => { 228 | Shape::MultipointM(MultipointM::read_shape_content(&mut source, record_size)?) 229 | } 230 | ShapeType::MultipointZ => { 231 | Shape::MultipointZ(MultipointZ::read_shape_content(&mut source, record_size)?) 232 | } 233 | ShapeType::Multipatch => { 234 | Shape::Multipatch(Multipatch::read_shape_content(&mut source, record_size)?) 235 | } 236 | ShapeType::NullShape => Shape::NullShape, 237 | }; 238 | Ok(shape) 239 | } 240 | } 241 | 242 | impl Shape { 243 | /// Returns the shapetype 244 | pub fn shapetype(&self) -> ShapeType { 245 | match self { 246 | Shape::Polyline(_) => ShapeType::Polyline, 247 | Shape::PolylineM(_) => ShapeType::PolylineM, 248 | Shape::PolylineZ(_) => ShapeType::PolylineZ, 249 | Shape::Point(_) => ShapeType::Point, 250 | Shape::PointM(_) => ShapeType::PointM, 251 | Shape::PointZ(_) => ShapeType::PointZ, 252 | Shape::Polygon(_) => ShapeType::Polygon, 253 | Shape::PolygonM(_) => ShapeType::PolygonM, 254 | Shape::PolygonZ(_) => ShapeType::PolygonZ, 255 | Shape::Multipoint(_) => ShapeType::Multipoint, 256 | Shape::MultipointM(_) => ShapeType::Multipoint, 257 | Shape::MultipointZ(_) => ShapeType::Multipoint, 258 | Shape::Multipatch(_) => ShapeType::Multipatch, 259 | Shape::NullShape => ShapeType::NullShape, 260 | } 261 | } 262 | } 263 | 264 | impl fmt::Display for Shape { 265 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 266 | write!(f, "Shape::")?; 267 | match self { 268 | Shape::Polyline(shp) => write!(f, "{}", shp), 269 | Shape::PolylineM(shp) => write!(f, "{}", shp), 270 | Shape::PolylineZ(shp) => write!(f, "{}", shp), 271 | Shape::Point(shp) => write!(f, "{}", shp), 272 | Shape::PointM(shp) => write!(f, "{}", shp), 273 | Shape::PointZ(shp) => write!(f, "{}", shp), 274 | Shape::Polygon(shp) => write!(f, "{}", shp), 275 | Shape::PolygonM(shp) => write!(f, "{}", shp), 276 | Shape::PolygonZ(shp) => write!(f, "{}", shp), 277 | Shape::Multipoint(shp) => write!(f, "{}", shp), 278 | Shape::MultipointM(shp) => write!(f, "{}", shp), 279 | Shape::MultipointZ(shp) => write!(f, "{}", shp), 280 | Shape::Multipatch(shp) => write!(f, "{}", shp), 281 | Shape::NullShape => write!(f, "NullShape"), 282 | } 283 | } 284 | } 285 | 286 | /// Header of a shape record, present before any shape record 287 | #[derive(Debug, Copy, Clone)] 288 | pub(crate) struct RecordHeader { 289 | pub record_number: i32, 290 | pub record_size: i32, 291 | } 292 | 293 | impl RecordHeader { 294 | pub(crate) const SIZE: usize = 2 * std::mem::size_of::(); 295 | 296 | pub fn read_from(source: &mut T) -> Result { 297 | let record_number = source.read_i32::()?; 298 | let record_size = source.read_i32::()?; 299 | Ok(RecordHeader { 300 | record_number, 301 | record_size, 302 | }) 303 | } 304 | 305 | pub fn write_to(&self, dest: &mut T) -> Result<(), std::io::Error> { 306 | dest.write_i32::(self.record_number)?; 307 | dest.write_i32::(self.record_size)?; 308 | Ok(()) 309 | } 310 | } 311 | 312 | /// Function that can converts a `Vec` to a vector of any real struct 313 | /// (ie [Polyline](polyline/type.Polyline.html), [Multipatch](multipatch/struct.Multipatch.html), etc) 314 | /// if all the `Shapes` in the `Vec` are of the correct corresponding variant. 315 | /// 316 | /// # Examples 317 | /// 318 | /// ``` 319 | /// use shapefile::{Polyline, Multipoint, Point, Shape}; 320 | /// use shapefile::convert_shapes_to_vec_of; 321 | /// 322 | /// // Build a Vec with only polylines in it 323 | /// let points = vec![Point::default(), Point::default()]; 324 | /// let shapes = vec![ 325 | /// Shape::from(Polyline::new(points.clone())), 326 | /// Shape::from(Polyline::new(points)), 327 | /// ]; 328 | /// 329 | /// // try a conversion to the wrong type 330 | /// assert_eq!(convert_shapes_to_vec_of::(shapes).is_ok(), false); 331 | /// ``` 332 | /// 333 | /// ``` 334 | /// # fn main() -> Result<(), shapefile::Error> { 335 | /// use shapefile::{convert_shapes_to_vec_of, MultipointZ}; 336 | /// let shapes = shapefile::read_shapes("tests/data/multipointz.shp")?; 337 | /// let multipoints = convert_shapes_to_vec_of::(shapes); 338 | /// assert_eq!(multipoints.is_ok(), true); 339 | /// # Ok(()) 340 | /// # } 341 | /// ``` 342 | pub fn convert_shapes_to_vec_of(shapes: Vec) -> Result, Error> 343 | where 344 | S: TryFrom, 345 | Error: From<>::Error>, 346 | { 347 | let mut concrete_shapes = Vec::::with_capacity(shapes.len()); 348 | for shape in shapes { 349 | let concrete = S::try_from(shape)?; 350 | concrete_shapes.push(concrete); 351 | } 352 | Ok(concrete_shapes) 353 | } 354 | 355 | /// Macro to have less boiler plate code to write just to implement 356 | /// the ConcreteShape Trait 357 | macro_rules! impl_concrete_shape_for { 358 | ($ConcreteType:ident) => { 359 | impl ConcreteShape for $ConcreteType {} 360 | }; 361 | } 362 | 363 | /// macro that implements the From Trait for the Shape enum 364 | /// where T is any of the ConcreteShape 365 | macro_rules! impl_from_concrete_shape { 366 | ($ConcreteShape:ident=>Shape::$ShapeEnumVariant:ident) => { 367 | impl From<$ConcreteShape> for Shape { 368 | fn from(concrete: $ConcreteShape) -> Self { 369 | Shape::$ShapeEnumVariant(concrete) 370 | } 371 | } 372 | }; 373 | } 374 | 375 | /// macro to implement the TryFrom trait 376 | macro_rules! impl_try_from_shape { 377 | (Shape::$ShapeEnumVariant:ident=>$ConcreteShape:ident) => { 378 | impl TryFrom for $ConcreteShape { 379 | type Error = Error; 380 | fn try_from(shape: Shape) -> Result { 381 | match shape { 382 | Shape::$ShapeEnumVariant(shp) => Ok(shp), 383 | _ => Err(Error::MismatchShapeType { 384 | requested: Self::shapetype(), 385 | actual: shape.shapetype(), 386 | }), 387 | } 388 | } 389 | } 390 | }; 391 | } 392 | 393 | macro_rules! impl_to_way_conversion { 394 | (Shape::$ShapeEnumVariant:ident<=>$ConcreteShape:ident) => { 395 | impl_try_from_shape!(Shape::$ShapeEnumVariant => $ConcreteShape); 396 | impl_from_concrete_shape!($ConcreteShape => Shape::$ShapeEnumVariant); 397 | }; 398 | } 399 | 400 | impl_concrete_shape_for!(Point); 401 | impl_concrete_shape_for!(PointM); 402 | impl_concrete_shape_for!(PointZ); 403 | impl_concrete_shape_for!(Polyline); 404 | impl_concrete_shape_for!(PolylineM); 405 | impl_concrete_shape_for!(PolylineZ); 406 | impl_concrete_shape_for!(Polygon); 407 | impl_concrete_shape_for!(PolygonM); 408 | impl_concrete_shape_for!(PolygonZ); 409 | impl_concrete_shape_for!(Multipoint); 410 | impl_concrete_shape_for!(MultipointM); 411 | impl_concrete_shape_for!(MultipointZ); 412 | impl_concrete_shape_for!(Multipatch); 413 | 414 | impl_to_way_conversion!(Shape::Point <=> Point); 415 | impl_to_way_conversion!(Shape::PointM <=> PointM); 416 | impl_to_way_conversion!(Shape::PointZ <=> PointZ); 417 | impl_to_way_conversion!(Shape::Polyline <=> Polyline); 418 | impl_to_way_conversion!(Shape::PolylineM <=> PolylineM); 419 | impl_to_way_conversion!(Shape::PolylineZ <=> PolylineZ); 420 | impl_to_way_conversion!(Shape::Polygon <=> Polygon); 421 | impl_to_way_conversion!(Shape::PolygonM <=> PolygonM); 422 | impl_to_way_conversion!(Shape::PolygonZ <=> PolygonZ); 423 | impl_to_way_conversion!(Shape::Multipoint <=> Multipoint); 424 | impl_to_way_conversion!(Shape::MultipointM <=> MultipointM); 425 | impl_to_way_conversion!(Shape::MultipointZ <=> MultipointZ); 426 | impl_to_way_conversion!(Shape::Multipatch <=> Multipatch); 427 | 428 | /// Tries to convert a shapefile's Shape into a geo_types::Geometry 429 | /// 430 | /// This conversion can fail because the conversion of shapefile's polygons & multipatch into 431 | /// their geo_types counter parts can fail. And the NullShape has no equivalent Geometry; 432 | #[cfg(feature = "geo-types")] 433 | impl TryFrom for geo_types::Geometry { 434 | type Error = &'static str; 435 | 436 | fn try_from(shape: Shape) -> Result { 437 | use geo_types::Geometry; 438 | match shape { 439 | Shape::NullShape => Err("Cannot convert NullShape into any geo_types Geometry"), 440 | Shape::Point(point) => Ok(Geometry::Point(geo_types::Point::from(point))), 441 | Shape::PointM(point) => Ok(Geometry::Point(geo_types::Point::from(point))), 442 | Shape::PointZ(point) => Ok(Geometry::Point(geo_types::Point::from(point))), 443 | Shape::Polyline(polyline) => Ok(Geometry::MultiLineString( 444 | geo_types::MultiLineString::::from(polyline), 445 | )), 446 | Shape::PolylineM(polyline) => Ok(Geometry::MultiLineString( 447 | geo_types::MultiLineString::::from(polyline), 448 | )), 449 | Shape::PolylineZ(polyline) => Ok(Geometry::MultiLineString( 450 | geo_types::MultiLineString::::from(polyline), 451 | )), 452 | Shape::Polygon(polygon) => Ok(Geometry::MultiPolygon( 453 | geo_types::MultiPolygon::::from(polygon), 454 | )), 455 | Shape::PolygonM(polygon) => Ok(Geometry::MultiPolygon( 456 | geo_types::MultiPolygon::::from(polygon), 457 | )), 458 | Shape::PolygonZ(polygon) => Ok(Geometry::MultiPolygon( 459 | geo_types::MultiPolygon::::from(polygon), 460 | )), 461 | Shape::Multipoint(multipoint) => Ok(Geometry::MultiPoint( 462 | geo_types::MultiPoint::::from(multipoint), 463 | )), 464 | Shape::MultipointM(multipoint) => Ok(Geometry::MultiPoint( 465 | geo_types::MultiPoint::::from(multipoint), 466 | )), 467 | Shape::MultipointZ(multipoint) => Ok(Geometry::MultiPoint( 468 | geo_types::MultiPoint::::from(multipoint), 469 | )), 470 | Shape::Multipatch(multipatch) => { 471 | geo_types::MultiPolygon::::try_from(multipatch).map(Geometry::MultiPolygon) 472 | } 473 | } 474 | } 475 | } 476 | 477 | /// Converts a Geometry to a Shape 478 | /// 479 | /// Since all Geometries are in 2D, the resulting shape will be 2D 480 | /// (Polygon, Polyline, etc and not PolylineM, PolylineZ, etc) 481 | /// 482 | /// Fails if the geometry is a GeometryCollection, Rect, or Triangle 483 | #[cfg(feature = "geo-types")] 484 | impl TryFrom> for Shape { 485 | type Error = &'static str; 486 | fn try_from(geometry: geo_types::Geometry) -> Result { 487 | match geometry { 488 | geo_types::Geometry::Point(point) => Ok(Shape::Point(point.into())), 489 | geo_types::Geometry::Line(line) => Ok(Shape::Polyline(line.into())), 490 | geo_types::Geometry::LineString(polyline) => Ok(Shape::Polyline(polyline.into())), 491 | geo_types::Geometry::Polygon(polygon) => Ok(Shape::Polygon(polygon.into())), 492 | geo_types::Geometry::MultiPoint(multipoint) => Ok(Shape::Multipoint(multipoint.into())), 493 | geo_types::Geometry::MultiLineString(multi_linestring) => { 494 | Ok(Shape::Polyline(multi_linestring.into())) 495 | } 496 | geo_types::Geometry::MultiPolygon(multi_polygon) => { 497 | Ok(Shape::Polygon(multi_polygon.into())) 498 | } 499 | geo_types::Geometry::GeometryCollection(_) => { 500 | Err("Cannot convert geo_types::GeometryCollection into a Shape") 501 | } 502 | #[allow(unreachable_patterns)] // Unreachable before geo-types 0.6.0 503 | _ => { 504 | // New geometries Rect(_) and Triangle(_) added in 0.6.0 505 | Err("Cannot convert unrecognized Geometry type into a Shape") 506 | } 507 | } 508 | } 509 | } 510 | 511 | #[cfg(test)] 512 | mod tests { 513 | use super::*; 514 | 515 | #[test] 516 | fn convert_to_vec_of_poly_err() { 517 | let points = vec![Point::default(), Point::default()]; 518 | let shapes = vec![ 519 | Shape::Point(Point::default()), 520 | Shape::Polyline(Polyline::new(points)), 521 | ]; 522 | assert!(convert_shapes_to_vec_of::(shapes).is_err()); 523 | } 524 | 525 | #[test] 526 | fn convert_to_vec_of_point_err() { 527 | let points = vec![Point::default(), Point::default()]; 528 | let shapes = vec![ 529 | Shape::Point(Point::default()), 530 | Shape::Polyline(Polyline::new(points)), 531 | ]; 532 | assert!(convert_shapes_to_vec_of::(shapes).is_err()); 533 | } 534 | 535 | #[test] 536 | fn convert_to_vec_of_poly_ok() { 537 | let points = vec![Point::default(), Point::default()]; 538 | 539 | let shapes = vec![ 540 | Shape::from(Polyline::new(points.clone())), 541 | Shape::from(Polyline::new(points)), 542 | ]; 543 | 544 | assert!(convert_shapes_to_vec_of::(shapes).is_ok()); 545 | } 546 | 547 | #[test] 548 | fn convert_to_vec_of_point_ok() { 549 | let shapes = vec![ 550 | Shape::Point(Point::default()), 551 | Shape::Point(Point::default()), 552 | ]; 553 | assert!(convert_shapes_to_vec_of::(shapes).is_ok()); 554 | } 555 | 556 | #[test] 557 | fn test_vertices_order() { 558 | let mut points = vec![ 559 | Point::new(0.0, 0.0), 560 | Point::new(1.0, 0.0), 561 | Point::new(1.0, 1.0), 562 | Point::new(0.0, 1.0), 563 | ]; 564 | 565 | assert_eq!(ring_type_from_points_ordering(&points), RingType::InnerRing); 566 | points.reverse(); 567 | assert_eq!(ring_type_from_points_ordering(&points), RingType::OuterRing); 568 | } 569 | } 570 | -------------------------------------------------------------------------------- /src/record/multipatch.rs: -------------------------------------------------------------------------------- 1 | //! Module for the Multipatch shape 2 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; 3 | 4 | use std::fmt; 5 | use std::io::{Read, Write}; 6 | use std::mem::size_of; 7 | 8 | use super::io::*; 9 | use super::ConcreteReadableShape; 10 | use super::{close_points_if_not_already, GenericBBox}; 11 | use super::{Error, ShapeType}; 12 | use super::{EsriShape, HasShapeType, Point, PointZ, WritableShape}; 13 | 14 | #[cfg(feature = "geo-types")] 15 | use geo_types; 16 | #[cfg(feature = "geo-types")] 17 | use std::convert::TryFrom; 18 | 19 | #[derive(Debug, Copy, Clone, PartialEq)] 20 | enum PatchType { 21 | TriangleStrip, 22 | TriangleFan, 23 | OuterRing, 24 | InnerRing, 25 | FirstRing, 26 | Ring, 27 | } 28 | 29 | impl PatchType { 30 | pub fn read_from(source: &mut T) -> Result { 31 | let code = source.read_i32::()?; 32 | Self::from(code).ok_or(Error::InvalidPatchType(code)) 33 | } 34 | 35 | pub fn from(code: i32) -> Option { 36 | match code { 37 | 0 => Some(PatchType::TriangleStrip), 38 | 1 => Some(PatchType::TriangleFan), 39 | 2 => Some(PatchType::OuterRing), 40 | 3 => Some(PatchType::InnerRing), 41 | 4 => Some(PatchType::FirstRing), 42 | 5 => Some(PatchType::Ring), 43 | _ => None, 44 | } 45 | } 46 | } 47 | 48 | #[derive(Debug, Clone, PartialEq)] 49 | pub enum Patch { 50 | /// A linked strip of triangles, where every vertex 51 | /// (after the first two)completes a new triangle. 52 | /// 53 | /// A new triangle is always formed by connecting 54 | /// the new vertex with its two immediate predecessors 55 | TriangleStrip(Vec), 56 | /// A linked fan of triangles, 57 | /// where every vertex (after the first two) completes a new triangle. 58 | /// 59 | /// A new triangle is always formed by connecting 60 | /// the new vertex with its immediate predecessor 61 | /// and the first vertex of the part. 62 | TriangleFan(Vec), 63 | /// The outer ring of a polygon. 64 | OuterRing(Vec), 65 | /// A hole of a polygon 66 | InnerRing(Vec), 67 | /// The first ring of a polygon of an unspecified type 68 | FirstRing(Vec), 69 | /// A ring of a polygon of an unspecified type 70 | Ring(Vec), 71 | } 72 | 73 | impl Patch { 74 | /// Returns the slice of points contained within the patch 75 | #[inline] 76 | pub fn points(&self) -> &[PointZ] { 77 | match self { 78 | Patch::TriangleStrip(points) => points, 79 | Patch::TriangleFan(points) => points, 80 | Patch::OuterRing(points) => points, 81 | Patch::InnerRing(points) => points, 82 | Patch::FirstRing(points) => points, 83 | Patch::Ring(points) => points, 84 | } 85 | } 86 | } 87 | 88 | impl AsRef<[PointZ]> for Patch { 89 | fn as_ref(&self) -> &[PointZ] { 90 | self.points() 91 | } 92 | } 93 | 94 | // TODO all the checks described at page 24/34 95 | /// Shapefile's Multipatch shape (p 24/34) 96 | /// 97 | /// The following things are important with Multipatch shape: 98 | /// 1) Ring types must be closed 99 | /// **(the various constructors will close the rings if you did not close them yourself)** 100 | /// 2) InnerRings must follow their OuterRings (**this is not checked**) 101 | /// 3) Parts must not intersects or penetrate each others (**this is not checked**) 102 | /// 4) The points organization of [`TriangleStrip`] and [`TriangleFan`] is **not checked** 103 | /// 104 | /// [`TriangleStrip`]: enum.Patch.html#variant.TriangleStrip 105 | /// [`TriangleFan`]: enum.Patch.html#variant.TriangleFan 106 | #[derive(Debug, PartialEq, Clone)] 107 | pub struct Multipatch { 108 | bbox: GenericBBox, 109 | patches: Vec, 110 | } 111 | 112 | impl Multipatch { 113 | /// Creates a Multipatch with one patch 114 | /// 115 | /// The constructor closes rings patch 116 | /// 117 | /// # Examples 118 | /// 119 | /// ``` 120 | /// use shapefile::{PointZ, Multipatch, NO_DATA, Patch}; 121 | /// let points = vec![ 122 | /// PointZ::new(0.0, 0.0, 0.0, NO_DATA), 123 | /// PointZ::new(0.0, 1.0, 0.0, NO_DATA), 124 | /// PointZ::new(1.0, 1.0, 0.0, NO_DATA), 125 | /// PointZ::new(1.0, 0.0, 0.0, NO_DATA), 126 | /// ]; 127 | /// let multip = Multipatch::new(Patch::OuterRing(points)); 128 | /// ``` 129 | pub fn new(patch: Patch) -> Self { 130 | Self::with_parts(vec![patch]) 131 | } 132 | 133 | /// Creates a Multipatch with multiple patches 134 | /// 135 | /// Closes any patch part that is a ring 136 | /// 137 | /// # Example 138 | /// 139 | /// ``` 140 | /// use shapefile::{PointZ, Multipatch, NO_DATA, Patch}; 141 | /// let multipatch = Multipatch::with_parts(vec![ 142 | /// Patch::OuterRing(vec![ 143 | /// PointZ::new(0.0, 0.0, 0.0, NO_DATA), 144 | /// PointZ::new(0.0, 4.0, 0.0, NO_DATA), 145 | /// PointZ::new(4.0, 4.0, 0.0, NO_DATA), 146 | /// PointZ::new(4.0, 0.0, 0.0, NO_DATA), 147 | /// ]), 148 | /// Patch::InnerRing(vec![ 149 | /// PointZ::new(0.0, 0.0, 0.0, NO_DATA), 150 | /// PointZ::new(0.0, 2.0, 0.0, NO_DATA), 151 | /// PointZ::new(2.0, 2.0, 0.0, NO_DATA), 152 | /// PointZ::new(2.0, 0.0, 0.0, NO_DATA), 153 | /// ]) 154 | /// ]); 155 | /// ``` 156 | pub fn with_parts(mut patches: Vec) -> Self { 157 | for patch in patches.iter_mut() { 158 | match patch { 159 | Patch::TriangleStrip(_) => {} 160 | Patch::TriangleFan(_) => {} 161 | Patch::OuterRing(points) => close_points_if_not_already(points), 162 | Patch::InnerRing(points) => close_points_if_not_already(points), 163 | Patch::FirstRing(points) => close_points_if_not_already(points), 164 | Patch::Ring(points) => close_points_if_not_already(points), 165 | } 166 | } 167 | let mut bbox = GenericBBox::::from_points(patches[0].points()); 168 | for patch in &patches[1..] { 169 | bbox.grow_from_points(patch.points()); 170 | } 171 | 172 | Self { bbox, patches } 173 | } 174 | 175 | /// Returns the bounding box of the points contained in this multipatch 176 | #[inline] 177 | pub fn bbox(&self) -> &GenericBBox { 178 | &self.bbox 179 | } 180 | 181 | /// Returns a reference to the patches of the Multipatch Shape 182 | #[inline] 183 | pub fn patches(&self) -> &Vec { 184 | &self.patches 185 | } 186 | 187 | /// Returns a reference to the patch at given index 188 | #[inline] 189 | pub fn patch(&self, index: usize) -> Option<&Patch> { 190 | self.patches.get(index) 191 | } 192 | 193 | /// Consumes the shape and returns the patches 194 | #[inline] 195 | pub fn into_inner(self) -> Vec { 196 | self.patches 197 | } 198 | 199 | #[inline] 200 | pub fn total_point_count(&self) -> usize { 201 | self.patches.iter().map(|patch| patch.points().len()).sum() 202 | } 203 | 204 | pub(crate) fn size_of_record(num_points: i32, num_parts: i32, is_m_used: bool) -> usize { 205 | let mut size = 0usize; 206 | size += 4 * size_of::(); // BBOX 207 | size += size_of::(); // num parts 208 | size += size_of::(); // num points 209 | size += size_of::() * num_parts as usize; // parts 210 | size += size_of::() * num_parts as usize; // parts type 211 | size += size_of::() * num_points as usize; 212 | size += 2 * size_of::(); // mandatory Z Range 213 | size += size_of::() * num_points as usize; // mandatory Z 214 | 215 | if is_m_used { 216 | size += 2 * size_of::(); // Optional M range 217 | size += size_of::() * num_points as usize; // Optional M 218 | } 219 | size 220 | } 221 | } 222 | 223 | impl fmt::Display for Multipatch { 224 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 225 | write!(f, "Multipatch({} patches)", self.patches.len()) 226 | } 227 | } 228 | 229 | impl HasShapeType for Multipatch { 230 | fn shapetype() -> ShapeType { 231 | ShapeType::Multipatch 232 | } 233 | } 234 | 235 | impl ConcreteReadableShape for Multipatch { 236 | fn read_shape_content(source: &mut T, record_size: i32) -> Result { 237 | let reader = MultiPartShapeReader::::new(source)?; 238 | 239 | let record_size_with_m = 240 | Self::size_of_record(reader.num_points, reader.num_parts, true) as i32; 241 | let record_size_without_m = 242 | Self::size_of_record(reader.num_points, reader.num_parts, false) as i32; 243 | 244 | if (record_size != record_size_with_m) & (record_size != record_size_without_m) { 245 | Err(Error::InvalidShapeRecordSize) 246 | } else { 247 | let mut patch_types = vec![PatchType::Ring; reader.num_parts as usize]; 248 | let mut patches = Vec::::with_capacity(reader.num_parts as usize); 249 | for i in 0..reader.num_parts { 250 | patch_types[i as usize] = PatchType::read_from(reader.source)?; 251 | } 252 | let (bbox, patches_points) = reader 253 | .read_xy() 254 | .and_then(|rdr| rdr.read_zs()) 255 | .and_then(|rdr| rdr.read_ms_if(record_size == record_size_with_m)) 256 | .map_err(Error::IoError) 257 | .map(|rdr| (rdr.bbox, rdr.parts))?; 258 | 259 | for (patch_type, points) in patch_types.iter().zip(patches_points) { 260 | let patch = match patch_type { 261 | PatchType::TriangleStrip => Patch::TriangleStrip(points), 262 | PatchType::TriangleFan => Patch::TriangleFan(points), 263 | PatchType::OuterRing => Patch::OuterRing(points), 264 | PatchType::InnerRing => Patch::InnerRing(points), 265 | PatchType::FirstRing => Patch::FirstRing(points), 266 | PatchType::Ring => Patch::Ring(points), 267 | }; 268 | patches.push(patch); 269 | } 270 | Ok(Self { bbox, patches }) 271 | } 272 | } 273 | } 274 | 275 | impl WritableShape for Multipatch { 276 | fn size_in_bytes(&self) -> usize { 277 | let mut size = 0usize; 278 | size += 4 * size_of::(); 279 | size += size_of::(); 280 | size += size_of::(); 281 | size += size_of::() * self.patches.len(); 282 | size += size_of::() * self.patches.len(); 283 | size += 4 * size_of::() * self.total_point_count(); 284 | size += 2 * size_of::(); 285 | size += 2 * size_of::(); 286 | size 287 | } 288 | 289 | fn write_to(&self, dest: &mut T) -> Result<(), Error> { 290 | let parts_iter = self.patches.iter().map(|patch| patch.points()); 291 | let writer = MultiPartShapeWriter::new(&self.bbox, parts_iter, dest); 292 | writer 293 | .write_bbox_xy() 294 | .and_then(|wrt| wrt.write_num_parts()) 295 | .and_then(|wrt| wrt.write_num_points()) 296 | .and_then(|wrt| wrt.write_parts_array()) 297 | .and_then(|wrt| { 298 | for patch in self.patches.iter() { 299 | match patch { 300 | Patch::TriangleStrip(_) => wrt.dst.write_i32::(0)?, 301 | Patch::TriangleFan(_) => wrt.dst.write_i32::(1)?, 302 | Patch::OuterRing(_) => wrt.dst.write_i32::(2)?, 303 | Patch::InnerRing(_) => wrt.dst.write_i32::(3)?, 304 | Patch::FirstRing(_) => wrt.dst.write_i32::(4)?, 305 | Patch::Ring(_) => wrt.dst.write_i32::(5)?, 306 | } 307 | } 308 | Ok(wrt) 309 | }) 310 | .and_then(|wrt| wrt.write_xy()) 311 | .and_then(|wrt| wrt.write_bbox_z_range()) 312 | .and_then(|wrt| wrt.write_zs()) 313 | .and_then(|wrt| wrt.write_bbox_m_range()) 314 | .and_then(|wrt| wrt.write_ms()) 315 | .map_err(Error::IoError) 316 | .map(|_wrt| {}) 317 | } 318 | } 319 | 320 | impl EsriShape for Multipatch { 321 | fn x_range(&self) -> [f64; 2] { 322 | self.bbox.x_range() 323 | } 324 | 325 | fn y_range(&self) -> [f64; 2] { 326 | self.bbox.y_range() 327 | } 328 | 329 | fn z_range(&self) -> [f64; 2] { 330 | self.bbox.z_range() 331 | } 332 | 333 | fn m_range(&self) -> [f64; 2] { 334 | self.bbox.m_range() 335 | } 336 | } 337 | /// Converts a Multipatch to Multipolygon 338 | /// 339 | /// For simplicity,reasons, Triangle Fan & Triangle Strip are considered 340 | /// to be valid polygons 341 | /// ` 342 | /// When the individual types of rings in a collection of rings representing a polygonal patch with holes 343 | /// are unknown, the sequence must start with First Ring, 344 | /// followed by a number of Rings. A sequence of Rings not preceded by an First Ring 345 | /// is treated as a sequence of Outer Rings without holes. 346 | /// ` 347 | #[cfg(feature = "geo-types")] 348 | impl TryFrom for geo_types::MultiPolygon { 349 | type Error = &'static str; 350 | 351 | fn try_from(mp: Multipatch) -> Result { 352 | use geo_types::{Coord, LineString}; 353 | 354 | let mut polygons = Vec::>::new(); 355 | let mut last_poly = None; 356 | for patch in mp.patches { 357 | match patch { 358 | Patch::TriangleStrip(_) => { 359 | return Err("Cannot convert Multipatch::TriangleStrip to Multipolygon") 360 | } 361 | Patch::TriangleFan(_) => { 362 | return Err("Cannot convert Multipatch::TriangleFan to Multipolygon") 363 | } 364 | Patch::OuterRing(points) | Patch::FirstRing(points) => { 365 | let exterior = points 366 | .into_iter() 367 | .map(Coord::::from) 368 | .collect::>>(); 369 | 370 | if let Some(poly) = last_poly.take() { 371 | polygons.push(poly); 372 | } 373 | last_poly = Some(geo_types::Polygon::new(LineString::from(exterior), vec![])) 374 | } 375 | Patch::InnerRing(points) | Patch::Ring(points) => { 376 | let interior = points 377 | .into_iter() 378 | .map(Coord::::from) 379 | .collect::>>(); 380 | 381 | if let Some(poly) = last_poly.as_mut() { 382 | poly.interiors_push(interior); 383 | } else { 384 | // This is the strange (?) case: inner ring without a previous outer ring 385 | polygons.push(geo_types::Polygon::::new( 386 | LineString::::from(Vec::>::new()), 387 | vec![LineString::from(interior)], 388 | )); 389 | } 390 | } 391 | } 392 | } 393 | 394 | if let Some(poly) = last_poly { 395 | polygons.push(poly); 396 | } 397 | Ok(polygons.into()) 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/record/multipoint.rs: -------------------------------------------------------------------------------- 1 | //! Module with the definition of Multipoint, MultipointM and MultipointZ 2 | //! 3 | //! All three variant of Multipoint Shape (Multipoint, MultipointM, MultipointZ) 4 | //! are specialization of the `GenericMultipoint` 5 | //! 6 | use std::fmt; 7 | use std::io::{Read, Write}; 8 | use std::mem::size_of; 9 | use std::ops::Index; 10 | use std::slice::SliceIndex; 11 | 12 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; 13 | 14 | use super::io::*; 15 | use super::traits::{GrowablePoint, ShrinkablePoint}; 16 | use super::EsriShape; 17 | use super::{ConcreteReadableShape, GenericBBox}; 18 | use super::{Error, ShapeType}; 19 | use super::{HasShapeType, WritableShape}; 20 | use super::{Point, PointM, PointZ}; 21 | 22 | #[cfg(feature = "geo-types")] 23 | use geo_types; 24 | 25 | /// Generic struct to create the Multipoint, MultipointM, MultipointZ types 26 | /// 27 | /// Multipoints are a collection of... multiple points, 28 | /// they can be created from [`Vec`] of points using the [`From`] trait 29 | /// or using the [`new`] method. 30 | /// 31 | /// `Multipoint` shapes only offers non-mutable access to the points data, 32 | /// to be able to mutate it you have to move the points data out of the struct. 33 | /// 34 | /// ``` 35 | /// use shapefile::{Multipoint, Point}; 36 | /// let multipoint = Multipoint::from(vec![ 37 | /// Point::new(1.0, 1.0), 38 | /// Point::new(2.0, 2.0), 39 | /// ]); 40 | /// 41 | /// assert_eq!(multipoint[0], Point::new(1.0, 1.0)); 42 | /// 43 | /// let points: Vec = multipoint.into(); 44 | /// assert_eq!(points.len(), 2); 45 | /// ``` 46 | /// 47 | /// # geo-types 48 | /// 49 | /// Multipoints are convertible to the geo-types's Multipoint 50 | /// 51 | /// ``` 52 | /// # #[cfg(feature = "geo-types")] 53 | /// # fn main() -> Result<(), shapefile::Error> { 54 | /// let mut multipoints = shapefile::read_shapes_as::<_, shapefile::Multipoint>("tests/data/multipoint.shp")?; 55 | /// let geo_multipoint: geo_types::MultiPoint = multipoints.pop().unwrap().into(); 56 | /// let multipoint = shapefile::Multipoint::from(geo_multipoint); 57 | /// # Ok(()) 58 | /// # } 59 | /// # #[cfg(not(feature = "geo-types"))] 60 | /// # fn main() {} 61 | /// ``` 62 | /// 63 | /// [`new`]: #method.new 64 | #[derive(Debug, Clone, PartialEq)] 65 | pub struct GenericMultipoint { 66 | pub(crate) bbox: GenericBBox, 67 | pub(crate) points: Vec, 68 | } 69 | 70 | impl GenericMultipoint { 71 | /// Creates a new Multipoint shape 72 | /// 73 | /// # Examples 74 | /// 75 | /// Creating Multipoint 76 | /// ``` 77 | /// use shapefile::{Multipoint, Point}; 78 | /// let points = vec![ 79 | /// Point::new(1.0, 1.0), 80 | /// Point::new(2.0, 2.0), 81 | /// ]; 82 | /// let multipoint = Multipoint::new(points); 83 | /// ``` 84 | /// 85 | /// Creating a MultipointM 86 | /// ``` 87 | /// use shapefile::{MultipointM, PointM, NO_DATA}; 88 | /// let points = vec![ 89 | /// PointM::new(1.0, 1.0, NO_DATA), 90 | /// PointM::new(2.0, 2.0, NO_DATA), 91 | /// ]; 92 | /// let multipointm = MultipointM::new(points); 93 | /// ``` 94 | /// 95 | /// Creating a MultipointZ 96 | /// ``` 97 | /// use shapefile::{MultipointZ, PointZ, NO_DATA}; 98 | /// let points = vec![ 99 | /// PointZ::new(1.0, 1.0, 1.0, NO_DATA), 100 | /// PointZ::new(2.0, 2.0, 2.0, NO_DATA), 101 | /// ]; 102 | /// let multipointz = MultipointZ::new(points); 103 | /// ``` 104 | pub fn new(points: Vec) -> Self { 105 | let bbox = GenericBBox::::from_points(&points); 106 | Self { bbox, points } 107 | } 108 | } 109 | 110 | impl GenericMultipoint { 111 | /// Returns the bbox 112 | /// 113 | /// # Example 114 | /// 115 | /// ``` 116 | /// use shapefile::{MultipointZ, PointZ, NO_DATA}; 117 | /// let multipointz = MultipointZ::new(vec![ 118 | /// PointZ::new(1.0, 4.0, 1.2, 4.2), 119 | /// PointZ::new(2.0, 6.0, 4.0, 13.37), 120 | /// ]); 121 | /// 122 | /// let bbox = multipointz.bbox(); 123 | /// assert_eq!(bbox.min.x, 1.0); 124 | /// assert_eq!(bbox.max.x, 2.0); 125 | /// assert_eq!(bbox.m_range(), [4.2, 13.37]) 126 | /// ``` 127 | #[inline] 128 | pub fn bbox(&self) -> &GenericBBox { 129 | &self.bbox 130 | } 131 | 132 | /// Returns a non-mutable slice of point 133 | #[inline] 134 | pub fn points(&self) -> &[PointType] { 135 | &self.points 136 | } 137 | 138 | /// Returns a reference to a point 139 | /// 140 | /// # Example 141 | /// 142 | /// ``` 143 | /// use shapefile::{MultipointZ, PointZ}; 144 | /// let multipointz = MultipointZ::new(vec![ 145 | /// PointZ::new(1.0, 4.0, 1.2, 4.2), 146 | /// PointZ::new(2.0, 6.0, 4.0, 13.37), 147 | /// ]); 148 | /// 149 | /// assert_eq!(multipointz.point(0), Some(&PointZ::new(1.0, 4.0, 1.2, 4.2))); 150 | /// assert_eq!(multipointz.point(2), None); 151 | /// ``` 152 | #[inline] 153 | pub fn point(&self, index: usize) -> Option<&PointType> { 154 | self.points.get(index) 155 | } 156 | 157 | /// Consumes the shape, returning the points 158 | #[inline] 159 | pub fn into_inner(self) -> Vec { 160 | self.points 161 | } 162 | } 163 | 164 | impl From> for GenericMultipoint 165 | where 166 | PointType: ShrinkablePoint + GrowablePoint + Copy, 167 | { 168 | fn from(points: Vec) -> Self { 169 | Self::new(points) 170 | } 171 | } 172 | 173 | impl> Index for GenericMultipoint { 174 | type Output = I::Output; 175 | 176 | #[inline] 177 | fn index(&self, index: I) -> &Self::Output { 178 | Index::index(&self.points, index) 179 | } 180 | } 181 | 182 | // We do this because we can't use generics: 183 | // error[E0210]: type parameter `PointType` must be used as the type parameter for some local type 184 | // (e.g., `MyStruct`) 185 | macro_rules! impl_from_multipoint_to_vec_for_point_type { 186 | ($PointType:ty) => { 187 | impl From> for Vec<$PointType> { 188 | fn from(multipoints: GenericMultipoint<$PointType>) -> Self { 189 | multipoints.points 190 | } 191 | } 192 | }; 193 | } 194 | 195 | impl_from_multipoint_to_vec_for_point_type!(Point); 196 | impl_from_multipoint_to_vec_for_point_type!(PointM); 197 | impl_from_multipoint_to_vec_for_point_type!(PointZ); 198 | 199 | #[cfg(feature = "geo-types")] 200 | impl From> for geo_types::MultiPoint 201 | where 202 | geo_types::Point: From, 203 | { 204 | fn from(multi_points: GenericMultipoint) -> Self { 205 | multi_points 206 | .points 207 | .into_iter() 208 | .map(geo_types::Point::from) 209 | .collect::>>() 210 | .into() 211 | } 212 | } 213 | 214 | #[cfg(feature = "geo-types")] 215 | impl From> for GenericMultipoint 216 | where 217 | PointType: From> + ShrinkablePoint + GrowablePoint + Copy, 218 | { 219 | fn from(mp: geo_types::MultiPoint) -> Self { 220 | let points = mp.into_iter().map(|p| p.into()).collect(); 221 | Self::new(points) 222 | } 223 | } 224 | 225 | /* 226 | * Multipoint 227 | */ 228 | 229 | /// Specialization of the `GenericMultipoint` struct to represent a `Multipoint` shape 230 | /// ( collection of [Point](../point/struct.Point.html)) 231 | pub type Multipoint = GenericMultipoint; 232 | 233 | impl Multipoint { 234 | pub(crate) fn size_of_record(num_points: i32) -> usize { 235 | let mut size = 0usize; 236 | size += 4 * size_of::(); // BBOX 237 | size += size_of::(); // num points 238 | size += size_of::() * num_points as usize; 239 | size 240 | } 241 | } 242 | 243 | impl fmt::Display for Multipoint { 244 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 245 | write!(f, "Multipoint({} points)", self.points.len()) 246 | } 247 | } 248 | 249 | impl HasShapeType for Multipoint { 250 | fn shapetype() -> ShapeType { 251 | ShapeType::Multipoint 252 | } 253 | } 254 | 255 | impl ConcreteReadableShape for Multipoint { 256 | fn read_shape_content(source: &mut T, record_size: i32) -> Result { 257 | let mut bbox = GenericBBox::::default(); 258 | bbox_read_xy_from(&mut bbox, source)?; 259 | 260 | let num_points = source.read_i32::()?; 261 | if record_size == Self::size_of_record(num_points) as i32 { 262 | let points = read_xy_in_vec_of::(source, num_points)?; 263 | Ok(Self { bbox, points }) 264 | } else { 265 | Err(Error::InvalidShapeRecordSize) 266 | } 267 | } 268 | } 269 | 270 | impl WritableShape for Multipoint { 271 | fn size_in_bytes(&self) -> usize { 272 | let mut size = 0usize; 273 | size += 4 * size_of::(); // BBOX 274 | size += size_of::(); // num points 275 | size += 2 * size_of::() * self.points.len(); 276 | size 277 | } 278 | 279 | fn write_to(&self, dest: &mut T) -> Result<(), Error> { 280 | bbox_write_xy_to(&self.bbox, dest)?; 281 | dest.write_i32::(self.points.len() as i32)?; 282 | for point in self.points.iter() { 283 | dest.write_f64::(point.x)?; 284 | dest.write_f64::(point.y)?; 285 | } 286 | Ok(()) 287 | } 288 | } 289 | 290 | impl EsriShape for Multipoint { 291 | fn x_range(&self) -> [f64; 2] { 292 | self.bbox.x_range() 293 | } 294 | 295 | fn y_range(&self) -> [f64; 2] { 296 | self.bbox.y_range() 297 | } 298 | } 299 | 300 | /* 301 | * MultipointM 302 | */ 303 | 304 | /// Specialization of the `GenericMultipoint` struct to represent a `MultipointM` shape 305 | /// ( collection of [PointM](../point/struct.PointM.html)) 306 | pub type MultipointM = GenericMultipoint; 307 | 308 | impl MultipointM { 309 | pub(crate) fn size_of_record(num_points: i32, is_m_used: bool) -> usize { 310 | let mut size = Multipoint::size_of_record(num_points); 311 | if is_m_used { 312 | size += 2 * size_of::(); // M Range 313 | size += size_of::() * num_points as usize; // M 314 | } 315 | size 316 | } 317 | } 318 | 319 | impl fmt::Display for MultipointM { 320 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 321 | write!(f, "MultipointM({} points)", self.points.len()) 322 | } 323 | } 324 | 325 | impl HasShapeType for MultipointM { 326 | fn shapetype() -> ShapeType { 327 | ShapeType::MultipointM 328 | } 329 | } 330 | 331 | impl ConcreteReadableShape for MultipointM { 332 | fn read_shape_content(source: &mut T, record_size: i32) -> Result { 333 | let mut bbox = GenericBBox::::default(); 334 | bbox_read_xy_from(&mut bbox, source)?; 335 | 336 | let num_points = source.read_i32::()?; 337 | 338 | let size_with_m = Self::size_of_record(num_points, true) as i32; 339 | let size_without_m = Self::size_of_record(num_points, false) as i32; 340 | 341 | if (record_size != size_with_m) & (record_size != size_without_m) { 342 | Err(Error::InvalidShapeRecordSize) 343 | } else { 344 | let m_is_used = size_with_m == record_size; 345 | let mut points = read_xy_in_vec_of::(source, num_points)?; 346 | 347 | if m_is_used { 348 | bbox_read_m_range_from(&mut bbox, source)?; 349 | read_ms_into(source, &mut points)?; 350 | } 351 | Ok(Self { bbox, points }) 352 | } 353 | } 354 | } 355 | 356 | impl WritableShape for MultipointM { 357 | fn size_in_bytes(&self) -> usize { 358 | let mut size = 0usize; 359 | size += 4 * size_of::(); 360 | size += size_of::(); 361 | size += 3 * size_of::() * self.points.len(); 362 | size += 2 * size_of::(); 363 | size 364 | } 365 | 366 | fn write_to(&self, mut dest: &mut T) -> Result<(), Error> { 367 | bbox_write_xy_to(&self.bbox, dest)?; 368 | dest.write_i32::(self.points.len() as i32)?; 369 | 370 | write_points(&mut dest, &self.points)?; 371 | 372 | bbox_write_m_range_to(&self.bbox, dest)?; 373 | write_ms(&mut dest, &self.points)?; 374 | Ok(()) 375 | } 376 | } 377 | 378 | impl EsriShape for MultipointM { 379 | fn x_range(&self) -> [f64; 2] { 380 | self.bbox.x_range() 381 | } 382 | 383 | fn y_range(&self) -> [f64; 2] { 384 | self.bbox.y_range() 385 | } 386 | 387 | fn m_range(&self) -> [f64; 2] { 388 | self.bbox.m_range() 389 | } 390 | } 391 | 392 | /* 393 | * MultipointZ 394 | */ 395 | 396 | /// Specialization of the `GenericMultipoint` struct to represent a `MultipointZ` shape 397 | /// ( collection of [PointZ](../point/struct.PointZ.html)) 398 | pub type MultipointZ = GenericMultipoint; 399 | 400 | impl fmt::Display for MultipointZ { 401 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 402 | write!(f, "MultipointZ({} points)", self.points.len()) 403 | } 404 | } 405 | impl MultipointZ { 406 | pub(crate) fn size_of_record(num_points: i32, is_m_used: bool) -> usize { 407 | let mut size = Multipoint::size_of_record(num_points); 408 | size += 2 * size_of::(); // Z Range 409 | size += size_of::() * num_points as usize; // Z 410 | 411 | if is_m_used { 412 | size += 2 * size_of::(); // M Range 413 | size += size_of::() * num_points as usize; // M 414 | } 415 | 416 | size 417 | } 418 | } 419 | 420 | impl HasShapeType for MultipointZ { 421 | fn shapetype() -> ShapeType { 422 | ShapeType::MultipointZ 423 | } 424 | } 425 | 426 | impl ConcreteReadableShape for MultipointZ { 427 | fn read_shape_content(source: &mut T, record_size: i32) -> Result { 428 | let mut bbox = GenericBBox::::default(); 429 | bbox_read_xy_from(&mut bbox, source)?; 430 | let num_points = source.read_i32::()?; 431 | 432 | let size_with_m = Self::size_of_record(num_points, true) as i32; 433 | let size_without_m = Self::size_of_record(num_points, false) as i32; 434 | 435 | if (record_size != size_with_m) & (record_size != size_without_m) { 436 | Err(Error::InvalidShapeRecordSize) 437 | } else { 438 | let m_is_used = size_with_m == record_size; 439 | let mut points = read_xy_in_vec_of::(source, num_points)?; 440 | 441 | bbox_read_z_range_from(&mut bbox, source)?; 442 | read_zs_into(source, &mut points)?; 443 | 444 | if m_is_used { 445 | bbox_read_m_range_from(&mut bbox, source)?; 446 | read_ms_into(source, &mut points)?; 447 | } 448 | 449 | Ok(Self { bbox, points }) 450 | } 451 | } 452 | } 453 | 454 | impl WritableShape for MultipointZ { 455 | fn size_in_bytes(&self) -> usize { 456 | let mut size = 0usize; 457 | size += 4 * size_of::(); 458 | size += size_of::(); 459 | size += 4 * size_of::() * self.points.len(); 460 | size += 2 * size_of::(); 461 | size += 2 * size_of::(); 462 | size 463 | } 464 | 465 | fn write_to(&self, mut dest: &mut T) -> Result<(), Error> { 466 | bbox_write_xy_to(&self.bbox, dest)?; 467 | dest.write_i32::(self.points.len() as i32)?; 468 | 469 | write_points(&mut dest, &self.points)?; 470 | 471 | bbox_write_z_range_to(&self.bbox, dest)?; 472 | write_zs(&mut dest, &self.points)?; 473 | 474 | bbox_write_m_range_to(&self.bbox, dest)?; 475 | write_ms(&mut dest, &self.points)?; 476 | 477 | Ok(()) 478 | } 479 | } 480 | 481 | impl EsriShape for MultipointZ { 482 | fn x_range(&self) -> [f64; 2] { 483 | self.bbox.x_range() 484 | } 485 | 486 | fn y_range(&self) -> [f64; 2] { 487 | self.bbox.y_range() 488 | } 489 | 490 | fn z_range(&self) -> [f64; 2] { 491 | self.bbox.z_range() 492 | } 493 | 494 | fn m_range(&self) -> [f64; 2] { 495 | self.bbox.m_range() 496 | } 497 | } 498 | 499 | #[cfg(test)] 500 | #[cfg(feature = "geo-types")] 501 | mod test_geo_types_conversions { 502 | use super::*; 503 | use crate::{geo_types, NO_DATA}; 504 | use geo_types::Coord; 505 | 506 | #[test] 507 | fn test_multipoint_to_geo_types_multipoint() { 508 | let shapefile_points = vec![Point::new(1.0, 1.0), Point::new(2.0, 2.0)]; 509 | let geo_types_coords = shapefile_points 510 | .iter() 511 | .copied() 512 | .map(Coord::::from) 513 | .collect::>>(); 514 | 515 | let expected_shapefile_multipoint = Multipoint::new(shapefile_points); 516 | let expected_geo_types_multipoint = geo_types::MultiPoint::from(geo_types_coords); 517 | 518 | let geo_types_multipoint: geo_types::MultiPoint = 519 | expected_shapefile_multipoint.clone().into(); 520 | let shapefile_multipoint: Multipoint = expected_geo_types_multipoint.clone().into(); 521 | 522 | assert_eq!(geo_types_multipoint, expected_geo_types_multipoint); 523 | assert_eq!(shapefile_multipoint, expected_shapefile_multipoint); 524 | } 525 | 526 | #[test] 527 | fn test_multipoint_m_to_geo_types_multipoint() { 528 | let points = vec![ 529 | PointM::new(120.0, 56.0, 42.2), 530 | PointM::new(6.0, 18.7, 462.54), 531 | ]; 532 | let shapefile_multipoint = MultipointM::new(points); 533 | let geo_types_multipoint = geo_types::MultiPoint::from(shapefile_multipoint); 534 | 535 | let mut iter = geo_types_multipoint.into_iter(); 536 | let p1 = iter.next().unwrap(); 537 | let p2 = iter.next().unwrap(); 538 | assert_eq!(p1.x(), 120.0); 539 | assert_eq!(p1.y(), 56.0); 540 | 541 | assert_eq!(p2.x(), 6.0); 542 | assert_eq!(p2.y(), 18.7); 543 | 544 | let geo_types_multipoint: geo_types::MultiPoint<_> = vec![p1, p2].into(); 545 | let shapefile_multipoint = MultipointM::from(geo_types_multipoint); 546 | 547 | assert_eq!(shapefile_multipoint.points[0].x, 120.0); 548 | assert_eq!(shapefile_multipoint.points[0].y, 56.0); 549 | assert_eq!(shapefile_multipoint.points[0].m, NO_DATA); 550 | 551 | assert_eq!(shapefile_multipoint.points[1].x, 6.0); 552 | assert_eq!(shapefile_multipoint.points[1].y, 18.7); 553 | assert_eq!(shapefile_multipoint.points[0].m, NO_DATA); 554 | } 555 | 556 | #[test] 557 | fn test_multipoint_z_to_geo_types_multipoint() { 558 | let points = vec![ 559 | PointZ::new(1.0, 1.0, 17.0, 18.0), 560 | PointZ::new(2.0, 2.0, 15.0, 16.0), 561 | ]; 562 | let shapefile_multipoint = MultipointZ::new(points); 563 | let geo_types_multipoint = geo_types::MultiPoint::from(shapefile_multipoint); 564 | 565 | let mut iter = geo_types_multipoint.into_iter(); 566 | let p1 = iter.next().unwrap(); 567 | let p2 = iter.next().unwrap(); 568 | assert_eq!(p1.x(), 1.0); 569 | assert_eq!(p1.y(), 1.0); 570 | 571 | assert_eq!(p2.x(), 2.0); 572 | assert_eq!(p2.y(), 2.0); 573 | 574 | let geo_types_multipoint: geo_types::MultiPoint<_> = vec![p1, p2].into(); 575 | let shapefile_multipoint = MultipointZ::from(geo_types_multipoint); 576 | 577 | assert_eq!(shapefile_multipoint.points[0].x, 1.0); 578 | assert_eq!(shapefile_multipoint.points[0].y, 1.0); 579 | assert_eq!(shapefile_multipoint.points[0].z, 0.0); 580 | assert_eq!(shapefile_multipoint.points[0].m, NO_DATA); 581 | 582 | assert_eq!(shapefile_multipoint.points[1].x, 2.0); 583 | assert_eq!(shapefile_multipoint.points[1].y, 2.0); 584 | assert_eq!(shapefile_multipoint.points[0].z, 0.0); 585 | assert_eq!(shapefile_multipoint.points[0].m, NO_DATA); 586 | } 587 | } 588 | 589 | #[cfg(test)] 590 | mod tests { 591 | use super::{MultipointZ, PointZ}; 592 | 593 | #[test] 594 | fn test_multipoint_index() { 595 | let points = vec![ 596 | PointZ::new(1.0, 1.0, 17.0, 18.0), 597 | PointZ::new(2.0, 2.0, 15.0, 16.0), 598 | ]; 599 | let multipoint = MultipointZ::new(points.clone()); 600 | 601 | assert_eq!(multipoint[0], points[0]); 602 | assert_eq!(multipoint[1], points[1]); 603 | 604 | assert_eq!(multipoint[..1], points[..1]); 605 | } 606 | } 607 | -------------------------------------------------------------------------------- /src/record/point.rs: -------------------------------------------------------------------------------- 1 | //! Module with the definition of Point, PointM and PointZ 2 | 3 | use std::io::{Read, Write}; 4 | 5 | use super::EsriShape; 6 | use super::{ShapeType, NO_DATA}; 7 | use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; 8 | use std::mem::size_of; 9 | 10 | use super::ConcreteReadableShape; 11 | use super::Error; 12 | use super::{is_no_data, HasShapeType, WritableShape}; 13 | use std::fmt; 14 | 15 | #[cfg(feature = "geo-types")] 16 | use geo_types; 17 | 18 | /// Point with only `x` and `y` Coords 19 | #[derive(PartialEq, Debug, Default, Copy, Clone)] 20 | pub struct Point { 21 | pub x: f64, 22 | pub y: f64, 23 | } 24 | 25 | impl Point { 26 | /// Creates a new point 27 | /// 28 | /// # Examples 29 | /// 30 | /// ``` 31 | /// use shapefile::Point; 32 | /// let point = Point::new(1.0, 42.0); 33 | /// assert_eq!(point.x, 1.0); 34 | /// assert_eq!(point.y, 42.0); 35 | /// ``` 36 | /// 37 | /// ``` 38 | /// use shapefile::Point; 39 | /// let point = Point::default(); 40 | /// assert_eq!(point.x, 0.0); 41 | /// assert_eq!(point.y, 0.0); 42 | /// ``` 43 | pub fn new(x: f64, y: f64) -> Self { 44 | Self { x, y } 45 | } 46 | } 47 | 48 | impl HasShapeType for Point { 49 | fn shapetype() -> ShapeType { 50 | ShapeType::Point 51 | } 52 | } 53 | 54 | impl ConcreteReadableShape for Point { 55 | fn read_shape_content(source: &mut T, record_size: i32) -> Result { 56 | if record_size == 2 * size_of::() as i32 { 57 | let x = source.read_f64::()?; 58 | let y = source.read_f64::()?; 59 | Ok(Self { x, y }) 60 | } else { 61 | Err(Error::InvalidShapeRecordSize) 62 | } 63 | } 64 | } 65 | 66 | impl WritableShape for Point { 67 | fn size_in_bytes(&self) -> usize { 68 | 2 * size_of::() 69 | } 70 | 71 | fn write_to(&self, dest: &mut T) -> Result<(), Error> { 72 | dest.write_f64::(self.x)?; 73 | dest.write_f64::(self.y)?; 74 | Ok(()) 75 | } 76 | } 77 | 78 | impl EsriShape for Point { 79 | fn x_range(&self) -> [f64; 2] { 80 | [self.x, self.x] 81 | } 82 | 83 | fn y_range(&self) -> [f64; 2] { 84 | [self.y, self.y] 85 | } 86 | } 87 | 88 | impl fmt::Display for Point { 89 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 90 | write!(f, "Point(x: {}, y: {})", self.x, self.y) 91 | } 92 | } 93 | 94 | #[cfg(feature = "geo-types")] 95 | impl From for geo_types::Point { 96 | fn from(p: Point) -> Self { 97 | geo_types::Point::new(p.x, p.y) 98 | } 99 | } 100 | 101 | #[cfg(feature = "geo-types")] 102 | impl From> for Point { 103 | fn from(p: geo_types::Point) -> Self { 104 | Point::new(p.x(), p.y()) 105 | } 106 | } 107 | 108 | #[cfg(feature = "geo-types")] 109 | impl From> for Point { 110 | fn from(c: geo_types::Coord) -> Self { 111 | Point::new(c.x, c.y) 112 | } 113 | } 114 | 115 | #[cfg(feature = "geo-types")] 116 | impl From for geo_types::Coord { 117 | fn from(p: Point) -> Self { 118 | geo_types::Coord { x: p.x, y: p.y } 119 | } 120 | } 121 | 122 | /* 123 | * PointM 124 | */ 125 | 126 | /// Point with `x`, `y`, `m` 127 | #[derive(PartialEq, Debug, Copy, Clone)] 128 | pub struct PointM { 129 | pub x: f64, 130 | pub y: f64, 131 | pub m: f64, 132 | } 133 | 134 | impl PointM { 135 | /// Creates a new pointM 136 | /// 137 | /// # Examples 138 | /// 139 | /// ``` 140 | /// use shapefile::PointM; 141 | /// let point = PointM::new(1.0, 42.0, 13.37); 142 | /// assert_eq!(point.x, 1.0); 143 | /// assert_eq!(point.y, 42.0); 144 | /// assert_eq!(point.m, 13.37); 145 | /// ``` 146 | /// 147 | /// ``` 148 | /// use shapefile::{PointM, NO_DATA}; 149 | /// let point = PointM::default(); 150 | /// assert_eq!(point.x, 0.0); 151 | /// assert_eq!(point.y, 0.0); 152 | /// assert_eq!(point.m, NO_DATA); 153 | /// ``` 154 | pub fn new(x: f64, y: f64, m: f64) -> Self { 155 | Self { x, y, m } 156 | } 157 | } 158 | 159 | impl HasShapeType for PointM { 160 | fn shapetype() -> ShapeType { 161 | ShapeType::PointM 162 | } 163 | } 164 | 165 | impl ConcreteReadableShape for PointM { 166 | fn read_shape_content(source: &mut T, record_size: i32) -> Result { 167 | if record_size == 3 * size_of::() as i32 { 168 | let x = source.read_f64::()?; 169 | let y = source.read_f64::()?; 170 | let m = source.read_f64::()?; 171 | Ok(Self { x, y, m }) 172 | } else { 173 | Err(Error::InvalidShapeRecordSize) 174 | } 175 | } 176 | } 177 | 178 | impl WritableShape for PointM { 179 | fn size_in_bytes(&self) -> usize { 180 | 3 * size_of::() 181 | } 182 | 183 | fn write_to(&self, dest: &mut T) -> Result<(), Error> { 184 | dest.write_f64::(self.x)?; 185 | dest.write_f64::(self.y)?; 186 | dest.write_f64::(self.m)?; 187 | Ok(()) 188 | } 189 | } 190 | 191 | impl EsriShape for PointM { 192 | fn x_range(&self) -> [f64; 2] { 193 | [self.x, self.x] 194 | } 195 | 196 | fn y_range(&self) -> [f64; 2] { 197 | [self.y, self.y] 198 | } 199 | 200 | fn m_range(&self) -> [f64; 2] { 201 | if is_no_data(self.m) { 202 | [0.0, 0.0] 203 | } else { 204 | [self.m, self.m] 205 | } 206 | } 207 | } 208 | 209 | impl fmt::Display for PointM { 210 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 211 | if is_no_data(self.m) { 212 | write!(f, "Point(x: {}, y: {}, m: NO_DATA)", self.x, self.y) 213 | } else { 214 | write!(f, "Point(x: {}, y: {}, m: {})", self.x, self.y, self.m) 215 | } 216 | } 217 | } 218 | 219 | impl Default for PointM { 220 | fn default() -> Self { 221 | Self { 222 | x: 0.0, 223 | y: 0.0, 224 | m: NO_DATA, 225 | } 226 | } 227 | } 228 | 229 | #[cfg(feature = "geo-types")] 230 | impl From for geo_types::Point { 231 | fn from(p: PointM) -> Self { 232 | geo_types::Point::new(p.x, p.y) 233 | } 234 | } 235 | 236 | #[cfg(feature = "geo-types")] 237 | impl From> for PointM { 238 | fn from(p: geo_types::Point) -> Self { 239 | PointM { 240 | x: p.x(), 241 | y: p.y(), 242 | ..Default::default() 243 | } 244 | } 245 | } 246 | 247 | #[cfg(feature = "geo-types")] 248 | impl From> for PointM { 249 | fn from(c: geo_types::Coord) -> Self { 250 | PointM::new(c.x, c.y, NO_DATA) 251 | } 252 | } 253 | 254 | #[cfg(feature = "geo-types")] 255 | impl From for geo_types::Coord { 256 | fn from(p: PointM) -> Self { 257 | geo_types::Coord { x: p.x, y: p.y } 258 | } 259 | } 260 | 261 | /* 262 | * PointZ 263 | */ 264 | 265 | /// Point with `x`, `y`, `m`, `z` 266 | #[derive(PartialEq, Debug, Copy, Clone)] 267 | pub struct PointZ { 268 | pub x: f64, 269 | pub y: f64, 270 | pub z: f64, 271 | pub m: f64, 272 | } 273 | 274 | impl PointZ { 275 | /// Creates a new pointZ 276 | /// 277 | /// # Examples 278 | /// 279 | /// ``` 280 | /// use shapefile::{PointZ, NO_DATA}; 281 | /// let point = PointZ::new(1.0, 42.0, 13.37, NO_DATA); 282 | /// assert_eq!(point.x, 1.0); 283 | /// assert_eq!(point.y, 42.0); 284 | /// assert_eq!(point.z, 13.37); 285 | /// assert_eq!(point.m, NO_DATA); 286 | /// ``` 287 | pub fn new(x: f64, y: f64, z: f64, m: f64) -> Self { 288 | Self { x, y, z, m } 289 | } 290 | 291 | fn read_xyz(source: &mut R) -> std::io::Result { 292 | let x = source.read_f64::()?; 293 | let y = source.read_f64::()?; 294 | let z = source.read_f64::()?; 295 | Ok(Self { 296 | x, 297 | y, 298 | z, 299 | m: NO_DATA, 300 | }) 301 | } 302 | } 303 | 304 | impl HasShapeType for PointZ { 305 | fn shapetype() -> ShapeType { 306 | ShapeType::PointZ 307 | } 308 | } 309 | 310 | impl ConcreteReadableShape for PointZ { 311 | fn read_shape_content(source: &mut T, record_size: i32) -> Result { 312 | if record_size == 3 * size_of::() as i32 { 313 | let point = Self::read_xyz(source)?; 314 | Ok(point) 315 | } else if record_size == 4 * size_of::() as i32 { 316 | let mut point = Self::read_xyz(source)?; 317 | point.m = source.read_f64::()?; 318 | Ok(point) 319 | } else { 320 | Err(Error::InvalidShapeRecordSize) 321 | } 322 | } 323 | } 324 | 325 | impl WritableShape for PointZ { 326 | fn size_in_bytes(&self) -> usize { 327 | 4 * size_of::() 328 | } 329 | 330 | fn write_to(&self, dest: &mut T) -> Result<(), Error> { 331 | dest.write_f64::(self.x)?; 332 | dest.write_f64::(self.y)?; 333 | dest.write_f64::(self.z)?; 334 | dest.write_f64::(self.m)?; 335 | Ok(()) 336 | } 337 | } 338 | 339 | impl EsriShape for PointZ { 340 | fn x_range(&self) -> [f64; 2] { 341 | [self.x, self.x] 342 | } 343 | 344 | fn y_range(&self) -> [f64; 2] { 345 | [self.y, self.y] 346 | } 347 | 348 | fn z_range(&self) -> [f64; 2] { 349 | [self.z, self.z] 350 | } 351 | 352 | fn m_range(&self) -> [f64; 2] { 353 | if is_no_data(self.m) { 354 | [0.0, 0.0] 355 | } else { 356 | [self.m, self.m] 357 | } 358 | } 359 | } 360 | 361 | impl Default for PointZ { 362 | fn default() -> Self { 363 | Self { 364 | x: 0.0, 365 | y: 0.0, 366 | z: 0.0, 367 | m: NO_DATA, 368 | } 369 | } 370 | } 371 | 372 | impl fmt::Display for PointZ { 373 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 374 | if is_no_data(self.m) { 375 | write!( 376 | f, 377 | "Point(x: {}, y: {}, z: {}, m: NO_DATA)", 378 | self.x, self.y, self.z 379 | ) 380 | } else { 381 | write!( 382 | f, 383 | "Point(x: {}, y: {}, z: {}, m: {})", 384 | self.x, self.y, self.z, self.m 385 | ) 386 | } 387 | } 388 | } 389 | 390 | #[cfg(feature = "geo-types")] 391 | impl From for geo_types::Point { 392 | fn from(p: PointZ) -> Self { 393 | geo_types::Point::new(p.x, p.y) 394 | } 395 | } 396 | 397 | #[cfg(feature = "geo-types")] 398 | impl From> for PointZ { 399 | fn from(p: geo_types::Point) -> Self { 400 | PointZ { 401 | x: p.x(), 402 | y: p.y(), 403 | ..Default::default() 404 | } 405 | } 406 | } 407 | 408 | #[cfg(feature = "geo-types")] 409 | impl From> for PointZ { 410 | fn from(c: geo_types::Coord) -> Self { 411 | PointZ::new(c.x, c.y, 0.0, NO_DATA) 412 | } 413 | } 414 | 415 | #[cfg(feature = "geo-types")] 416 | impl From for geo_types::Coord { 417 | fn from(p: PointZ) -> Self { 418 | geo_types::Coord { x: p.x, y: p.y } 419 | } 420 | } 421 | 422 | #[cfg(test)] 423 | #[cfg(feature = "geo-types")] 424 | mod test_geo_types { 425 | use super::*; 426 | #[test] 427 | fn geo_types_point_conversion() { 428 | let p = Point::new(14.0, 42.65); 429 | let gp: geo_types::Point = p.into(); 430 | 431 | assert_eq!(gp.x(), 14.0); 432 | assert_eq!(gp.y(), 42.65); 433 | 434 | let p: Point = gp.into(); 435 | assert_eq!(p.x, 14.0); 436 | assert_eq!(p.y, 42.65); 437 | } 438 | 439 | #[test] 440 | fn geo_types_point_m_conversion() { 441 | let p = PointM::new(14.0, 42.65, 652.3); 442 | let gp: geo_types::Point = p.into(); 443 | 444 | assert_eq!(gp.x(), 14.0); 445 | assert_eq!(gp.y(), 42.65); 446 | 447 | let p: PointM = gp.into(); 448 | assert_eq!(p.x, 14.0); 449 | assert_eq!(p.y, 42.65); 450 | assert_eq!(p.m, NO_DATA); 451 | } 452 | 453 | #[test] 454 | fn geo_types_point_z_conversion() { 455 | let p = PointZ::new(14.0, 42.65, 111.0, 652.3); 456 | let gp: geo_types::Point = p.into(); 457 | 458 | assert_eq!(gp.x(), 14.0); 459 | assert_eq!(gp.y(), 42.65); 460 | 461 | let p: PointZ = gp.into(); 462 | assert_eq!(p.x, 14.0); 463 | assert_eq!(p.y, 42.65); 464 | assert_eq!(p.z, 0.0); 465 | assert_eq!(p.m, NO_DATA); 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /src/record/polyline.rs: -------------------------------------------------------------------------------- 1 | //! Module with the definition of Polyline, PolylineM, PolylineZ 2 | 3 | use std::fmt; 4 | use std::io::{Read, Write}; 5 | use std::mem::size_of; 6 | 7 | use super::io::*; 8 | use super::traits::{GrowablePoint, ShrinkablePoint}; 9 | use super::ConcreteReadableShape; 10 | use super::GenericBBox; 11 | use super::{Error, ShapeType}; 12 | use super::{EsriShape, HasShapeType, WritableShape}; 13 | use super::{Point, PointM, PointZ}; 14 | 15 | #[cfg(feature = "geo-types")] 16 | use geo_types; 17 | 18 | /// Generic struct to create Polyline; PolylineM, PolylineZ 19 | /// 20 | /// Polylines can have multiple parts. 21 | /// 22 | /// Polylines parts must have 2 at least 2 points 23 | /// 24 | /// To create a polyline with only one part use [`new`], 25 | /// to create a polyline with multiple parts use [`with_parts`] 26 | /// 27 | /// # geo-types 28 | /// 29 | /// shapefile's Polyline can be converted to geo_types's `MultiLineString` 30 | /// 31 | /// geo-types's `Line`, `LineString`, `MultiLineString` can be converted to shapefile's Polyline 32 | /// ``` 33 | /// # #[cfg(feature = "geo-types")] 34 | /// # fn main() -> Result<(), shapefile::Error>{ 35 | /// let mut polylines = shapefile::read_shapes_as::<_, shapefile::Polyline>("tests/data/line.shp")?; 36 | /// let geo_polyline: geo_types::MultiLineString = polylines.pop().unwrap().into(); 37 | /// let polyline = shapefile::Polyline::from(geo_polyline); 38 | /// # Ok(()) 39 | /// # } 40 | /// # #[cfg(not(feature = "geo-types"))] 41 | /// # fn main() {} 42 | /// ``` 43 | /// 44 | /// [`new`]: #method.new 45 | /// [`with_parts`]: #method.with_parts 46 | #[derive(Debug, Clone, PartialEq)] 47 | pub struct GenericPolyline { 48 | pub(crate) bbox: GenericBBox, 49 | pub(crate) parts: Vec>, 50 | } 51 | 52 | /// Creating a Polyline 53 | impl GenericPolyline { 54 | /// # Examples 55 | /// 56 | /// Polyline with single part 57 | /// ``` 58 | /// use shapefile::{Point, Polyline}; 59 | /// let points = vec![ 60 | /// Point::new(1.0, 1.0), 61 | /// Point::new(2.0, 2.0), 62 | /// ]; 63 | /// let poly = Polyline::new(points); 64 | /// ``` 65 | /// 66 | /// # panic 67 | /// 68 | /// This will panic if the vec has less than 2 points 69 | pub fn new(points: Vec) -> Self { 70 | assert!( 71 | points.len() >= 2, 72 | "Polylines parts must have at least 2 points" 73 | ); 74 | Self { 75 | bbox: GenericBBox::::from_points(&points), 76 | parts: vec![points], 77 | } 78 | } 79 | 80 | /// # Examples 81 | /// 82 | /// Polyline with multiple parts 83 | /// ``` 84 | /// use shapefile::{Point, Polyline}; 85 | /// let first_part = vec![ 86 | /// Point::new(1.0, 1.0), 87 | /// Point::new(2.0, 2.0), 88 | /// ]; 89 | /// 90 | /// let second_part = vec![ 91 | /// Point::new(3.0, 1.0), 92 | /// Point::new(5.0, 6.0), 93 | /// ]; 94 | /// 95 | /// let third_part = vec![ 96 | /// Point::new(17.0, 15.0), 97 | /// Point::new(18.0, 19.0), 98 | /// Point::new(20.0, 19.0), 99 | /// ]; 100 | /// let poly = Polyline::with_parts(vec![first_part, second_part, third_part]); 101 | /// ``` 102 | /// 103 | /// # panic 104 | /// 105 | /// This will panic if any of the parts are less than 2 points 106 | pub fn with_parts(parts: Vec>) -> Self { 107 | assert!( 108 | parts.iter().all(|p| p.len() >= 2), 109 | "Polylines parts must have at least 2 points" 110 | ); 111 | Self { 112 | bbox: GenericBBox::::from_parts(&parts), 113 | parts, 114 | } 115 | } 116 | } 117 | 118 | impl GenericPolyline { 119 | /// Returns the bounding box associated to the polyline 120 | #[inline] 121 | pub fn bbox(&self) -> &GenericBBox { 122 | &self.bbox 123 | } 124 | 125 | /// Returns a reference to all the parts 126 | #[inline] 127 | pub fn parts(&self) -> &Vec> { 128 | &self.parts 129 | } 130 | 131 | /// Returns a reference to a part 132 | #[inline] 133 | pub fn part(&self, index: usize) -> Option<&Vec> { 134 | self.parts.get(index) 135 | } 136 | 137 | /// Consumes the polyline and returns the parts 138 | #[inline] 139 | pub fn into_inner(self) -> Vec> { 140 | self.parts 141 | } 142 | 143 | /// Returns the number of points contained in all the parts 144 | #[inline] 145 | pub fn total_point_count(&self) -> usize { 146 | self.parts.iter().map(|part| part.len()).sum() 147 | } 148 | } 149 | 150 | /// Specialization of the `GenericPolyline` struct to represent a `Polyline` shape 151 | /// ( collection of [Point](../point/struct.Point.html)) 152 | pub type Polyline = GenericPolyline; 153 | 154 | impl Polyline { 155 | pub(crate) fn size_of_record(num_points: i32, num_parts: i32) -> usize { 156 | let mut size = 0usize; 157 | size += 4 * size_of::(); // BBOX 158 | size += size_of::(); // num parts 159 | size += size_of::(); // num points 160 | size += size_of::() * num_parts as usize; 161 | size += size_of::() * num_points as usize; 162 | size 163 | } 164 | } 165 | 166 | impl fmt::Display for Polyline { 167 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 168 | write!(f, "Polyline({} parts)", self.parts.len()) 169 | } 170 | } 171 | 172 | impl HasShapeType for Polyline { 173 | fn shapetype() -> ShapeType { 174 | ShapeType::Polyline 175 | } 176 | } 177 | 178 | impl ConcreteReadableShape for Polyline { 179 | fn read_shape_content(source: &mut T, record_size: i32) -> Result { 180 | let rdr = MultiPartShapeReader::::new(source)?; 181 | if record_size != Self::size_of_record(rdr.num_points, rdr.num_parts) as i32 { 182 | Err(Error::InvalidShapeRecordSize) 183 | } else { 184 | rdr.read_xy().map_err(Error::IoError).map(|rdr| Self { 185 | bbox: rdr.bbox, 186 | parts: rdr.parts, 187 | }) 188 | } 189 | } 190 | } 191 | 192 | impl WritableShape for Polyline { 193 | fn size_in_bytes(&self) -> usize { 194 | let mut size = 0usize; 195 | size += 4 * size_of::(); 196 | size += size_of::(); 197 | size += size_of::(); 198 | size += size_of::() * self.parts.len(); 199 | size += 2 * size_of::() * self.total_point_count(); 200 | size 201 | } 202 | 203 | fn write_to(&self, dest: &mut T) -> Result<(), Error> { 204 | let parts_iter = self.parts.iter().map(|part| part.as_slice()); 205 | let writer = MultiPartShapeWriter::new(&self.bbox, parts_iter, dest); 206 | writer.write_point_shape()?; 207 | Ok(()) 208 | } 209 | } 210 | 211 | impl EsriShape for Polyline { 212 | fn x_range(&self) -> [f64; 2] { 213 | self.bbox.x_range() 214 | } 215 | 216 | fn y_range(&self) -> [f64; 2] { 217 | self.bbox.y_range() 218 | } 219 | } 220 | 221 | /* 222 | * PolylineM 223 | */ 224 | 225 | /// Specialization of the `GenericPolyline` struct to represent a `PolylineM` shape 226 | /// ( collection of [PointM](../point/struct.PointM.html)) 227 | pub type PolylineM = GenericPolyline; 228 | 229 | impl PolylineM { 230 | pub(crate) fn size_of_record(num_points: i32, num_parts: i32, is_m_used: bool) -> usize { 231 | let mut size = Polyline::size_of_record(num_points, num_parts); 232 | if is_m_used { 233 | size += 2 * size_of::(); // MRange 234 | size += num_points as usize * size_of::(); // M 235 | } 236 | size 237 | } 238 | } 239 | 240 | impl fmt::Display for PolylineM { 241 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 242 | write!(f, "PolylineM({} parts)", self.parts.len()) 243 | } 244 | } 245 | 246 | impl HasShapeType for PolylineM { 247 | fn shapetype() -> ShapeType { 248 | ShapeType::PolylineM 249 | } 250 | } 251 | 252 | impl ConcreteReadableShape for PolylineM { 253 | fn read_shape_content(source: &mut T, record_size: i32) -> Result { 254 | let rdr = MultiPartShapeReader::::new(source)?; 255 | 256 | let record_size_with_m = Self::size_of_record(rdr.num_points, rdr.num_parts, true) as i32; 257 | let record_size_without_m = 258 | Self::size_of_record(rdr.num_points, rdr.num_parts, false) as i32; 259 | 260 | if (record_size != record_size_with_m) && (record_size != record_size_without_m) { 261 | Err(Error::InvalidShapeRecordSize) 262 | } else { 263 | rdr.read_xy() 264 | .and_then(|rdr| rdr.read_ms_if(record_size == record_size_with_m)) 265 | .map_err(Error::IoError) 266 | .map(|rdr| Self { 267 | bbox: rdr.bbox, 268 | parts: rdr.parts, 269 | }) 270 | } 271 | } 272 | } 273 | 274 | impl WritableShape for PolylineM { 275 | fn size_in_bytes(&self) -> usize { 276 | let mut size = 0_usize; 277 | size += size_of::() * 4; 278 | size += size_of::(); // num parts 279 | size += size_of::(); //num points 280 | size += size_of::() * self.parts.len(); 281 | size += 3 * size_of::() * self.total_point_count(); 282 | size += 2 * size_of::(); 283 | size 284 | } 285 | 286 | fn write_to(&self, dest: &mut T) -> Result<(), Error> { 287 | let parts_iter = self.parts.iter().map(|part| part.as_slice()); 288 | let writer = MultiPartShapeWriter::new(&self.bbox, parts_iter, dest); 289 | writer.write_point_m_shape()?; 290 | Ok(()) 291 | } 292 | } 293 | 294 | impl EsriShape for PolylineM { 295 | fn x_range(&self) -> [f64; 2] { 296 | self.bbox.x_range() 297 | } 298 | 299 | fn y_range(&self) -> [f64; 2] { 300 | self.bbox.y_range() 301 | } 302 | 303 | fn m_range(&self) -> [f64; 2] { 304 | self.bbox.m_range() 305 | } 306 | } 307 | 308 | /* 309 | * PolylineZ 310 | */ 311 | 312 | /// Specialization of the `GenericPolyline` struct to represent a `PolylineZ` shape 313 | /// ( collection of [PointZ](../point/struct.PointZ.html)) 314 | pub type PolylineZ = GenericPolyline; 315 | 316 | impl PolylineZ { 317 | pub(crate) fn size_of_record(num_points: i32, num_parts: i32, is_m_used: bool) -> usize { 318 | let mut size = Polyline::size_of_record(num_points, num_parts); 319 | size += 2 * size_of::(); // ZRange 320 | size += num_points as usize * size_of::(); // Z 321 | if is_m_used { 322 | size += 2 * size_of::(); // MRange 323 | size += num_points as usize * size_of::(); // M 324 | } 325 | size 326 | } 327 | } 328 | 329 | impl fmt::Display for PolylineZ { 330 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 331 | write!(f, "PolylineZ({} parts)", self.parts.len()) 332 | } 333 | } 334 | 335 | impl HasShapeType for PolylineZ { 336 | fn shapetype() -> ShapeType { 337 | ShapeType::PolylineZ 338 | } 339 | } 340 | 341 | impl ConcreteReadableShape for PolylineZ { 342 | fn read_shape_content(source: &mut T, record_size: i32) -> Result { 343 | let rdr = MultiPartShapeReader::::new(source)?; 344 | 345 | let record_size_with_m = Self::size_of_record(rdr.num_points, rdr.num_parts, true) as i32; 346 | let record_size_without_m = 347 | Self::size_of_record(rdr.num_points, rdr.num_parts, false) as i32; 348 | 349 | if (record_size != record_size_with_m) && (record_size != record_size_without_m) { 350 | Err(Error::InvalidShapeRecordSize) 351 | } else { 352 | rdr.read_xy() 353 | .and_then(|rdr| rdr.read_zs()) 354 | .and_then(|rdr| rdr.read_ms_if(record_size == record_size_with_m)) 355 | .map_err(Error::IoError) 356 | .map(|rdr| Self { 357 | bbox: rdr.bbox, 358 | parts: rdr.parts, 359 | }) 360 | } 361 | } 362 | } 363 | 364 | impl WritableShape for PolylineZ { 365 | fn size_in_bytes(&self) -> usize { 366 | let mut size = 0_usize; 367 | size += size_of::() * 4; 368 | size += size_of::(); // num parts 369 | size += size_of::(); //num points 370 | size += size_of::() * self.parts.len(); 371 | size += 4 * size_of::() * self.total_point_count(); 372 | size += 2 * size_of::(); 373 | size += 2 * size_of::(); 374 | size 375 | } 376 | 377 | fn write_to(&self, dest: &mut T) -> Result<(), Error> { 378 | let parts_iter = self.parts.iter().map(|part| part.as_slice()); 379 | let writer = MultiPartShapeWriter::new(&self.bbox, parts_iter, dest); 380 | writer.write_point_z_shape()?; 381 | Ok(()) 382 | } 383 | } 384 | 385 | impl EsriShape for PolylineZ { 386 | fn x_range(&self) -> [f64; 2] { 387 | self.bbox.x_range() 388 | } 389 | 390 | fn y_range(&self) -> [f64; 2] { 391 | self.bbox.y_range() 392 | } 393 | 394 | fn z_range(&self) -> [f64; 2] { 395 | self.bbox.z_range() 396 | } 397 | 398 | fn m_range(&self) -> [f64; 2] { 399 | self.bbox.m_range() 400 | } 401 | } 402 | 403 | #[cfg(feature = "geo-types")] 404 | impl From> for geo_types::MultiLineString 405 | where 406 | PointType: Copy, 407 | geo_types::Coord: From, 408 | { 409 | fn from(polyline: GenericPolyline) -> Self { 410 | let mut lines = Vec::>::with_capacity(polyline.parts().len()); 411 | 412 | for points in polyline.parts { 413 | let line: Vec> = points 414 | .into_iter() 415 | .map(geo_types::Coord::::from) 416 | .collect(); 417 | lines.push(line.into()); 418 | } 419 | geo_types::MultiLineString::::from_iter(lines) 420 | } 421 | } 422 | 423 | #[cfg(feature = "geo-types")] 424 | impl From> for GenericPolyline 425 | where 426 | PointType: From> + ShrinkablePoint + GrowablePoint + Copy, 427 | { 428 | fn from(line: geo_types::Line) -> Self { 429 | let (p1, p2) = line.points(); 430 | Self::new(vec![PointType::from(p1), PointType::from(p2)]) 431 | } 432 | } 433 | 434 | #[cfg(feature = "geo-types")] 435 | impl From> for GenericPolyline 436 | where 437 | PointType: From> + ShrinkablePoint + GrowablePoint + Copy, 438 | { 439 | fn from(line: geo_types::LineString) -> Self { 440 | let points: Vec = line.into_iter().map(PointType::from).collect(); 441 | Self::new(points) 442 | } 443 | } 444 | 445 | #[cfg(feature = "geo-types")] 446 | impl From> for GenericPolyline 447 | where 448 | PointType: From> + ShrinkablePoint + GrowablePoint + Copy, 449 | { 450 | fn from(mls: geo_types::MultiLineString) -> Self { 451 | let mut parts = Vec::>::with_capacity(mls.0.len()); 452 | for linestring in mls.0.into_iter() { 453 | parts.push(linestring.into_iter().map(PointType::from).collect()); 454 | } 455 | Self::with_parts(parts) 456 | } 457 | } 458 | 459 | #[cfg(test)] 460 | mod tests { 461 | use super::*; 462 | 463 | #[test] 464 | #[should_panic(expected = "Polylines parts must have at least 2 points")] 465 | fn test_polyline_new_less_than_2_points() { 466 | let _polyline = Polyline::new(vec![Point::new(1.0, 1.0)]); 467 | } 468 | 469 | #[test] 470 | #[should_panic(expected = "Polylines parts must have at least 2 points")] 471 | fn test_polyline_with_parts_less_than_2_points() { 472 | let _polyline = Polyline::with_parts(vec![ 473 | vec![Point::new(1.0, 1.0), Point::new(2.0, 2.0)], 474 | vec![Point::new(1.0, 1.0)], 475 | ]); 476 | } 477 | } 478 | 479 | #[cfg(test)] 480 | #[cfg(feature = "geo-types")] 481 | mod test_geo_types_conversions { 482 | use super::*; 483 | use crate::NO_DATA; 484 | use crate::{PointM, PolylineM}; 485 | use geo_types::{Coord, LineString, MultiLineString}; 486 | 487 | #[test] 488 | fn test_polyline_into_multiline_string() { 489 | let polyline_m = PolylineM::with_parts(vec![ 490 | vec![ 491 | PointM::new(1.0, 5.0, 0.0), 492 | PointM::new(5.0, 5.0, NO_DATA), 493 | PointM::new(5.0, 1.0, 3.0), 494 | ], 495 | vec![PointM::new(1.0, 5.0, 0.0), PointM::new(1.0, 1.0, 0.0)], 496 | ]); 497 | 498 | let multiline_string: MultiLineString = polyline_m.into(); 499 | 500 | let expected_multiline = geo_types::MultiLineString(vec![ 501 | LineString::(vec![ 502 | Coord { x: 1.0, y: 5.0 }, 503 | Coord { x: 5.0, y: 5.0 }, 504 | Coord { x: 5.0, y: 1.0 }, 505 | ]), 506 | LineString::(vec![Coord { x: 1.0, y: 5.0 }, Coord { x: 1.0, y: 1.0 }]), 507 | ]); 508 | assert_eq!(multiline_string, expected_multiline); 509 | } 510 | 511 | #[test] 512 | fn test_line_into_polyline() { 513 | let line = geo_types::Line::new(Coord { x: 2.0, y: 3.0 }, Coord { x: 6.0, y: -6.0 }); 514 | let polyline: PolylineZ = line.into(); 515 | 516 | assert_eq!( 517 | polyline.parts, 518 | vec![vec![ 519 | PointZ::new(2.0, 3.0, 0.0, NO_DATA), 520 | PointZ::new(6.0, -6.0, 0.0, NO_DATA) 521 | ]] 522 | ); 523 | } 524 | 525 | #[test] 526 | fn test_linestring_into_polyline() { 527 | let linestring = LineString::from(vec![ 528 | Coord { x: 1.0, y: 5.0 }, 529 | Coord { x: 5.0, y: 5.0 }, 530 | Coord { x: 5.0, y: 1.0 }, 531 | ]); 532 | 533 | let polyline: Polyline = linestring.into(); 534 | assert_eq!( 535 | polyline.parts, 536 | vec![vec![ 537 | Point::new(1.0, 5.0), 538 | Point::new(5.0, 5.0), 539 | Point::new(5.0, 1.0), 540 | ]] 541 | ) 542 | } 543 | 544 | #[test] 545 | fn test_multi_line_string_into_polyline() { 546 | let multiline_string = geo_types::MultiLineString(vec![ 547 | LineString::(vec![ 548 | Coord { x: 1.0, y: 5.0 }, 549 | Coord { x: 5.0, y: 5.0 }, 550 | Coord { x: 5.0, y: 1.0 }, 551 | ]), 552 | LineString::(vec![Coord { x: 1.0, y: 5.0 }, Coord { x: 1.0, y: 1.0 }]), 553 | ]); 554 | 555 | let expected_polyline_z = PolylineZ::with_parts(vec![ 556 | vec![ 557 | PointZ::new(1.0, 5.0, 0.0, NO_DATA), 558 | PointZ::new(5.0, 5.0, 0.0, NO_DATA), 559 | PointZ::new(5.0, 1.0, 0.0, NO_DATA), 560 | ], 561 | vec![ 562 | PointZ::new(1.0, 5.0, 0.0, NO_DATA), 563 | PointZ::new(1.0, 1.0, 0.0, NO_DATA), 564 | ], 565 | ]); 566 | 567 | let polyline_z: PolylineZ = multiline_string.into(); 568 | assert_eq!(polyline_z, expected_polyline_z); 569 | } 570 | } 571 | -------------------------------------------------------------------------------- /src/record/traits.rs: -------------------------------------------------------------------------------- 1 | use super::{Point, PointM, PointZ}; 2 | use crate::writer::{f64_max, f64_min}; 3 | 4 | /// Trait to access the x, and y values of a point 5 | /// 6 | /// # Examples 7 | /// 8 | /// ``` 9 | /// use shapefile::record::traits::HasXY; 10 | /// use shapefile::{Point, NO_DATA, PointZ}; 11 | /// fn mean_x_y(points: &[PointType]) -> (f64, f64) { 12 | /// let (sum_x, sum_y) = points.iter() 13 | /// .fold((0.0, 0.0), 14 | /// |acc, point| (acc.0 + point.x(), acc.1 + point.y())); 15 | /// 16 | /// (sum_x / points.len() as f64, sum_y / points.len() as f64) 17 | /// } 18 | /// let points = vec![PointZ::new(1.0, 2.0, 3.0, NO_DATA), PointZ::new(1.0, 2.0, 5.0, NO_DATA)]; 19 | /// assert_eq!(mean_x_y(&points), (1.0, 2.0)); 20 | /// 21 | /// ``` 22 | pub trait HasXY { 23 | /// Returns the value of the x dimension 24 | fn x(&self) -> f64; 25 | /// Returns the value of the y dimension 26 | fn y(&self) -> f64; 27 | } 28 | 29 | /// Trait to access the m value of a point 30 | pub trait HasM { 31 | fn m(&self) -> f64; 32 | } 33 | 34 | /// Trait to access the z value of a point 35 | pub trait HasZ { 36 | fn z(&self) -> f64; 37 | } 38 | 39 | pub(crate) trait HasMutXY { 40 | fn x_mut(&mut self) -> &mut f64; 41 | fn y_mut(&mut self) -> &mut f64; 42 | } 43 | 44 | pub(crate) trait HasMutM { 45 | fn m_mut(&mut self) -> &mut f64; 46 | } 47 | 48 | pub(crate) trait HasMutZ { 49 | fn z_mut(&mut self) -> &mut f64; 50 | } 51 | 52 | macro_rules! impl_has_xy_for { 53 | ($PointType:ty) => { 54 | impl HasXY for $PointType { 55 | fn x(&self) -> f64 { 56 | self.x 57 | } 58 | fn y(&self) -> f64 { 59 | self.y 60 | } 61 | } 62 | }; 63 | } 64 | 65 | macro_rules! impl_has_mut_xy_for { 66 | ($PointType:ty) => { 67 | impl HasMutXY for $PointType { 68 | fn x_mut(&mut self) -> &mut f64 { 69 | &mut self.x 70 | } 71 | fn y_mut(&mut self) -> &mut f64 { 72 | &mut self.y 73 | } 74 | } 75 | }; 76 | } 77 | 78 | macro_rules! impl_has_m_for { 79 | ($PointType:ty) => { 80 | impl HasM for $PointType { 81 | fn m(&self) -> f64 { 82 | self.m 83 | } 84 | } 85 | 86 | impl HasMutM for $PointType { 87 | fn m_mut(&mut self) -> &mut f64 { 88 | &mut self.m 89 | } 90 | } 91 | }; 92 | } 93 | 94 | impl_has_xy_for!(Point); 95 | impl_has_xy_for!(PointM); 96 | impl_has_xy_for!(PointZ); 97 | 98 | impl_has_mut_xy_for!(Point); 99 | impl_has_mut_xy_for!(PointM); 100 | impl_has_mut_xy_for!(PointZ); 101 | 102 | impl_has_m_for!(PointM); 103 | impl_has_m_for!(PointZ); 104 | 105 | impl HasZ for PointZ { 106 | fn z(&self) -> f64 { 107 | self.z 108 | } 109 | } 110 | 111 | impl HasMutZ for PointZ { 112 | fn z_mut(&mut self) -> &mut f64 { 113 | &mut self.z 114 | } 115 | } 116 | 117 | pub trait ShrinkablePoint { 118 | fn shrink(&mut self, other: &Self); 119 | } 120 | 121 | pub trait GrowablePoint { 122 | fn grow(&mut self, other: &Self); 123 | } 124 | 125 | impl ShrinkablePoint for Point { 126 | fn shrink(&mut self, other: &Self) { 127 | self.x = f64_min(self.x, other.x); 128 | self.y = f64_min(self.y, other.y); 129 | } 130 | } 131 | 132 | impl ShrinkablePoint for PointM { 133 | fn shrink(&mut self, other: &Self) { 134 | self.x = f64_min(self.x, other.x); 135 | self.y = f64_min(self.y, other.y); 136 | self.m = f64_min(self.m, other.m); 137 | } 138 | } 139 | 140 | impl ShrinkablePoint for PointZ { 141 | fn shrink(&mut self, other: &Self) { 142 | self.x = f64_min(self.x, other.x); 143 | self.y = f64_min(self.y, other.y); 144 | self.z = f64_min(self.z, other.z); 145 | self.m = f64_min(self.m, other.m); 146 | } 147 | } 148 | 149 | impl GrowablePoint for Point { 150 | fn grow(&mut self, other: &Self) { 151 | self.x = f64_max(self.x, other.x); 152 | self.y = f64_max(self.y, other.y); 153 | } 154 | } 155 | 156 | impl GrowablePoint for PointM { 157 | fn grow(&mut self, other: &Self) { 158 | self.x = f64_max(self.x, other.x); 159 | self.y = f64_max(self.y, other.y); 160 | self.m = f64_max(self.m, other.m); 161 | } 162 | } 163 | 164 | impl GrowablePoint for PointZ { 165 | fn grow(&mut self, other: &Self) { 166 | self.x = f64_max(self.x, other.x); 167 | self.y = f64_max(self.y, other.y); 168 | self.z = f64_max(self.z, other.z); 169 | self.m = f64_max(self.m, other.m); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/writer.rs: -------------------------------------------------------------------------------- 1 | //! Module with the definition of the [Writer] that allows writing shapefile 2 | //! 3 | //! # Writer 4 | //! 5 | //! [Writer] is the struct that writes a complete shapefile (_.shp_, _.shx_, _.dbf_). 6 | //! 7 | //! # ShapeWriter 8 | //! 9 | //! The [ShapeWriter] can be used if you only want to write the .shp 10 | //! and .shx files, however since it does not write the .dbf file, it is not recommended. 11 | use std::io::{BufWriter, Seek, SeekFrom, Write}; 12 | 13 | use super::{header, ShapeType}; 14 | use super::{Error, PointZ}; 15 | use crate::record::{BBoxZ, EsriShape, RecordHeader}; 16 | use std::fs::File; 17 | use std::path::Path; 18 | 19 | use crate::reader::ShapeIndex; 20 | use dbase::TableWriterBuilder; 21 | 22 | pub(crate) fn f64_min(a: f64, b: f64) -> f64 { 23 | if a < b { 24 | a 25 | } else { 26 | b 27 | } 28 | } 29 | 30 | pub(crate) fn f64_max(a: f64, b: f64) -> f64 { 31 | if a > b { 32 | a 33 | } else { 34 | b 35 | } 36 | } 37 | 38 | /// struct that handles the writing of the .shp 39 | /// and (optionally) the .idx 40 | /// 41 | /// The recommended way to create a ShapeWriter by using [ShapeWriter::from_path] 42 | /// 43 | /// # Important 44 | /// 45 | /// As this writer does not write the _.dbf_, it does not write what is considered 46 | /// a complete (thus valid) shapefile. 47 | pub struct ShapeWriter { 48 | shp_dest: T, 49 | shx_dest: Option, 50 | header: header::Header, 51 | rec_num: u32, 52 | dirty: bool, 53 | } 54 | 55 | impl ShapeWriter { 56 | /// Creates a writer that can be used to write a new shapefile. 57 | /// 58 | /// The `dest` argument is only for the .shp 59 | pub fn new(shp_dest: T) -> Self { 60 | Self { 61 | shp_dest, 62 | shx_dest: None, 63 | header: header::Header::default(), 64 | rec_num: 1, 65 | dirty: true, 66 | } 67 | } 68 | 69 | pub fn with_shx(shp_dest: T, shx_dest: T) -> Self { 70 | Self { 71 | shp_dest, 72 | shx_dest: Some(shx_dest), 73 | header: Default::default(), 74 | rec_num: 1, 75 | dirty: true, 76 | } 77 | } 78 | 79 | /// Write the shape to the file 80 | /// 81 | /// # Examples 82 | /// 83 | /// ``` 84 | /// # fn main() -> Result<(), shapefile::Error> { 85 | /// use shapefile::Point; 86 | /// let mut writer = shapefile::ShapeWriter::from_path("points.shp")?; 87 | /// 88 | /// writer.write_shape(&Point::new(0.0, 0.0))?; 89 | /// writer.write_shape(&Point::new(1.0, 0.0))?; 90 | /// writer.write_shape(&Point::new(2.0, 0.0))?; 91 | /// 92 | /// # std::fs::remove_file("points.shp")?; 93 | /// # std::fs::remove_file("points.shx")?; 94 | /// # Ok(()) 95 | /// # } 96 | /// ``` 97 | pub fn write_shape(&mut self, shape: &S) -> Result<(), Error> { 98 | match (self.header.shape_type, S::shapetype()) { 99 | // This is the first call to write shape, we shall write the header 100 | // to reserve it space in the file. 101 | (ShapeType::NullShape, t) => { 102 | self.header.shape_type = t; 103 | self.header.bbox = BBoxZ { 104 | max: PointZ::new(f64::MIN, f64::MIN, f64::MIN, f64::MIN), 105 | min: PointZ::new(f64::MAX, f64::MAX, f64::MAX, f64::MAX), 106 | }; 107 | self.header.write_to(&mut self.shp_dest)?; 108 | if let Some(shx_dest) = &mut self.shx_dest { 109 | self.header.write_to(shx_dest)?; 110 | } 111 | } 112 | (t1, t2) if t1 != t2 => { 113 | return Err(Error::MismatchShapeType { 114 | requested: t1, 115 | actual: t2, 116 | }); 117 | } 118 | _ => {} 119 | } 120 | 121 | let record_size = (shape.size_in_bytes() + std::mem::size_of::()) / 2; 122 | 123 | RecordHeader { 124 | record_number: self.rec_num as i32, 125 | record_size: record_size as i32, 126 | } 127 | .write_to(&mut self.shp_dest)?; 128 | self.header.shape_type.write_to(&mut self.shp_dest)?; 129 | shape.write_to(&mut self.shp_dest)?; 130 | 131 | if let Some(shx_dest) = &mut self.shx_dest { 132 | ShapeIndex { 133 | offset: self.header.file_length, 134 | record_size: record_size as i32, 135 | } 136 | .write_to(shx_dest)?; 137 | } 138 | 139 | self.header.file_length += record_size as i32 + RecordHeader::SIZE as i32 / 2; 140 | self.header.bbox.grow_from_shape(shape); 141 | self.rec_num += 1; 142 | self.dirty = true; 143 | 144 | Ok(()) 145 | } 146 | 147 | /// Writes a collection of shapes to the file 148 | /// 149 | /// # Examples 150 | /// 151 | /// ``` 152 | /// # fn main() -> Result<(), shapefile::Error> { 153 | /// use shapefile::Point; 154 | /// let mut writer = shapefile::ShapeWriter::from_path("points.shp")?; 155 | /// let points = vec![Point::new(0.0, 0.0), Point::new(1.0, 0.0), Point::new(2.0, 0.0)]; 156 | /// 157 | /// writer.write_shapes(&points)?; 158 | /// # std::fs::remove_file("points.shp")?; 159 | /// # std::fs::remove_file("points.shx")?; 160 | /// # Ok(()) 161 | /// # } 162 | /// ``` 163 | /// 164 | /// ``` 165 | /// # fn main() -> Result<(), shapefile::Error> { 166 | /// use shapefile::{Point, Polyline}; 167 | /// let mut writer = shapefile::ShapeWriter::from_path("polylines.shp")?; 168 | /// let points = vec![Point::new(0.0, 0.0), Point::new(1.0, 0.0), Point::new(2.0, 0.0)]; 169 | /// let polyline = Polyline::new(points); 170 | /// 171 | /// writer.write_shapes(&vec![polyline])?; 172 | /// # std::fs::remove_file("polylines.shp")?; 173 | /// # std::fs::remove_file("polylines.shx")?; 174 | /// # Ok(()) 175 | /// # } 176 | /// ``` 177 | pub fn write_shapes<'a, S: EsriShape + 'a, C: IntoIterator>( 178 | mut self, 179 | container: C, 180 | ) -> Result<(), Error> { 181 | for shape in container { 182 | self.write_shape(shape)?; 183 | } 184 | Ok(()) 185 | } 186 | 187 | /// Finalizes the file by updating the header 188 | /// 189 | /// * Also flushes the destinations 190 | pub fn finalize(&mut self) -> Result<(), Error> { 191 | if !self.dirty { 192 | return Ok(()); 193 | } 194 | 195 | if self.header.bbox.max.m == f64::MIN && self.header.bbox.min.m == f64::MAX { 196 | self.header.bbox.max.m = 0.0; 197 | self.header.bbox.min.m = 0.0; 198 | } 199 | 200 | if self.header.bbox.max.z == f64::MIN && self.header.bbox.min.z == f64::MAX { 201 | self.header.bbox.max.z = 0.0; 202 | self.header.bbox.min.z = 0.0; 203 | } 204 | 205 | self.shp_dest.seek(SeekFrom::Start(0))?; 206 | self.header.write_to(&mut self.shp_dest)?; 207 | self.shp_dest.seek(SeekFrom::End(0))?; 208 | self.shp_dest.flush()?; 209 | 210 | if let Some(shx_dest) = &mut self.shx_dest { 211 | let mut shx_header = self.header; 212 | shx_header.file_length = header::HEADER_SIZE / 2 213 | + ((self.rec_num - 1) as i32 * 2 * size_of::() as i32 / 2); 214 | shx_dest.seek(SeekFrom::Start(0))?; 215 | shx_header.write_to(shx_dest)?; 216 | shx_dest.seek(SeekFrom::End(0))?; 217 | shx_dest.flush()?; 218 | } 219 | self.dirty = false; 220 | Ok(()) 221 | } 222 | } 223 | 224 | impl Drop for ShapeWriter { 225 | fn drop(&mut self) { 226 | let _ = self.finalize(); 227 | } 228 | } 229 | 230 | impl ShapeWriter> { 231 | /// Creates a new writer from a path. 232 | /// Creates both a .shp and .shx files 233 | /// 234 | /// 235 | /// # Examples 236 | /// 237 | /// ```no_run 238 | /// let writer = shapefile::ShapeWriter::from_path("new_file.shp"); 239 | /// ``` 240 | pub fn from_path>(path: P) -> Result { 241 | let shp_path = path.as_ref().to_path_buf(); 242 | let shx_path = shp_path.with_extension("shx"); 243 | 244 | let shp_file = BufWriter::new(File::create(shp_path)?); 245 | let shx_file = BufWriter::new(File::create(shx_path)?); 246 | 247 | Ok(Self::with_shx(shp_file, shx_file)) 248 | } 249 | } 250 | 251 | /// The Writer writes a complete shapefile that is, it 252 | /// writes the 3 mandatory files (.shp, .shx, .dbf) 253 | /// 254 | /// The recommended way to create a new shapefile is via the 255 | /// [Writer::from_path] or [Writer::from_path_with_info] associated functions. 256 | /// 257 | /// # Examples 258 | /// 259 | /// To create a Writer that writes a .dbf file that has the same 260 | /// structure as .dbf read earlier you will have to do: 261 | /// 262 | /// ``` 263 | /// # fn main() -> Result<(), shapefile::Error> { 264 | /// let mut reader = shapefile::Reader::from_path("tests/data/multipatch.shp")?; 265 | /// let shape_records = reader.read()?; 266 | /// let table_info = reader.into_table_info(); 267 | /// 268 | /// let writer = shapefile::Writer::from_path_with_info("new_multipatch.shp", table_info); 269 | /// 270 | /// # std::fs::remove_file("new_multipatch.shp")?; 271 | /// # std::fs::remove_file("new_multipatch.shx")?; 272 | /// # std::fs::remove_file("new_multipatch.dbf")?; 273 | /// # Ok(()) 274 | /// # } 275 | /// ``` 276 | pub struct Writer { 277 | shape_writer: ShapeWriter, 278 | dbase_writer: dbase::TableWriter, 279 | } 280 | 281 | impl Writer { 282 | /// Creates a new writer using the provided ShapeWriter and TableWriter 283 | /// 284 | /// # Example 285 | /// 286 | /// Creating a Writer that writes to in memory buffers. 287 | /// 288 | /// ``` 289 | /// # fn main() -> Result<(), shapefile::Error> { 290 | /// use std::convert::TryInto; 291 | /// let mut shp_dest = std::io::Cursor::new(Vec::::new()); 292 | /// let mut shx_dest = std::io::Cursor::new(Vec::::new()); 293 | /// let mut dbf_dest = std::io::Cursor::new(Vec::::new()); 294 | /// 295 | /// let shape_writer = shapefile::ShapeWriter::with_shx(&mut shp_dest, &mut shx_dest); 296 | /// let dbase_writer = dbase::TableWriterBuilder::new() 297 | /// .add_character_field("Name".try_into().unwrap(), 50) 298 | /// .build_with_dest(&mut dbf_dest); 299 | /// 300 | /// let shape_writer = shapefile::Writer::new(shape_writer, dbase_writer); 301 | /// # Ok(()) 302 | /// # } 303 | /// ``` 304 | pub fn new(shape_writer: ShapeWriter, dbase_writer: dbase::TableWriter) -> Self { 305 | Self { 306 | shape_writer, 307 | dbase_writer, 308 | } 309 | } 310 | 311 | pub fn write_shape_and_record( 312 | &mut self, 313 | shape: &S, 314 | record: &R, 315 | ) -> Result<(), Error> { 316 | self.shape_writer.write_shape(shape)?; 317 | self.dbase_writer.write_record(record)?; 318 | Ok(()) 319 | } 320 | 321 | pub fn write_shapes_and_records< 322 | 'a, 323 | S: EsriShape + 'a, 324 | R: dbase::WritableRecord + 'a, 325 | C: IntoIterator, 326 | >( 327 | mut self, 328 | container: C, 329 | ) -> Result<(), Error> { 330 | for (shape, record) in container.into_iter() { 331 | self.write_shape_and_record(shape, record)?; 332 | } 333 | Ok(()) 334 | } 335 | } 336 | 337 | impl Writer> { 338 | /// Creates all the files needed for the shapefile to be complete (.shp, .shx, .dbf) 339 | /// 340 | /// ``` 341 | /// # fn main() -> Result<(), shapefile::Error> { 342 | /// use std::convert::TryInto; 343 | /// let table_builder = dbase::TableWriterBuilder::new() 344 | /// .add_character_field("name".try_into().unwrap(), 50); 345 | /// let writer = shapefile::Writer::from_path("new_cities.shp", table_builder)?; 346 | /// # std::fs::remove_file("new_cities.shp")?; 347 | /// # std::fs::remove_file("new_cities.shx")?; 348 | /// # std::fs::remove_file("new_cities.dbf")?; 349 | /// # Ok(()) 350 | /// # } 351 | /// ``` 352 | pub fn from_path>( 353 | path: P, 354 | table_builder: TableWriterBuilder, 355 | ) -> Result { 356 | Ok(Self { 357 | shape_writer: ShapeWriter::from_path(path.as_ref())?, 358 | dbase_writer: table_builder 359 | .build_with_file_dest(path.as_ref().with_extension("dbf"))?, 360 | }) 361 | } 362 | 363 | pub fn from_path_with_info>( 364 | path: P, 365 | table_info: dbase::TableInfo, 366 | ) -> Result { 367 | Ok(Self { 368 | shape_writer: ShapeWriter::from_path(path.as_ref())?, 369 | dbase_writer: dbase::TableWriterBuilder::from_table_info(table_info) 370 | .build_with_file_dest(path.as_ref().with_extension("dbf"))?, 371 | }) 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /tests/data/line.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/line.shp -------------------------------------------------------------------------------- /tests/data/line.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/line.shx -------------------------------------------------------------------------------- /tests/data/linem.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/linem.shp -------------------------------------------------------------------------------- /tests/data/linez.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/linez.shp -------------------------------------------------------------------------------- /tests/data/multi_polygon.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/multi_polygon.shp -------------------------------------------------------------------------------- /tests/data/multipatch.dbf: -------------------------------------------------------------------------------- 1 | v A3nameC2 house1 -------------------------------------------------------------------------------- /tests/data/multipatch.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/multipatch.shp -------------------------------------------------------------------------------- /tests/data/multipoint.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/multipoint.shp -------------------------------------------------------------------------------- /tests/data/multipointz.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/multipointz.shp -------------------------------------------------------------------------------- /tests/data/point.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/point.shp -------------------------------------------------------------------------------- /tests/data/point.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/point.shx -------------------------------------------------------------------------------- /tests/data/pointm.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/pointm.shp -------------------------------------------------------------------------------- /tests/data/pointz.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/pointz.shp -------------------------------------------------------------------------------- /tests/data/polygon.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/polygon.shp -------------------------------------------------------------------------------- /tests/data/polygon_hole.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/polygon_hole.shp -------------------------------------------------------------------------------- /tests/data/polygon_hole.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/polygon_hole.shx -------------------------------------------------------------------------------- /tests/data/polygonm.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/polygonm.shp -------------------------------------------------------------------------------- /tests/data/polygonz.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmontaigu/shapefile-rs/59c17f5584e3f1b84b2859180971354e5c64a830/tests/data/polygonz.shp -------------------------------------------------------------------------------- /tests/read_with_index.rs: -------------------------------------------------------------------------------- 1 | extern crate shapefile; 2 | 3 | mod testfiles; 4 | 5 | #[test] 6 | fn test_line_read_nth() { 7 | let mut reader = shapefile::ShapeReader::from_path(testfiles::LINE_PATH).unwrap(); 8 | 9 | if let Some(shape) = reader.read_nth_shape(0) { 10 | let shp = shape.unwrap(); 11 | testfiles::check_line_first_shape(&shp); 12 | } else { 13 | panic!("Should be Some(shape)") 14 | } 15 | 16 | assert!(reader.read_nth_shape(1).is_none()); 17 | } 18 | 19 | #[test] 20 | fn test_size_hint() { 21 | let mut reader = shapefile::ShapeReader::from_path(testfiles::LINE_PATH).unwrap(); 22 | let mut iter = reader.iter_shapes(); 23 | assert_eq!(iter.size_hint(), (1, Some(1))); 24 | iter.next(); 25 | 26 | // size_hint must return the remaining length and not the entire length at the beginning. 27 | assert_eq!(iter.size_hint(), (0, Some(0))); 28 | } 29 | -------------------------------------------------------------------------------- /tests/testfiles.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | extern crate shapefile; 3 | 4 | use shapefile::Point; 5 | 6 | pub const LINE_PATH: &str = "./tests/data/line.shp"; 7 | pub const LINE_SHX_PATH: &str = "./tests/data/line.shx"; 8 | pub const LINEM_PATH: &str = "./tests/data/linem.shp"; 9 | pub const LINEZ_PATH: &str = "./tests/data/linez.shp"; 10 | 11 | pub const POINT_PATH: &str = "./tests/data/point.shp"; 12 | pub const POINT_SHX_PATH: &str = "./tests/data/point.shx"; 13 | pub const POINTM_PATH: &str = "./tests/data/pointm.shp"; 14 | pub const POINTZ_PATH: &str = "./tests/data/pointz.shp"; 15 | 16 | pub const POLYGON_PATH: &str = "./tests/data/polygon.shp"; 17 | pub const POLYGON_HOLE_PATH: &str = "./tests/data/polygon_hole.shp"; 18 | pub const POLYGON_HOLE_SHX_PATH: &str = "./tests/data/polygon_hole.shx"; 19 | pub const POLYGONM_PATH: &str = "./tests/data/polygonm.shp"; 20 | pub const POLYGONZ_PATH: &str = "./tests/data/polygonz.shp"; 21 | 22 | pub const MULTIPOINT_PATH: &str = "./tests/data/multipoint.shp"; 23 | pub const MULTIPOINTZ_PATH: &str = "./tests/data/multipointz.shp"; 24 | 25 | pub const MULTIPATCH_PATH: &str = "./tests/data/multipatch.shp"; 26 | 27 | pub fn check_line_first_shape(shape: &shapefile::Shape) { 28 | if let shapefile::Shape::Polyline(shp) = shape { 29 | assert_eq!(shp.bbox().min.x, 1.0); 30 | assert_eq!(shp.bbox().min.y, 1.0); 31 | assert_eq!(shp.bbox().max.x, 5.0); 32 | assert_eq!(shp.bbox().max.y, 6.0); 33 | let first_part = vec![ 34 | Point { x: 1.0, y: 5.0 }, 35 | Point { x: 5.0, y: 5.0 }, 36 | Point { x: 5.0, y: 1.0 }, 37 | Point { x: 3.0, y: 3.0 }, 38 | Point { x: 1.0, y: 1.0 }, 39 | ]; 40 | let second_part = vec![Point { x: 3.0, y: 2.0 }, Point { x: 2.0, y: 6.0 }]; 41 | assert_eq!(shp.parts()[0], first_part.as_slice()); 42 | assert_eq!(shp.parts()[1], second_part.as_slice()); 43 | } else { 44 | panic!("The shape is not a Polyline"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/write_tests.rs: -------------------------------------------------------------------------------- 1 | extern crate shapefile; 2 | 3 | mod testfiles; 4 | 5 | use shapefile::writer::ShapeWriter; 6 | use shapefile::{Point, Polygon, PolygonRing, Polyline}; 7 | use std::io::Cursor; 8 | 9 | fn read_a_file(path: &str) -> std::io::Result> { 10 | use std::io::Read; 11 | 12 | let mut file = std::fs::File::open(path)?; 13 | let mut data: Vec = vec![]; 14 | file.read_to_end(&mut data)?; 15 | Ok(data) 16 | } 17 | 18 | #[test] 19 | fn single_point() { 20 | let point = Point::new(122.0, 37.0); 21 | let mut shp: Cursor> = Cursor::new(vec![]); 22 | let mut shx: Cursor> = Cursor::new(vec![]); 23 | let writer = ShapeWriter::with_shx(&mut shp, &mut shx); 24 | writer.write_shapes(&vec![point]).unwrap(); 25 | 26 | let expected = read_a_file(testfiles::POINT_PATH).unwrap(); 27 | assert_eq!(shp.get_ref(), &expected); 28 | 29 | let expected = read_a_file(testfiles::POINT_SHX_PATH).unwrap(); 30 | assert_eq!(&shx.get_ref()[..100], &expected[..100]); 31 | } 32 | 33 | #[test] 34 | fn multi_line() { 35 | let point = Polyline::with_parts(vec![ 36 | vec![ 37 | Point::new(1.0, 5.0), 38 | Point::new(5.0, 5.0), 39 | Point::new(5.0, 1.0), 40 | Point::new(3.0, 3.0), 41 | Point::new(1.0, 1.0), 42 | ], 43 | vec![Point::new(3.0, 2.0), Point::new(2.0, 6.0)], 44 | ]); 45 | let mut shp: Cursor> = Cursor::new(vec![]); 46 | let mut shx: Cursor> = Cursor::new(vec![]); 47 | let writer = ShapeWriter::with_shx(&mut shp, &mut shx); 48 | writer.write_shapes(&vec![point]).unwrap(); 49 | 50 | let expected = read_a_file(testfiles::LINE_PATH).unwrap(); 51 | assert_eq!(shp.get_ref(), &expected); 52 | 53 | let expected = read_a_file(testfiles::LINE_SHX_PATH).unwrap(); 54 | assert_eq!(shx.get_ref(), &expected); 55 | } 56 | 57 | #[test] 58 | fn polygon_inner() { 59 | let point = Polygon::with_rings(vec![ 60 | PolygonRing::Outer(vec![ 61 | Point::new(-120.0, 60.0), 62 | Point::new(120.0, 60.0), 63 | Point::new(120.0, -60.0), 64 | Point::new(-120.0, -60.0), 65 | Point::new(-120.0, 60.0), 66 | ]), 67 | PolygonRing::Inner(vec![ 68 | Point::new(-60.0, 30.0), 69 | Point::new(-60.0, -30.0), 70 | Point::new(60.0, -30.0), 71 | Point::new(60.0, 30.0), 72 | Point::new(-60.0, 30.0), 73 | ]), 74 | ]); 75 | let mut shp: Cursor> = Cursor::new(vec![]); 76 | let mut shx: Cursor> = Cursor::new(vec![]); 77 | let writer = ShapeWriter::with_shx(&mut shp, &mut shx); 78 | writer.write_shapes(&vec![point]).unwrap(); 79 | 80 | let expected = read_a_file(testfiles::POLYGON_HOLE_PATH).unwrap(); 81 | assert_eq!(shp.get_ref(), &expected); 82 | 83 | let expected = read_a_file(testfiles::POLYGON_HOLE_SHX_PATH).unwrap(); 84 | assert_eq!(shx.get_ref(), &expected); 85 | } 86 | 87 | /// Same polygon as test above, but the points for the ring are in the 88 | /// incorrect order for a shapefile, so this test if we reorder points correctly 89 | #[test] 90 | fn polygon_inner_is_correctly_reordered() { 91 | let point = Polygon::with_rings(vec![ 92 | PolygonRing::Outer(vec![ 93 | Point::new(-120.0, 60.0), 94 | Point::new(-120.0, -60.0), 95 | Point::new(120.0, -60.0), 96 | Point::new(120.0, 60.0), 97 | Point::new(-120.0, 60.0), 98 | ]), 99 | PolygonRing::Inner(vec![ 100 | Point::new(-60.0, 30.0), 101 | Point::new(60.0, 30.0), 102 | Point::new(60.0, -30.0), 103 | Point::new(-60.0, -30.0), 104 | Point::new(-60.0, 30.0), 105 | ]), 106 | ]); 107 | let mut shp: Cursor> = Cursor::new(vec![]); 108 | let mut shx: Cursor> = Cursor::new(vec![]); 109 | let writer = ShapeWriter::with_shx(&mut shp, &mut shx); 110 | writer.write_shapes(&vec![point]).unwrap(); 111 | 112 | let expected = read_a_file(testfiles::POLYGON_HOLE_PATH).unwrap(); 113 | assert_eq!(shp.get_ref(), &expected); 114 | 115 | let expected = read_a_file(testfiles::POLYGON_HOLE_SHX_PATH).unwrap(); 116 | assert_eq!(shx.get_ref(), &expected); 117 | } 118 | 119 | /// Same polygon as test above, but the points for the ring are in the 120 | /// incorrect order for a shapefile, so this test if we reorder points correctly 121 | #[test] 122 | fn shape_writer_explicit_finalize() { 123 | let shape = Polygon::with_rings(vec![ 124 | PolygonRing::Outer(vec![ 125 | Point::new(-120.0, 60.0), 126 | Point::new(-120.0, -60.0), 127 | Point::new(120.0, -60.0), 128 | Point::new(120.0, 60.0), 129 | Point::new(-120.0, 60.0), 130 | ]), 131 | PolygonRing::Inner(vec![ 132 | Point::new(-60.0, 30.0), 133 | Point::new(60.0, 30.0), 134 | Point::new(60.0, -30.0), 135 | Point::new(-60.0, -30.0), 136 | Point::new(-60.0, 30.0), 137 | ]), 138 | ]); 139 | let mut shp: Cursor> = Cursor::new(vec![]); 140 | let mut shx: Cursor> = Cursor::new(vec![]); 141 | let mut writer = ShapeWriter::with_shx(&mut shp, &mut shx); 142 | writer.write_shape(&shape).unwrap(); 143 | writer.finalize().unwrap(); 144 | drop(writer); 145 | 146 | let expected = read_a_file(testfiles::POLYGON_HOLE_PATH).unwrap(); 147 | assert_eq!(shp.get_ref(), &expected); 148 | 149 | let expected = read_a_file(testfiles::POLYGON_HOLE_SHX_PATH).unwrap(); 150 | assert_eq!(shx.get_ref(), &expected); 151 | } 152 | --------------------------------------------------------------------------------