1use crate::dbf::{DbfReader, FieldDescriptor};
7use crate::error::{Result, ShapefileError};
8use crate::shp::{Shape, ShapefileHeader, ShpReader};
9use crate::shx::{IndexEntry, ShxReader};
10use oxigdal_core::vector::{
11 Coordinate, Feature, Geometry, LineString as CoreLineString,
12 MultiLineString as CoreMultiLineString, MultiPoint as CoreMultiPoint, Point as CorePoint,
13 Polygon as CorePolygon, PropertyValue,
14};
15use std::collections::HashMap;
16use std::fs::File;
17use std::io::BufReader;
18use std::path::{Path, PathBuf};
19
20#[derive(Debug, Clone)]
22pub struct ShapefileFeature {
23 pub record_number: i32,
25 pub geometry: Option<Geometry>,
27 pub attributes: HashMap<String, PropertyValue>,
29}
30
31impl ShapefileFeature {
32 pub fn new(
34 record_number: i32,
35 geometry: Option<Geometry>,
36 attributes: HashMap<String, PropertyValue>,
37 ) -> Self {
38 Self {
39 record_number,
40 geometry,
41 attributes,
42 }
43 }
44
45 pub fn to_oxigdal_feature(&self) -> Result<Feature> {
47 let geometry = self
48 .geometry
49 .clone()
50 .ok_or_else(|| ShapefileError::invalid_geometry("feature has no geometry"))?;
51
52 let mut feature = Feature::new(geometry);
53
54 for (key, value) in &self.attributes {
56 feature.set_property(key, value.clone());
57 }
58
59 Ok(feature)
60 }
61}
62
63pub struct ShapefileReader {
65 base_path: PathBuf,
67 header: ShapefileHeader,
69 field_descriptors: Vec<FieldDescriptor>,
71 index_entries: Option<Vec<IndexEntry>>,
73}
74
75impl ShapefileReader {
76 pub fn open<P: AsRef<Path>>(base_path: P) -> Result<Self> {
80 let base_path = base_path.as_ref();
81
82 let shp_path = Self::with_extension(base_path, "shp");
84 let dbf_path = Self::with_extension(base_path, "dbf");
85 let shx_path = Self::with_extension(base_path, "shx");
86
87 let shp_file = File::open(&shp_path).map_err(|_| ShapefileError::MissingFile {
89 file_type: ".shp".to_string(),
90 })?;
91 let shp_reader = BufReader::new(shp_file);
92 let shp_reader = ShpReader::new(shp_reader)?;
93 let header = shp_reader.header().clone();
94
95 let dbf_file = File::open(&dbf_path).map_err(|_| ShapefileError::MissingFile {
97 file_type: ".dbf".to_string(),
98 })?;
99 let dbf_reader = BufReader::new(dbf_file);
100 let dbf_reader = DbfReader::new(dbf_reader)?;
101 let field_descriptors = dbf_reader.field_descriptors().to_vec();
102
103 let index_entries = if shx_path.exists() {
105 let shx_file = File::open(&shx_path).ok();
106 if let Some(file) = shx_file {
107 let shx_reader = BufReader::new(file);
108 let mut shx_reader = ShxReader::new(shx_reader)?;
109 Some(shx_reader.read_all_entries()?)
110 } else {
111 None
112 }
113 } else {
114 None
115 };
116
117 Ok(Self {
118 base_path: base_path.to_path_buf(),
119 header,
120 field_descriptors,
121 index_entries,
122 })
123 }
124
125 pub fn header(&self) -> &ShapefileHeader {
127 &self.header
128 }
129
130 pub fn field_descriptors(&self) -> &[FieldDescriptor] {
132 &self.field_descriptors
133 }
134
135 pub fn index_entries(&self) -> Option<&[IndexEntry]> {
137 self.index_entries.as_deref()
138 }
139
140 pub fn read_features(&self) -> Result<Vec<ShapefileFeature>> {
142 let shp_path = Self::with_extension(&self.base_path, "shp");
144 let dbf_path = Self::with_extension(&self.base_path, "dbf");
145
146 let shp_file = File::open(&shp_path)?;
147 let shp_reader = BufReader::new(shp_file);
148 let mut shp_reader = ShpReader::new(shp_reader)?;
149
150 let dbf_file = File::open(&dbf_path)?;
151 let dbf_reader = BufReader::new(dbf_file);
152 let mut dbf_reader = DbfReader::new(dbf_reader)?;
153
154 let shape_records = shp_reader.read_all_records()?;
156
157 let dbf_records = dbf_reader.read_all_records()?;
159
160 if shape_records.len() != dbf_records.len() {
162 return Err(ShapefileError::RecordMismatch {
163 shp_count: shape_records.len(),
164 dbf_count: dbf_records.len(),
165 });
166 }
167
168 let mut features = Vec::with_capacity(shape_records.len());
170 for (shape_record, dbf_record) in shape_records.iter().zip(dbf_records.iter()) {
171 let geometry = Self::shape_to_geometry(&shape_record.shape)?;
172
173 let attributes = Self::dbf_to_attributes(dbf_record, &self.field_descriptors);
175
176 features.push(ShapefileFeature::new(
177 shape_record.record_number,
178 geometry,
179 attributes,
180 ));
181 }
182
183 Ok(features)
184 }
185
186 fn shape_to_geometry(shape: &Shape) -> Result<Option<Geometry>> {
188 match shape {
189 Shape::Null => Ok(None),
190 Shape::Point(point) => {
191 let oxigdal_point = CorePoint::new(point.x, point.y);
192 Ok(Some(Geometry::Point(oxigdal_point)))
193 }
194 Shape::PointZ(point) => {
195 let oxigdal_point = CorePoint::new(point.x, point.y);
197 Ok(Some(Geometry::Point(oxigdal_point)))
198 }
199 Shape::PointM(point) => {
200 let oxigdal_point = CorePoint::new(point.x, point.y);
201 Ok(Some(Geometry::Point(oxigdal_point)))
202 }
203 Shape::PolyLine(multi_part) => {
204 if multi_part.parts.len() == 1 {
205 let coords: Vec<Coordinate> = multi_part
207 .points
208 .iter()
209 .map(|p| Coordinate::new_2d(p.x, p.y))
210 .collect();
211
212 if coords.len() < 2 {
213 return Ok(None);
214 }
215
216 let linestring = CoreLineString::new(coords).map_err(|e| {
217 ShapefileError::invalid_geometry(format!("Invalid LineString: {}", e))
218 })?;
219 Ok(Some(Geometry::LineString(linestring)))
220 } else {
221 let mut linestrings = Vec::new();
223
224 for i in 0..multi_part.parts.len() {
225 let start_idx = multi_part.parts[i] as usize;
226 let end_idx = if i + 1 < multi_part.parts.len() {
227 multi_part.parts[i + 1] as usize
228 } else {
229 multi_part.points.len()
230 };
231
232 let coords: Vec<Coordinate> = multi_part.points[start_idx..end_idx]
233 .iter()
234 .map(|p| Coordinate::new_2d(p.x, p.y))
235 .collect();
236
237 if coords.len() >= 2 {
238 if let Ok(linestring) = CoreLineString::new(coords) {
239 linestrings.push(linestring);
240 }
241 }
242 }
243
244 if linestrings.is_empty() {
245 Ok(None)
246 } else {
247 Ok(Some(Geometry::MultiLineString(CoreMultiLineString::new(
248 linestrings,
249 ))))
250 }
251 }
252 }
253 Shape::Polygon(multi_part) => {
254 if multi_part.parts.is_empty() {
255 return Ok(None);
256 }
257
258 let exterior_start = multi_part.parts[0] as usize;
260 let exterior_end = if multi_part.parts.len() > 1 {
261 multi_part.parts[1] as usize
262 } else {
263 multi_part.points.len()
264 };
265
266 let exterior_coords: Vec<Coordinate> = multi_part.points
267 [exterior_start..exterior_end]
268 .iter()
269 .map(|p| Coordinate::new_2d(p.x, p.y))
270 .collect();
271
272 if exterior_coords.len() < 4 {
273 return Ok(None);
274 }
275
276 let exterior = CoreLineString::new(exterior_coords).map_err(|e| {
277 ShapefileError::invalid_geometry(format!("Invalid exterior ring: {}", e))
278 })?;
279
280 let mut interiors = Vec::new();
282 for i in 1..multi_part.parts.len() {
283 let start_idx = multi_part.parts[i] as usize;
284 let end_idx = if i + 1 < multi_part.parts.len() {
285 multi_part.parts[i + 1] as usize
286 } else {
287 multi_part.points.len()
288 };
289
290 let interior_coords: Vec<Coordinate> = multi_part.points[start_idx..end_idx]
291 .iter()
292 .map(|p| Coordinate::new_2d(p.x, p.y))
293 .collect();
294
295 if interior_coords.len() >= 4 {
296 if let Ok(interior) = CoreLineString::new(interior_coords) {
297 interiors.push(interior);
298 }
299 }
300 }
301
302 let polygon = CorePolygon::new(exterior, interiors).map_err(|e| {
303 ShapefileError::invalid_geometry(format!("Invalid polygon: {}", e))
304 })?;
305
306 Ok(Some(Geometry::Polygon(polygon)))
307 }
308 Shape::MultiPoint(multi_part) => {
309 let points: Vec<CorePoint> = multi_part
310 .points
311 .iter()
312 .map(|p| CorePoint::new(p.x, p.y))
313 .collect();
314
315 if points.is_empty() {
316 Ok(None)
317 } else {
318 Ok(Some(Geometry::MultiPoint(CoreMultiPoint::new(points))))
319 }
320 }
321 Shape::PolyLineZ(shape_z) => {
324 Self::shape_to_geometry(&Shape::PolyLine(shape_z.base.clone()))
325 }
326 Shape::PolygonZ(shape_z) => {
327 Self::shape_to_geometry(&Shape::Polygon(shape_z.base.clone()))
328 }
329 Shape::MultiPointZ(shape_z) => {
330 Self::shape_to_geometry(&Shape::MultiPoint(shape_z.base.clone()))
331 }
332 Shape::PolyLineM(shape_m) => {
335 Self::shape_to_geometry(&Shape::PolyLine(shape_m.base.clone()))
336 }
337 Shape::PolygonM(shape_m) => {
338 Self::shape_to_geometry(&Shape::Polygon(shape_m.base.clone()))
339 }
340 Shape::MultiPointM(shape_m) => {
341 Self::shape_to_geometry(&Shape::MultiPoint(shape_m.base.clone()))
342 }
343 }
344 }
345
346 fn dbf_to_attributes(
348 dbf_record: &crate::dbf::DbfRecord,
349 field_descriptors: &[FieldDescriptor],
350 ) -> HashMap<String, PropertyValue> {
351 let mut attributes = HashMap::new();
352
353 for (field, value) in field_descriptors.iter().zip(&dbf_record.values) {
354 let property_value = match value {
355 crate::dbf::FieldValue::String(s) => PropertyValue::String(s.clone()),
356 crate::dbf::FieldValue::Integer(i) => PropertyValue::Integer(*i),
357 crate::dbf::FieldValue::Float(f) => PropertyValue::Float(*f),
358 crate::dbf::FieldValue::Boolean(b) => PropertyValue::Bool(*b),
359 crate::dbf::FieldValue::Date(d) => PropertyValue::String(d.clone()),
360 crate::dbf::FieldValue::Null => PropertyValue::Null,
361 };
362
363 attributes.insert(field.name.clone(), property_value);
364 }
365
366 attributes
367 }
368
369 fn with_extension<P: AsRef<Path>>(base_path: P, ext: &str) -> PathBuf {
371 let base = base_path.as_ref();
372
373 if base.extension().is_some() {
375 base.with_extension(ext)
376 } else {
377 let mut path = base.to_path_buf();
379 path.set_extension(ext);
380 path
381 }
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388
389 #[test]
390 fn test_path_extension_helper() {
391 let base = PathBuf::from("/tmp/test");
392 assert_eq!(
393 ShapefileReader::with_extension(&base, "shp"),
394 PathBuf::from("/tmp/test.shp")
395 );
396
397 let base = PathBuf::from("/tmp/test.shp");
398 assert_eq!(
399 ShapefileReader::with_extension(&base, "dbf"),
400 PathBuf::from("/tmp/test.dbf")
401 );
402 }
403
404 #[test]
405 fn test_shapefile_feature_creation() {
406 let mut attributes = HashMap::new();
407 attributes.insert(
408 "name".to_string(),
409 PropertyValue::String("Test".to_string()),
410 );
411 attributes.insert("value".to_string(), PropertyValue::Integer(42));
412
413 let geometry = Some(Geometry::Point(CorePoint::new(10.0, 20.0)));
414
415 let feature = ShapefileFeature::new(1, geometry, attributes);
416 assert_eq!(feature.record_number, 1);
417 assert!(feature.geometry.is_some());
418 assert_eq!(feature.attributes.len(), 2);
419 }
420}