oxigdal_shapefile/lib.rs
1//! OxiGDAL Shapefile Driver - ESRI Shapefile Implementation
2//!
3//! This crate provides a pure Rust implementation of ESRI Shapefile reading and writing
4//! for the OxiGDAL ecosystem. It supports the complete Shapefile format including:
5//!
6//! - `.shp` files (geometry)
7//! - `.dbf` files (attributes)
8//! - `.shx` files (spatial index)
9//!
10//! # Supported Geometry Types
11//!
12//! - Point, PointZ, PointM
13//! - PolyLine, PolyLineZ, PolyLineM
14//! - Polygon, PolygonZ, PolygonM
15//! - MultiPoint, MultiPointZ, MultiPointM
16//!
17//! # Features
18//!
19//! - Pure Rust implementation (no C/Fortran dependencies)
20//! - Support for all DBF field types (Character, Number, Logical, Date, Float)
21//! - Proper encoding handling with code page support
22//! - Comprehensive error handling
23//! - No `unwrap()` or `panic!()` in production code
24//! - Round-trip compatibility (read → modify → write)
25//! - Spatial index (.shx) support
26//!
27//! # Example - Reading
28//!
29//! ```rust,no_run
30//! use oxigdal_shapefile::ShapefileReader;
31//!
32//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
33//! // Open a Shapefile (reads .shp, .dbf, and .shx)
34//! let reader = ShapefileReader::open("path/to/shapefile")?;
35//!
36//! // Read all features
37//! let features = reader.read_features()?;
38//!
39//! for feature in &features {
40//! println!("Record {}: {:?}", feature.record_number, feature.geometry);
41//! println!("Attributes: {:?}", feature.attributes);
42//! }
43//! # Ok(())
44//! # }
45//! ```
46//!
47//! # Example - Writing
48//!
49//! ```rust,no_run
50//! use oxigdal_shapefile::{ShapefileWriter, ShapefileSchemaBuilder};
51//! use oxigdal_shapefile::shp::shapes::ShapeType;
52//! use std::env;
53//!
54//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
55//! // Create schema
56//! let schema = ShapefileSchemaBuilder::new()
57//! .add_character_field("NAME", 50)?
58//! .add_numeric_field("VALUE", 10, 2)?
59//! .build();
60//!
61//! // Create writer
62//! let temp_dir = env::temp_dir();
63//! let output_path = temp_dir.join("output");
64//! let mut writer = ShapefileWriter::new(output_path, ShapeType::Point, schema)?;
65//!
66//! // Write features (example omitted for brevity)
67//! // writer.write_features(&features)?;
68//! # Ok(())
69//! # }
70//! ```
71//!
72//! # File Format
73//!
74//! A Shapefile consists of three required files:
75//!
76//! 1. **`.shp`** - Main file containing geometry data
77//! 2. **`.dbf`** - dBase file containing attribute data
78//! 3. **`.shx`** - Index file containing record offsets
79//!
80//! Additional optional files include `.prj` (projection), `.cpg` (code page), etc.
81//!
82//! # Binary Format Details
83//!
84//! ## .shp File Structure
85//!
86//! - Header (100 bytes)
87//! - File code: 9994 (big endian)
88//! - File length in 16-bit words (big endian)
89//! - Version: 1000 (little endian)
90//! - Shape type (little endian)
91//! - Bounding box (8 doubles, little endian)
92//! - Records (variable length)
93//! - Record header (8 bytes, big endian)
94//! - Shape content (variable, little endian)
95//!
96//! ## .dbf File Structure
97//!
98//! - Header (32 bytes)
99//! - Version, date, record count, header size, record size
100//! - Field descriptors (32 bytes each)
101//! - Header terminator (0x0D)
102//! - Records (fixed length based on field descriptors)
103//! - File terminator (0x1A)
104//!
105//! ## .shx File Structure
106//!
107//! - Header (100 bytes, same as .shp)
108//! - Index entries (8 bytes each)
109//! - Offset (4 bytes, big endian)
110//! - Content length (4 bytes, big endian)
111//!
112//! # COOLJAPAN Policies
113//!
114//! - Pure Rust implementation (no C/C++ dependencies)
115//! - No `unwrap()` or `expect()` in production code
116//! - Comprehensive error handling with descriptive errors
117//! - Extensive testing (unit + integration + property-based)
118//! - Clean API design following Rust idioms
119//! - Files under 2000 lines (use splitrs for refactoring)
120//!
121//! # Performance Considerations
122//!
123//! - Buffered I/O for efficient reading/writing
124//! - Spatial index (.shx) for fast random access
125//! - Streaming API for large files (iterate records one at a time)
126//! - Zero-copy optimizations where possible
127//!
128//! # Limitations
129//!
130//! - Currently only Point geometries are fully supported for conversion to OxiGDAL
131//! - PolyLine, Polygon, and MultiPoint parsing is implemented but conversion pending
132//! - MultiPatch (3D surfaces) support is limited
133//! - No support for memo fields (.dbt files)
134//!
135//! # References
136//!
137//! - [ESRI Shapefile Technical Description](https://www.esri.com/content/dam/esrisites/sitecore-archive/Files/Pdfs/library/whitepapers/pdfs/shapefile.pdf)
138//! - [dBase File Format](http://www.dbase.com/Knowledgebase/INT/db7_file_fmt.htm)
139
140#![cfg_attr(not(feature = "std"), no_std)]
141#![warn(clippy::all)]
142// Pedantic disabled to reduce noise - default clippy::all is sufficient
143// #![warn(clippy::pedantic)]
144#![deny(clippy::unwrap_used)]
145#![deny(clippy::panic)]
146#![allow(clippy::module_name_repetitions)]
147#![allow(clippy::similar_names)]
148#![allow(clippy::cast_possible_truncation)]
149#![allow(clippy::cast_sign_loss)]
150#![allow(clippy::cast_precision_loss)]
151// Allow slice patterns
152#![allow(clippy::borrow_as_ptr)]
153// Allow partial documentation
154#![allow(missing_docs)]
155// Allow collapsible match for explicit branching
156#![allow(clippy::collapsible_match)]
157
158#[cfg(feature = "std")]
159extern crate std;
160
161pub mod dbf;
162pub mod error;
163pub mod reader;
164pub mod shp;
165pub mod shx;
166pub mod writer;
167
168// Re-export commonly used types
169pub use error::{Result, ShapefileError};
170pub use reader::{ShapefileFeature, ShapefileReader};
171pub use writer::{ShapefileSchemaBuilder, ShapefileWriter};
172
173// Re-export shape types
174pub use shp::shapes::{MultiPartShapeM, MultiPartShapeZ, Point, PointM, PointZ, ShapeType};
175pub use shp::{Shape, ShapeRecord};
176
177// Re-export DBF types
178pub use dbf::{FieldDescriptor, FieldType, FieldValue};
179
180/// Crate version
181pub const VERSION: &str = env!("CARGO_PKG_VERSION");
182
183/// Crate name
184pub const NAME: &str = env!("CARGO_PKG_NAME");
185
186/// Shapefile magic number (file code)
187pub const FILE_CODE: i32 = 9994;
188
189/// Shapefile version
190pub const FILE_VERSION: i32 = 1000;
191
192/// Shapefile file extension
193pub const FILE_EXTENSION: &str = ".shp";
194
195/// DBF file extension
196pub const DBF_EXTENSION: &str = ".dbf";
197
198/// SHX file extension
199pub const SHX_EXTENSION: &str = ".shx";
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn test_constants() {
207 assert!(!VERSION.is_empty());
208 assert_eq!(NAME, "oxigdal-shapefile");
209 assert_eq!(FILE_CODE, 9994);
210 assert_eq!(FILE_VERSION, 1000);
211 assert_eq!(FILE_EXTENSION, ".shp");
212 assert_eq!(DBF_EXTENSION, ".dbf");
213 assert_eq!(SHX_EXTENSION, ".shx");
214 }
215
216 #[test]
217 fn test_shape_type_exports() {
218 // Ensure shape types are accessible
219 let _point = ShapeType::Point;
220 let _polygon = ShapeType::Polygon;
221 let _pointz = ShapeType::PointZ;
222 }
223
224 #[test]
225 fn test_field_type_exports() {
226 // Ensure field types are accessible
227 let _char = FieldType::Character;
228 let _num = FieldType::Number;
229 let _logical = FieldType::Logical;
230 }
231}