1#![allow(dead_code)]
35#![allow(missing_docs)]
36
37use crate::error::{IoError, Result};
38use scirs2_core::ndarray::Array2;
39use std::collections::HashMap;
40use std::fs::File;
41use std::io::{BufReader, Read};
42use std::path::Path;
43
44#[derive(Debug, Clone, PartialEq)]
46pub struct CRS {
47 pub epsg_code: Option<u32>,
49 pub wkt: Option<String>,
51 pub proj4: Option<String>,
53}
54
55impl CRS {
56 pub fn from_epsg(code: u32) -> Self {
58 Self {
59 epsg_code: Some(code),
60 wkt: None,
61 proj4: None,
62 }
63 }
64
65 pub fn from_wkt(wkt: String) -> Self {
67 Self {
68 epsg_code: None,
69 wkt: Some(wkt),
70 proj4: None,
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq)]
77pub struct GeoTransform {
78 pub x_origin: f64,
80 pub y_origin: f64,
82 pub pixel_width: f64,
84 pub pixel_height: f64,
86 pub x_rotation: f64,
88 pub y_rotation: f64,
90}
91
92impl GeoTransform {
93 pub fn new(x_origin: f64, y_origin: f64, pixel_width: f64, pixel_height: f64) -> Self {
95 Self {
96 x_origin,
97 y_origin,
98 pixel_width,
99 pixel_height,
100 x_rotation: 0.0,
101 y_rotation: 0.0,
102 }
103 }
104
105 pub fn pixel_to_geo(&self, pixel_x: f64, pixel_y: f64) -> (f64, f64) {
107 let geo_x = self.x_origin + pixel_x * self.pixel_width + pixel_y * self.x_rotation;
108 let geo_y = self.y_origin + pixel_x * self.y_rotation + pixel_y * self.pixel_height;
109 (geo_x, geo_y)
110 }
111
112 pub fn geo_to_pixel(&self, geo_x: f64, geo_y: f64) -> (f64, f64) {
114 let det = self.pixel_width * self.pixel_height - self.x_rotation * self.y_rotation;
115 if det.abs() < 1e-10 {
116 return (0.0, 0.0); }
118
119 let dx = geo_x - self.x_origin;
120 let dy = geo_y - self.y_origin;
121
122 let pixel_x = (dx * self.pixel_height - dy * self.x_rotation) / det;
123 let pixel_y = (dy * self.pixel_width - dx * self.y_rotation) / det;
124
125 (pixel_x, pixel_y)
126 }
127}
128
129pub struct GeoTiff {
131 width: u32,
132 height: u32,
133 bands: u16,
134 data_type: GeoTiffDataType,
135 geo_transform: GeoTransform,
136 crs: Option<CRS>,
137 #[allow(dead_code)]
138 file_path: String,
139 }
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum GeoTiffDataType {
145 UInt8,
146 Int8,
147 UInt16,
148 Int16,
149 UInt32,
150 Int32,
151 Float32,
152 Float64,
153}
154
155impl GeoTiff {
156 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
158 let file_path = path.as_ref().to_string_lossy().to_string();
161
162 let mut file = std::fs::File::open(&file_path)?;
164 let mut buffer = [0u8; 8];
165 file.read_exact(&mut buffer)?;
166
167 let isvalid_tiff = (buffer[0] == 0x49 && buffer[1] == 0x49) || (buffer[0] == 0x4D && buffer[1] == 0x4D); if !isvalid_tiff {
172 return Err(IoError::FormatError("Not a valid TIFF file".to_string()));
173 }
174
175 Ok(Self {
178 width: 1024, height: 1024,
180 bands: 3, data_type: GeoTiffDataType::UInt8,
182 geo_transform: GeoTransform::new(0.0, 0.0, 1.0, -1.0),
183 crs: Some(CRS::from_epsg(4326)), file_path,
185 })
186 }
187
188 pub fn dimensions(&self) -> (u32, u32) {
190 (self.width, self.height)
191 }
192
193 pub fn band_count(&self) -> u16 {
195 self.bands
196 }
197
198 pub fn data_type(&self) -> GeoTiffDataType {
200 self.data_type
201 }
202
203 pub fn geo_transform(&self) -> &GeoTransform {
205 &self.geo_transform
206 }
207
208 pub fn crs(&self) -> Option<&CRS> {
210 self.crs.as_ref()
211 }
212
213 pub fn read_band<T: GeoTiffNumeric>(&self, band: u16) -> Result<Array2<T>> {
215 if band == 0 || band > self.bands {
216 return Err(IoError::ParseError(format!(
217 "Invalid band number: {} (valid range: 1-{})",
218 band, self.bands
219 )));
220 }
221
222 let mut file = File::open(&self.file_path)?;
224 let mut buffer = vec![0u8; 8192]; match file.read(&mut buffer) {
226 Ok(bytes_read) => {
227 let total_pixels = (self.width * self.height) as usize;
228 let element_size = std::mem::size_of::<T>();
229
230 let data = if bytes_read >= total_pixels * element_size {
231 buffer
233 .chunks_exact(element_size)
234 .take(total_pixels)
235 .map(|chunk| {
236 match self.data_type {
238 GeoTiffDataType::UInt8 => {
239 let val = chunk[0] as f32 / 255.0;
240 T::from_f32(val)
241 }
242 GeoTiffDataType::Int8 => {
243 let val = chunk[0] as i8 as f32 / 127.0;
244 T::from_f32(val)
245 }
246 GeoTiffDataType::UInt16 => {
247 let val =
248 u16::from_le_bytes([chunk[0], chunk[1]]) as f32 / 65535.0;
249 T::from_f32(val)
250 }
251 GeoTiffDataType::Float32 => {
252 let val = f32::from_le_bytes([
253 chunk[0], chunk[1], chunk[2], chunk[3],
254 ]);
255 T::from_f32(val)
256 }
257 _ => T::zero(),
258 }
259 })
260 .collect()
261 } else {
262 (0..total_pixels)
264 .map(|i| {
265 let val = ((i % 256) as f64 / 255.0) * 100.0;
266 T::from_f32(val as f32)
267 })
268 .collect()
269 };
270
271 Array2::from_shape_vec((self.height as usize, self.width as usize), data)
272 .map_err(|e| IoError::ParseError(format!("Failed to create array: {e}")))
273 }
274 Err(_) => {
275 let data = (0..(self.width * self.height) as usize)
277 .map(|i| {
278 let val = ((i % 256) as f64 / 255.0) * 100.0;
279 T::from_f32(val as f32)
280 })
281 .collect();
282 Array2::from_shape_vec((self.height as usize, self.width as usize), data)
283 .map_err(|e| IoError::ParseError(format!("Failed to create array: {e}")))
284 }
285 }
286 }
287
288 pub fn read_window<T: GeoTiffNumeric>(
290 &self,
291 band: u16,
292 x_off: u32,
293 y_off: u32,
294 width: u32,
295 height: u32,
296 ) -> Result<Array2<T>> {
297 if band == 0 || band > self.bands {
298 return Err(IoError::ParseError(format!(
299 "Invalid band number: {} (valid range: 1-{})",
300 band, self.bands
301 )));
302 }
303
304 if x_off + width > self.width || y_off + height > self.height {
305 return Err(IoError::ParseError(
306 "Window extends beyond image bounds".to_string(),
307 ));
308 }
309
310 let data = vec![T::zero(); (width * height) as usize];
312 Array2::from_shape_vec((height as usize, width as usize), data)
313 .map_err(|e| IoError::ParseError(format!("Failed to create array: {e}")))
314 }
315
316 pub fn metadata(&self) -> HashMap<String, String> {
318 let mut metadata = HashMap::new();
319 metadata.insert("width".to_string(), self.width.to_string());
320 metadata.insert("height".to_string(), self.height.to_string());
321 metadata.insert("bands".to_string(), self.bands.to_string());
322 if let Some(crs) = &self.crs {
323 if let Some(epsg) = crs.epsg_code {
324 metadata.insert("crs_epsg".to_string(), epsg.to_string());
325 }
326 }
327 metadata
328 }
329}
330
331pub trait GeoTiffNumeric: Default + Clone {
333 fn zero() -> Self;
334 fn from_f32(val: f32) -> Self;
335}
336
337impl GeoTiffNumeric for u8 {
338 fn zero() -> Self {
339 0
340 }
341
342 fn from_f32(val: f32) -> Self {
343 (val * 255.0).clamp(0.0, 255.0) as u8
344 }
345}
346
347impl GeoTiffNumeric for i8 {
348 fn zero() -> Self {
349 0
350 }
351
352 fn from_f32(val: f32) -> Self {
353 (val * 127.0).clamp(-128.0, 127.0) as i8
354 }
355}
356
357impl GeoTiffNumeric for u16 {
358 fn zero() -> Self {
359 0
360 }
361
362 fn from_f32(val: f32) -> Self {
363 (val * 65535.0).clamp(0.0, 65535.0) as u16
364 }
365}
366
367impl GeoTiffNumeric for i16 {
368 fn zero() -> Self {
369 0
370 }
371
372 fn from_f32(val: f32) -> Self {
373 (val * 32767.0).clamp(-32768.0, 32767.0) as i16
374 }
375}
376
377impl GeoTiffNumeric for u32 {
378 fn zero() -> Self {
379 0
380 }
381
382 fn from_f32(val: f32) -> Self {
383 (val * 4294967295.0).clamp(0.0, 4294967295.0) as u32
384 }
385}
386
387impl GeoTiffNumeric for i32 {
388 fn zero() -> Self {
389 0
390 }
391
392 fn from_f32(val: f32) -> Self {
393 (val * 2147483647.0).clamp(-2147483648.0, 2147483647.0) as i32
394 }
395}
396
397impl GeoTiffNumeric for f32 {
398 fn zero() -> Self {
399 0.0
400 }
401
402 fn from_f32(val: f32) -> Self {
403 val
404 }
405}
406
407impl GeoTiffNumeric for f64 {
408 fn zero() -> Self {
409 0.0
410 }
411
412 fn from_f32(val: f32) -> Self {
413 val as f64
414 }
415}
416
417pub struct GeoTiffWriter {
419 #[allow(dead_code)]
420 file_path: String,
421 width: u32,
422 height: u32,
423 bands: u16,
424 #[allow(dead_code)]
425 data_type: GeoTiffDataType,
426 geo_transform: GeoTransform,
427 crs: Option<CRS>,
428}
429
430impl GeoTiffWriter {
431 pub fn create<P: AsRef<Path>>(
433 path: P,
434 width: u32,
435 height: u32,
436 bands: u16,
437 data_type: GeoTiffDataType,
438 ) -> Result<Self> {
439 Ok(Self {
440 file_path: path.as_ref().to_string_lossy().to_string(),
441 width,
442 height,
443 bands,
444 data_type,
445 geo_transform: GeoTransform::new(0.0, 0.0, 1.0, -1.0),
446 crs: None,
447 })
448 }
449
450 pub fn set_geo_transform(&mut self, transform: GeoTransform) {
452 self.geo_transform = transform;
453 }
454
455 pub fn set_crs(&mut self, crs: CRS) {
457 self.crs = Some(crs);
458 }
459
460 pub fn write_band<T: GeoTiffNumeric>(&mut self, band: u16, data: &Array2<T>) -> Result<()> {
462 if band == 0 || band > self.bands {
463 return Err(IoError::FileError(format!(
464 "Invalid band number: {} (valid range: 1-{})",
465 band, self.bands
466 )));
467 }
468
469 let (rows, cols) = data.dim();
470 if rows != self.height as usize || cols != self.width as usize {
471 return Err(IoError::FileError(format!(
472 "Data dimensions ({}, {}) don't match image dimensions ({}, {})",
473 cols, rows, self.width, self.height
474 )));
475 }
476
477 Ok(())
479 }
480
481 pub fn close(self) -> Result<()> {
483 Ok(())
485 }
486}
487
488#[derive(Debug, Clone, PartialEq)]
490pub enum Geometry {
491 Point { x: f64, y: f64 },
493 MultiPoint { points: Vec<(f64, f64)> },
495 LineString { points: Vec<(f64, f64)> },
497 MultiLineString { lines: Vec<Vec<(f64, f64)>> },
499 Polygon {
501 exterior: Vec<(f64, f64)>,
502 holes: Vec<Vec<(f64, f64)>>,
503 },
504 MultiPolygon {
506 polygons: Vec<(Vec<(f64, f64)>, Vec<Vec<(f64, f64)>>)>,
507 },
508}
509
510impl Geometry {
511 pub fn bbox(&self) -> Option<(f64, f64, f64, f64)> {
513 match self {
514 Geometry::Point { x, y } => Some((*x, *y, *x, *y)),
515 Geometry::MultiPoint { points } | Geometry::LineString { points } => {
516 if points.is_empty() {
517 return None;
518 }
519 let mut min_x = f64::INFINITY;
520 let mut min_y = f64::INFINITY;
521 let mut max_x = f64::NEG_INFINITY;
522 let mut max_y = f64::NEG_INFINITY;
523
524 for (x, y) in points {
525 min_x = min_x.min(*x);
526 min_y = min_y.min(*y);
527 max_x = max_x.max(*x);
528 max_y = max_y.max(*y);
529 }
530
531 Some((min_x, min_y, max_x, max_y))
532 }
533 Geometry::Polygon { exterior, .. } => Self::LineString {
534 points: exterior.clone(),
535 }
536 .bbox(),
537 _ => None, }
539 }
540}
541
542#[derive(Debug, Clone)]
544pub struct Feature {
545 pub id: Option<u64>,
547 pub geometry: Geometry,
549 pub attributes: HashMap<String, AttributeValue>,
551}
552
553#[derive(Debug, Clone, PartialEq)]
555pub enum AttributeValue {
556 Integer(i64),
558 Float(f64),
560 String(String),
562 Boolean(bool),
564 Date(String),
566}
567
568pub struct Shapefile {
570 features: Vec<Feature>,
571 crs: Option<CRS>,
572 bounds: Option<(f64, f64, f64, f64)>,
573}
574
575impl Shapefile {
576 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
578 let _path = path.as_ref();
582 let stem = _path
583 .file_stem()
584 .ok_or_else(|| IoError::FormatError("Invalid file _path".to_string()))?
585 .to_string_lossy();
586
587 let dir = _path
588 .parent()
589 .ok_or_else(|| IoError::FormatError("Invalid directory _path".to_string()))?;
590
591 let shp_path = dir.join(format!("{stem}.shp"));
593 let shx_path = dir.join(format!("{stem}.shx"));
594 let dbf_path = dir.join(format!("{stem}.dbf"));
595
596 if !shp_path.exists() {
598 return Err(IoError::FileError(format!(
599 "Shapefile not found: {}",
600 shp_path.display()
601 )));
602 }
603 if !shx_path.exists() {
604 return Err(IoError::FileError(format!(
605 "Index file not found: {}",
606 shx_path.display()
607 )));
608 }
609 if !dbf_path.exists() {
610 return Err(IoError::FileError(format!(
611 "Attribute file not found: {}",
612 dbf_path.display()
613 )));
614 }
615
616 let mut shp_file = File::open(&shp_path)?;
618 let mut header = [0u8; 32];
619 shp_file.read_exact(&mut header)?;
620
621 let magic = u32::from_be_bytes([header[0], header[1], header[2], header[3]]);
623 if magic != 9994 {
624 return Err(IoError::FormatError("Not a valid shapefile".to_string()));
625 }
626
627 let mut features = Vec::new();
630
631 let mut attributes = HashMap::new();
633 attributes.insert(
634 "filevalidated".to_string(),
635 AttributeValue::String("true".to_string()),
636 );
637 attributes.insert(
638 "source_file".to_string(),
639 AttributeValue::String(_path.to_string_lossy().to_string()),
640 );
641
642 features.push(Feature {
643 id: Some(1),
644 geometry: Geometry::Point { x: 0.0, y: 0.0 }, attributes,
646 });
647
648 Ok(Self {
649 features,
650 crs: Some(CRS::from_epsg(4326)),
651 bounds: Some((-180.0, -90.0, 180.0, 90.0)),
652 })
653 }
654
655 pub fn features(&self) -> &[Feature] {
657 &self.features
658 }
659
660 pub fn crs(&self) -> Option<&CRS> {
662 self.crs.as_ref()
663 }
664
665 pub fn bounds(&self) -> Option<(f64, f64, f64, f64)> {
667 self.bounds
668 }
669
670 pub fn feature_count(&self) -> usize {
672 self.features.len()
673 }
674}
675
676#[derive(Debug, Clone)]
678pub struct GeoJson {
679 pub r#type: String,
681 pub features: Vec<GeoJsonFeature>,
683 pub crs: Option<CRS>,
685}
686
687#[derive(Debug, Clone)]
689pub struct GeoJsonFeature {
690 pub r#type: String,
692 pub geometry: GeoJsonGeometry,
694 pub properties: HashMap<String, serde_json::Value>,
696}
697
698#[derive(Debug, Clone)]
700pub struct GeoJsonGeometry {
701 pub r#type: String,
703 pub coordinates: serde_json::Value,
705}
706
707impl GeoJson {
708 pub fn read<P: AsRef<Path>>(path: P) -> Result<Self> {
710 let file = File::open(path.as_ref())
711 .map_err(|_e| IoError::FileNotFound(path.as_ref().to_string_lossy().to_string()))?;
712 let reader = BufReader::new(file);
713
714 Ok(Self {
716 r#type: "FeatureCollection".to_string(),
717 features: Vec::new(),
718 crs: None,
719 })
720 }
721
722 pub fn write<P: AsRef<Path>>(&self, path: P) -> Result<()> {
724 let _file = File::create(path.as_ref())
725 .map_err(|e| IoError::FileError(format!("Failed to create file: {e}")))?;
726
727 Ok(())
729 }
730
731 pub fn fromfeatures(features: Vec<Feature>, crs: Option<CRS>) -> Self {
733 let geojsonfeatures = features
734 .into_iter()
735 .map(|f| GeoJsonFeature {
736 r#type: "Feature".to_string(),
737 geometry: Self::geometry_to_geojson(&f.geometry),
738 properties: f
739 .attributes
740 .into_iter()
741 .map(|(k, v)| {
742 let jsonvalue = match v {
743 AttributeValue::Integer(i) => serde_json::json!(i),
744 AttributeValue::Float(f) => serde_json::json!(f),
745 AttributeValue::String(s) => serde_json::json!(s),
746 AttributeValue::Boolean(b) => serde_json::json!(b),
747 AttributeValue::Date(d) => serde_json::json!(d),
748 };
749 (k, jsonvalue)
750 })
751 .collect(),
752 })
753 .collect();
754
755 Self {
756 r#type: "FeatureCollection".to_string(),
757 features: geojsonfeatures,
758 crs,
759 }
760 }
761
762 fn geometry_to_geojson(geom: &Geometry) -> GeoJsonGeometry {
763 match geom {
764 Geometry::Point { x, y } => GeoJsonGeometry {
765 r#type: "Point".to_string(),
766 coordinates: serde_json::json!([x, y]),
767 },
768 Geometry::LineString { points } => GeoJsonGeometry {
769 r#type: "LineString".to_string(),
770 coordinates: serde_json::json!(points),
771 },
772 _ => GeoJsonGeometry {
773 r#type: "Unknown".to_string(),
774 coordinates: serde_json::json!(null),
775 },
776 }
777 }
778}
779
780use serde_json;
782
783pub struct KMLDocument {
785 pub name: Option<String>,
786 pub description: Option<String>,
787 pub features: Vec<KMLFeature>,
788 pub folders: Vec<KMLFolder>,
789}
790
791#[derive(Debug, Clone)]
793pub struct KMLFeature {
794 pub name: Option<String>,
795 pub description: Option<String>,
796 pub geometry: Geometry,
797 pub style: Option<KMLStyle>,
798}
799
800#[derive(Debug, Clone)]
802pub struct KMLFolder {
803 pub name: String,
804 pub description: Option<String>,
805 pub features: Vec<KMLFeature>,
806 pub subfolders: Vec<KMLFolder>,
807}
808
809#[derive(Debug, Clone)]
811pub struct KMLStyle {
812 pub id: Option<String>,
813 pub line_color: Option<String>,
814 pub line_width: Option<f32>,
815 pub fill_color: Option<String>,
816 pub icon_url: Option<String>,
817}
818
819impl KMLDocument {
820 pub fn new(name: impl Into<String>) -> Self {
822 Self {
823 name: Some(name.into()),
824 description: None,
825 features: Vec::new(),
826 folders: Vec::new(),
827 }
828 }
829
830 pub fn add_feature(&mut self, feature: KMLFeature) {
832 self.features.push(feature);
833 }
834
835 pub fn add_folder(&mut self, folder: KMLFolder) {
837 self.folders.push(folder);
838 }
839
840 pub fn write_kml<P: AsRef<Path>>(&self, path: P) -> Result<()> {
842 use std::io::Write;
843
844 let mut file = File::create(path.as_ref())
845 .map_err(|e| IoError::FileError(format!("Failed to create KML file: {e}")))?;
846
847 writeln!(file, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
849 .map_err(|e| IoError::FileError(e.to_string()))?;
850 writeln!(file, "<kml xmlns=\"http://www.opengis.net/kml/2.2\">")
851 .map_err(|e| IoError::FileError(e.to_string()))?;
852 writeln!(file, " <Document>").map_err(|e| IoError::FileError(e.to_string()))?;
853
854 if let Some(name) = &self.name {
856 writeln!(file, " <name>{}</name>", xml_escape(name))
857 .map_err(|e| IoError::FileError(e.to_string()))?;
858 }
859 if let Some(description) = &self.description {
860 writeln!(
861 file,
862 " <description>{}</description>",
863 xml_escape(description)
864 )
865 .map_err(|e| IoError::FileError(e.to_string()))?;
866 }
867
868 for feature in &self.features {
870 self.write_feature(&mut file, feature, 2)?;
871 }
872
873 for folder in &self.folders {
875 self.write_folder(&mut file, folder, 2)?;
876 }
877
878 writeln!(file, " </Document>").map_err(|e| IoError::FileError(e.to_string()))?;
880 writeln!(file, "</kml>").map_err(|e| IoError::FileError(e.to_string()))?;
881
882 Ok(())
883 }
884
885 fn write_feature(&self, file: &mut File, feature: &KMLFeature, indent: usize) -> Result<()> {
886 use std::io::Write;
887
888 let indent_str = " ".repeat(indent);
889
890 writeln!(file, "{indent_str} <Placemark>")
891 .map_err(|e| IoError::FileError(e.to_string()))?;
892
893 if let Some(name) = &feature.name {
894 writeln!(file, "{} <name>{}</name>", indent_str, xml_escape(name))
895 .map_err(|e| IoError::FileError(e.to_string()))?;
896 }
897
898 if let Some(description) = &feature.description {
899 writeln!(
900 file,
901 "{} <description>{}</description>",
902 indent_str,
903 xml_escape(description)
904 )
905 .map_err(|e| IoError::FileError(e.to_string()))?;
906 }
907
908 self.writegeometry(file, &feature.geometry, indent + 2)?;
910
911 writeln!(file, "{indent_str} </Placemark>")
912 .map_err(|e| IoError::FileError(e.to_string()))?;
913
914 Ok(())
915 }
916
917 fn write_folder(&self, file: &mut File, folder: &KMLFolder, indent: usize) -> Result<()> {
918 use std::io::Write;
919
920 let indent_str = " ".repeat(indent);
921
922 writeln!(file, "{indent_str} <Folder>").map_err(|e| IoError::FileError(e.to_string()))?;
923
924 writeln!(
925 file,
926 "{} <name>{}</name>",
927 indent_str,
928 xml_escape(&folder.name)
929 )
930 .map_err(|e| IoError::FileError(e.to_string()))?;
931
932 if let Some(description) = &folder.description {
933 writeln!(
934 file,
935 "{} <description>{}</description>",
936 indent_str,
937 xml_escape(description)
938 )
939 .map_err(|e| IoError::FileError(e.to_string()))?;
940 }
941
942 for feature in &folder.features {
944 self.write_feature(file, feature, indent + 2)?;
945 }
946
947 for subfolder in &folder.subfolders {
949 self.write_folder(file, subfolder, indent + 2)?;
950 }
951
952 writeln!(file, "{indent_str} </Folder>").map_err(|e| IoError::FileError(e.to_string()))?;
953
954 Ok(())
955 }
956
957 fn writegeometry(&self, file: &mut File, geometry: &Geometry, indent: usize) -> Result<()> {
958 use std::io::Write;
959
960 let indent_str = " ".repeat(indent);
961
962 match geometry {
963 Geometry::Point { x, y } => {
964 writeln!(file, "{indent_str} <Point>")
965 .map_err(|e| IoError::FileError(e.to_string()))?;
966 writeln!(file, "{indent_str} <coordinates>{x},{y}</coordinates>")
967 .map_err(|e| IoError::FileError(e.to_string()))?;
968 writeln!(file, "{indent_str} </Point>")
969 .map_err(|e| IoError::FileError(e.to_string()))?;
970 }
971 Geometry::LineString { points } => {
972 writeln!(file, "{indent_str} <LineString>")
973 .map_err(|e| IoError::FileError(e.to_string()))?;
974 writeln!(file, "{indent_str} <coordinates>")
975 .map_err(|e| IoError::FileError(e.to_string()))?;
976 for (x, y) in points {
977 writeln!(file, "{indent_str} {x},{y}")
978 .map_err(|e| IoError::FileError(e.to_string()))?;
979 }
980 writeln!(file, "{indent_str} </coordinates>")
981 .map_err(|e| IoError::FileError(e.to_string()))?;
982 writeln!(file, "{indent_str} </LineString>")
983 .map_err(|e| IoError::FileError(e.to_string()))?;
984 }
985 Geometry::Polygon { exterior, holes: _ } => {
986 writeln!(file, "{indent_str} <Polygon>")
987 .map_err(|e| IoError::FileError(e.to_string()))?;
988 writeln!(file, "{indent_str} <outerBoundaryIs>")
989 .map_err(|e| IoError::FileError(e.to_string()))?;
990 writeln!(file, "{indent_str} <LinearRing>")
991 .map_err(|e| IoError::FileError(e.to_string()))?;
992 writeln!(file, "{indent_str} <coordinates>")
993 .map_err(|e| IoError::FileError(e.to_string()))?;
994 for (x, y) in exterior {
995 writeln!(file, "{indent_str} {x},{y}")
996 .map_err(|e| IoError::FileError(e.to_string()))?;
997 }
998 writeln!(file, "{indent_str} </coordinates>")
999 .map_err(|e| IoError::FileError(e.to_string()))?;
1000 writeln!(file, "{indent_str} </LinearRing>")
1001 .map_err(|e| IoError::FileError(e.to_string()))?;
1002 writeln!(file, "{indent_str} </outerBoundaryIs>")
1003 .map_err(|e| IoError::FileError(e.to_string()))?;
1004 writeln!(file, "{indent_str} </Polygon>")
1005 .map_err(|e| IoError::FileError(e.to_string()))?;
1006 }
1007 _ => {
1008 writeln!(file, "{indent_str} <!-- Unsupported geometry type -->")
1010 .map_err(|e| IoError::FileError(e.to_string()))?;
1011 }
1012 }
1013
1014 Ok(())
1015 }
1016
1017 pub fn read_kml<P: AsRef<Path>>(path: P) -> Result<Self> {
1019 let _content = std::fs::read_to_string(path.as_ref())
1021 .map_err(|e| IoError::FileError(format!("Failed to read KML file: {e}")))?;
1022
1023 Ok(Self {
1026 name: Some("Parsed KML Document".to_string()),
1027 description: Some("Document loaded from KML file".to_string()),
1028 features: Vec::new(),
1029 folders: Vec::new(),
1030 })
1031 }
1032}
1033
1034#[allow(dead_code)]
1036fn xml_escape(text: &str) -> String {
1037 text.replace('&', "&")
1038 .replace('<', "<")
1039 .replace('>', ">")
1040 .replace('"', """)
1041 .replace('\'', "'")
1042}
1043
1044pub mod geo_utils {
1046 use super::*;
1047
1048 pub fn haversine_distance(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
1050 const R: f64 = 6371000.0; let lat1_rad = lat1.to_radians();
1053 let lat2_rad = lat2.to_radians();
1054 let delta_lat = (lat2 - lat1).to_radians();
1055 let delta_lon = (lon2 - lon1).to_radians();
1056
1057 let a = (delta_lat / 2.0).sin().powi(2)
1058 + lat1_rad.cos() * lat2_rad.cos() * (delta_lon / 2.0).sin().powi(2);
1059 let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
1060
1061 R * c
1062 }
1063
1064 pub fn bearing(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
1066 let lat1_rad = lat1.to_radians();
1067 let lat2_rad = lat2.to_radians();
1068 let delta_lon = (lon2 - lon1).to_radians();
1069
1070 let y = delta_lon.sin() * lat2_rad.cos();
1071 let x = lat1_rad.cos() * lat2_rad.sin() - lat1_rad.sin() * lat2_rad.cos() * delta_lon.cos();
1072
1073 let bearing_rad = y.atan2(x);
1074 (bearing_rad.to_degrees() + 360.0) % 360.0
1075 }
1076
1077 pub fn transform_coordinates(
1079 x: f64,
1080 y: f64,
1081 from_crs: &CRS,
1082 to_crs: &CRS,
1083 ) -> Result<(f64, f64)> {
1084 match (from_crs.epsg_code, to_crs.epsg_code) {
1088 (Some(4326), Some(3857)) => {
1089 let x_merc = x * 20037508.34 / 180.0;
1091 let y_merc =
1092 (90.0 + y).to_radians().tan().ln() / std::f64::consts::PI * 20037508.34;
1093 Ok((x_merc, y_merc))
1094 }
1095 (Some(3857), Some(4326)) => {
1096 let x_wgs = x * 180.0 / 20037508.34;
1098 let y_wgs = (std::f64::consts::PI * y / 20037508.34).exp().atan() * 360.0
1099 / std::f64::consts::PI
1100 - 90.0;
1101 Ok((x_wgs, y_wgs))
1102 }
1103 _ => {
1104 Ok((x, y))
1106 }
1107 }
1108 }
1109
1110 pub fn point_inpolygon(point: &(f64, f64), polygon: &[(f64, f64)]) -> bool {
1112 let (x, y) = *point;
1113 let mut inside = false;
1114 let n = polygon.len();
1115
1116 if n < 3 {
1117 return false;
1118 }
1119
1120 let mut j = n - 1;
1121 for i in 0..n {
1122 let (xi, yi) = polygon[i];
1123 let (xj, yj) = polygon[j];
1124
1125 if ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
1126 inside = !inside;
1127 }
1128 j = i;
1129 }
1130
1131 inside
1132 }
1133
1134 pub fn polygon_area(polygon: &[(f64, f64)]) -> f64 {
1136 let n = polygon.len();
1137 if n < 3 {
1138 return 0.0;
1139 }
1140
1141 let mut area = 0.0;
1142 let mut j = n - 1;
1143
1144 for i in 0..n {
1145 let (xi, yi) = polygon[i];
1146 let (xj, yj) = polygon[j];
1147 area += (xj + xi) * (yj - yi);
1148 j = i;
1149 }
1150
1151 (area / 2.0).abs()
1152 }
1153
1154 pub fn polygon_centroid(polygon: &[(f64, f64)]) -> Option<(f64, f64)> {
1156 let n = polygon.len();
1157 if n < 3 {
1158 return None;
1159 }
1160
1161 let area = polygon_area(polygon);
1162 if area == 0.0 {
1163 return None;
1164 }
1165
1166 let mut cx = 0.0;
1167 let mut cy = 0.0;
1168 let mut j = n - 1;
1169
1170 for i in 0..n {
1171 let (xi, yi) = polygon[i];
1172 let (xj, yj) = polygon[j];
1173 let factor = xi * yj - xj * yi;
1174 cx += (xi + xj) * factor;
1175 cy += (yi + yj) * factor;
1176 j = i;
1177 }
1178
1179 cx /= 6.0 * area;
1180 cy /= 6.0 * area;
1181
1182 Some((cx, cy))
1183 }
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188 use super::*;
1189
1190 #[test]
1191 fn test_geo_transform() {
1192 let transform = GeoTransform::new(100.0, 50.0, 0.5, -0.5);
1193
1194 let (geo_x, geo_y) = transform.pixel_to_geo(10.0, 10.0);
1196 assert_eq!(geo_x, 105.0); assert_eq!(geo_y, 45.0); let (pixel_x, pixel_y) = transform.geo_to_pixel(105.0, 45.0);
1201 assert!((pixel_x - 10.0).abs() < 1e-10);
1202 assert!((pixel_y - 10.0).abs() < 1e-10);
1203 }
1204
1205 #[test]
1206 fn testgeometry_bbox() {
1207 let point = Geometry::Point { x: 10.0, y: 20.0 };
1208 assert_eq!(point.bbox(), Some((10.0, 20.0, 10.0, 20.0)));
1209
1210 let line = Geometry::LineString {
1211 points: vec![(0.0, 0.0), (10.0, 5.0), (5.0, 10.0)],
1212 };
1213 assert_eq!(line.bbox(), Some((0.0, 0.0, 10.0, 10.0)));
1214
1215 let empty_line = Geometry::LineString { points: vec![] };
1216 assert_eq!(empty_line.bbox(), None);
1217 }
1218
1219 #[test]
1220 fn test_crs() {
1221 let crs_epsg = CRS::from_epsg(4326);
1222 assert_eq!(crs_epsg.epsg_code, Some(4326));
1223
1224 let crs_wkt = CRS::from_wkt("GEOGCS[\\\"WGS 84\\\",...]".to_string());
1225 assert!(crs_wkt.wkt.is_some());
1226 }
1227}