1use std::collections::HashMap;
8
9use crate::error::GpkgError;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum FieldType {
18 Integer,
20 Real,
22 Text,
24 Blob,
26 Boolean,
28 Date,
30 DateTime,
32 Null,
34}
35
36impl FieldType {
37 pub fn from_sql_type(type_str: &str) -> Self {
42 match type_str.to_ascii_uppercase().trim() {
43 "INTEGER" | "INT" | "TINYINT" | "SMALLINT" | "MEDIUMINT" | "BIGINT"
44 | "UNSIGNED BIG INT" | "INT2" | "INT8" => Self::Integer,
45 "REAL" | "DOUBLE" | "DOUBLE PRECISION" | "FLOAT" | "NUMERIC" | "DECIMAL" => Self::Real,
46 "BLOB" => Self::Blob,
47 "BOOLEAN" | "BOOL" => Self::Boolean,
48 "DATE" => Self::Date,
49 "DATETIME" | "TIMESTAMP" => Self::DateTime,
50 "NULL" => Self::Null,
51 _ => Self::Text,
52 }
53 }
54
55 pub fn as_str(self) -> &'static str {
57 match self {
58 Self::Integer => "INTEGER",
59 Self::Real => "REAL",
60 Self::Text => "TEXT",
61 Self::Blob => "BLOB",
62 Self::Boolean => "BOOLEAN",
63 Self::Date => "DATE",
64 Self::DateTime => "DATETIME",
65 Self::Null => "NULL",
66 }
67 }
68}
69
70#[derive(Debug, Clone, PartialEq)]
76pub enum FieldValue {
77 Integer(i64),
79 Real(f64),
81 Text(String),
83 Blob(Vec<u8>),
85 Boolean(bool),
87 Null,
89}
90
91impl FieldValue {
92 pub fn as_integer(&self) -> Option<i64> {
94 match self {
95 Self::Integer(v) => Some(*v),
96 _ => None,
97 }
98 }
99
100 pub fn as_real(&self) -> Option<f64> {
102 match self {
103 Self::Real(v) => Some(*v),
104 _ => None,
105 }
106 }
107
108 pub fn as_text(&self) -> Option<&str> {
110 match self {
111 Self::Text(s) => Some(s.as_str()),
112 _ => None,
113 }
114 }
115
116 pub fn as_bool(&self) -> Option<bool> {
118 match self {
119 Self::Boolean(b) => Some(*b),
120 _ => None,
121 }
122 }
123
124 pub fn is_null(&self) -> bool {
126 matches!(self, Self::Null)
127 }
128
129 pub fn field_type(&self) -> FieldType {
131 match self {
132 Self::Integer(_) => FieldType::Integer,
133 Self::Real(_) => FieldType::Real,
134 Self::Text(_) => FieldType::Text,
135 Self::Blob(_) => FieldType::Blob,
136 Self::Boolean(_) => FieldType::Boolean,
137 Self::Null => FieldType::Null,
138 }
139 }
140
141 fn to_json(&self) -> String {
143 match self {
144 Self::Integer(v) => v.to_string(),
145 Self::Real(v) => {
146 if v.is_finite() {
147 format!("{v}")
148 } else {
149 "null".into()
150 }
151 }
152 Self::Text(s) => json_string_escape(s),
153 Self::Blob(b) => {
154 let hex: String = b.iter().map(|byte| format!("{byte:02x}")).collect();
156 json_string_escape(&format!("0x{hex}"))
157 }
158 Self::Boolean(b) => if *b { "true" } else { "false" }.into(),
159 Self::Null => "null".into(),
160 }
161 }
162}
163
164#[derive(Debug, Clone, PartialEq)]
170pub struct FieldDefinition {
171 pub name: String,
173 pub field_type: FieldType,
175 pub not_null: bool,
177 pub primary_key: bool,
179 pub default_value: Option<String>,
181}
182
183#[derive(Debug, Clone, PartialEq)]
192pub enum GpkgGeometry {
193 Point {
195 x: f64,
197 y: f64,
199 },
200 LineString {
202 coords: Vec<(f64, f64)>,
204 },
205 Polygon {
207 rings: Vec<Vec<(f64, f64)>>,
209 },
210 MultiPoint {
212 points: Vec<(f64, f64)>,
214 },
215 MultiLineString {
217 lines: Vec<Vec<(f64, f64)>>,
219 },
220 MultiPolygon {
222 polygons: Vec<Vec<Vec<(f64, f64)>>>,
224 },
225 GeometryCollection(Vec<GpkgGeometry>),
227 Empty,
229}
230
231impl GpkgGeometry {
232 pub fn geometry_type(&self) -> &'static str {
234 match self {
235 Self::Point { .. } => "Point",
236 Self::LineString { .. } => "LineString",
237 Self::Polygon { .. } => "Polygon",
238 Self::MultiPoint { .. } => "MultiPoint",
239 Self::MultiLineString { .. } => "MultiLineString",
240 Self::MultiPolygon { .. } => "MultiPolygon",
241 Self::GeometryCollection(_) => "GeometryCollection",
242 Self::Empty => "Empty",
243 }
244 }
245
246 pub fn point_count(&self) -> usize {
248 match self {
249 Self::Point { .. } => 1,
250 Self::LineString { coords } => coords.len(),
251 Self::Polygon { rings } => rings.iter().map(|r| r.len()).sum(),
252 Self::MultiPoint { points } => points.len(),
253 Self::MultiLineString { lines } => lines.iter().map(|l| l.len()).sum(),
254 Self::MultiPolygon { polygons } => polygons
255 .iter()
256 .flat_map(|poly| poly.iter())
257 .map(|ring| ring.len())
258 .sum(),
259 Self::GeometryCollection(geoms) => geoms.iter().map(|g| g.point_count()).sum(),
260 Self::Empty => 0,
261 }
262 }
263
264 pub fn bbox(&self) -> Option<(f64, f64, f64, f64)> {
267 let coords: Vec<(f64, f64)> = self.collect_coords();
268 if coords.is_empty() {
269 return None;
270 }
271 let mut min_x = f64::INFINITY;
272 let mut min_y = f64::INFINITY;
273 let mut max_x = f64::NEG_INFINITY;
274 let mut max_y = f64::NEG_INFINITY;
275 for (x, y) in &coords {
276 if *x < min_x {
277 min_x = *x;
278 }
279 if *y < min_y {
280 min_y = *y;
281 }
282 if *x > max_x {
283 max_x = *x;
284 }
285 if *y > max_y {
286 max_y = *y;
287 }
288 }
289 if min_x.is_finite() {
290 Some((min_x, min_y, max_x, max_y))
291 } else {
292 None
293 }
294 }
295
296 fn collect_coords(&self) -> Vec<(f64, f64)> {
298 match self {
299 Self::Point { x, y } => vec![(*x, *y)],
300 Self::LineString { coords } => coords.clone(),
301 Self::Polygon { rings } => rings.iter().flatten().copied().collect(),
302 Self::MultiPoint { points } => points.clone(),
303 Self::MultiLineString { lines } => lines.iter().flatten().copied().collect(),
304 Self::MultiPolygon { polygons } => polygons
305 .iter()
306 .flat_map(|poly| poly.iter().flatten())
307 .copied()
308 .collect(),
309 Self::GeometryCollection(geoms) => {
310 geoms.iter().flat_map(|g| g.collect_coords()).collect()
311 }
312 Self::Empty => vec![],
313 }
314 }
315
316 pub(crate) fn to_geojson_geometry(&self) -> String {
318 match self {
319 Self::Point { x, y } => {
320 format!(r#"{{"type":"Point","coordinates":[{x},{y}]}}"#)
321 }
322 Self::LineString { coords } => {
323 let pts = coords_to_json_array(coords);
324 format!(r#"{{"type":"LineString","coordinates":{pts}}}"#)
325 }
326 Self::Polygon { rings } => {
327 let rings_json = rings
328 .iter()
329 .map(|r| coords_to_json_array(r))
330 .collect::<Vec<_>>()
331 .join(",");
332 format!(r#"{{"type":"Polygon","coordinates":[{rings_json}]}}"#)
333 }
334 Self::MultiPoint { points } => {
335 let pts = coords_to_json_array(points);
336 format!(r#"{{"type":"MultiPoint","coordinates":{pts}}}"#)
337 }
338 Self::MultiLineString { lines } => {
339 let lines_json = lines
340 .iter()
341 .map(|l| coords_to_json_array(l))
342 .collect::<Vec<_>>()
343 .join(",");
344 format!(r#"{{"type":"MultiLineString","coordinates":[{lines_json}]}}"#)
345 }
346 Self::MultiPolygon { polygons } => {
347 let polys_json = polygons
348 .iter()
349 .map(|poly| {
350 let rings_json = poly
351 .iter()
352 .map(|r| coords_to_json_array(r))
353 .collect::<Vec<_>>()
354 .join(",");
355 format!("[{rings_json}]")
356 })
357 .collect::<Vec<_>>()
358 .join(",");
359 format!(r#"{{"type":"MultiPolygon","coordinates":[{polys_json}]}}"#)
360 }
361 Self::GeometryCollection(geoms) => {
362 let geom_json = geoms
363 .iter()
364 .map(|g| g.to_geojson_geometry())
365 .collect::<Vec<_>>()
366 .join(",");
367 format!(r#"{{"type":"GeometryCollection","geometries":[{geom_json}]}}"#)
368 }
369 Self::Empty => "null".into(),
370 }
371 }
372}
373
374pub struct GpkgBinaryParser;
393
394impl GpkgBinaryParser {
395 pub fn parse(data: &[u8]) -> Result<GpkgGeometry, GpkgError> {
402 if data.len() < 8 {
403 return Err(GpkgError::InsufficientData {
404 needed: 8,
405 available: data.len(),
406 });
407 }
408 if data[0] != 0x47 || data[1] != 0x50 {
409 return Err(GpkgError::InvalidGeometryMagic);
410 }
411
412 let flags = data[3];
413 let is_little_endian = (flags >> 5) & 1 == 1;
414 let envelope_indicator = flags & 0b0000_0111;
415 let empty_flag = (flags >> 3) & 1 == 1;
416
417 let _srs_id: i32 = if is_little_endian {
419 i32::from_le_bytes([data[4], data[5], data[6], data[7]])
420 } else {
421 i32::from_be_bytes([data[4], data[5], data[6], data[7]])
422 };
423
424 let envelope_bytes: usize = match envelope_indicator {
426 0 => 0,
427 1 => 32,
428 2 | 3 => 48,
429 4 => 64,
430 _ => {
431 return Err(GpkgError::WkbParseError(format!(
432 "Unknown envelope indicator {envelope_indicator}"
433 )));
434 }
435 };
436
437 let header_size = 8 + envelope_bytes;
438 if data.len() < header_size {
439 return Err(GpkgError::InsufficientData {
440 needed: header_size,
441 available: data.len(),
442 });
443 }
444
445 if empty_flag {
446 return Ok(GpkgGeometry::Empty);
447 }
448
449 let wkb = &data[header_size..];
450 if wkb.is_empty() {
451 return Ok(GpkgGeometry::Empty);
452 }
453 Self::parse_wkb(wkb)
454 }
455
456 pub fn parse_wkb(data: &[u8]) -> Result<GpkgGeometry, GpkgError> {
461 let (geom, _consumed) = parse_wkb_inner(data, 0)?;
462 Ok(geom)
463 }
464
465 pub fn to_wkb(geom: &GpkgGeometry) -> Vec<u8> {
467 let mut buf = Vec::new();
468 write_wkb(geom, &mut buf);
469 buf
470 }
471
472 pub fn to_gpb(geom: &GpkgGeometry, srs_id: i32) -> Vec<u8> {
476 let mut buf = Vec::new();
477 buf.push(0x47); buf.push(0x50); buf.push(0);
482 let is_empty = matches!(geom, GpkgGeometry::Empty);
484 let empty_bit: u8 = if is_empty { 1 << 3 } else { 0 };
485 let flags: u8 = empty_bit | (1 << 5); buf.push(flags);
487 buf.extend_from_slice(&srs_id.to_le_bytes());
489 if !is_empty {
491 write_wkb(geom, &mut buf);
492 }
493 buf
494 }
495}
496
497const WKB_POINT: u32 = 1;
503const WKB_LINESTRING: u32 = 2;
504const WKB_POLYGON: u32 = 3;
505const WKB_MULTIPOINT: u32 = 4;
506const WKB_MULTILINESTRING: u32 = 5;
507const WKB_MULTIPOLYGON: u32 = 6;
508const WKB_GEOMETRYCOLLECTION: u32 = 7;
509
510fn parse_wkb_inner(data: &[u8], offset: usize) -> Result<(GpkgGeometry, usize), GpkgError> {
513 if data.len() < offset + 5 {
514 return Err(GpkgError::InsufficientData {
515 needed: offset + 5,
516 available: data.len(),
517 });
518 }
519 let byte_order = data[offset];
520 let le = byte_order == 1;
521 let mut pos = offset + 1;
522
523 let wkb_type = read_u32(data, pos, le)?;
524 pos += 4;
525
526 match wkb_type {
527 WKB_POINT => {
528 let (x, y, new_pos) = read_point_coords(data, pos, le)?;
529 Ok((GpkgGeometry::Point { x, y }, new_pos))
530 }
531 WKB_LINESTRING => {
532 let (coords, new_pos) = read_coord_sequence(data, pos, le)?;
533 Ok((GpkgGeometry::LineString { coords }, new_pos))
534 }
535 WKB_POLYGON => {
536 let (rings, new_pos) = read_rings(data, pos, le)?;
537 Ok((GpkgGeometry::Polygon { rings }, new_pos))
538 }
539 WKB_MULTIPOINT => {
540 let (n, mut pos2) = read_u32_pos(data, pos, le)?;
541 let mut points = Vec::with_capacity(n as usize);
542 for _ in 0..n {
543 let (sub, new_pos) = parse_wkb_inner(data, pos2)?;
545 pos2 = new_pos;
546 match sub {
547 GpkgGeometry::Point { x, y } => points.push((x, y)),
548 other => {
549 return Err(GpkgError::WkbParseError(format!(
550 "Expected Point in MultiPoint, got {}",
551 other.geometry_type()
552 )));
553 }
554 }
555 }
556 Ok((GpkgGeometry::MultiPoint { points }, pos2))
557 }
558 WKB_MULTILINESTRING => {
559 let (n, mut pos2) = read_u32_pos(data, pos, le)?;
560 let mut lines = Vec::with_capacity(n as usize);
561 for _ in 0..n {
562 let (sub, new_pos) = parse_wkb_inner(data, pos2)?;
563 pos2 = new_pos;
564 match sub {
565 GpkgGeometry::LineString { coords } => lines.push(coords),
566 other => {
567 return Err(GpkgError::WkbParseError(format!(
568 "Expected LineString in MultiLineString, got {}",
569 other.geometry_type()
570 )));
571 }
572 }
573 }
574 Ok((GpkgGeometry::MultiLineString { lines }, pos2))
575 }
576 WKB_MULTIPOLYGON => {
577 let (n, mut pos2) = read_u32_pos(data, pos, le)?;
578 let mut polygons = Vec::with_capacity(n as usize);
579 for _ in 0..n {
580 let (sub, new_pos) = parse_wkb_inner(data, pos2)?;
581 pos2 = new_pos;
582 match sub {
583 GpkgGeometry::Polygon { rings } => polygons.push(rings),
584 other => {
585 return Err(GpkgError::WkbParseError(format!(
586 "Expected Polygon in MultiPolygon, got {}",
587 other.geometry_type()
588 )));
589 }
590 }
591 }
592 Ok((GpkgGeometry::MultiPolygon { polygons }, pos2))
593 }
594 WKB_GEOMETRYCOLLECTION => {
595 let (n, mut pos2) = read_u32_pos(data, pos, le)?;
596 let mut geoms = Vec::with_capacity(n as usize);
597 for _ in 0..n {
598 let (sub, new_pos) = parse_wkb_inner(data, pos2)?;
599 pos2 = new_pos;
600 geoms.push(sub);
601 }
602 Ok((GpkgGeometry::GeometryCollection(geoms), pos2))
603 }
604 other => Err(GpkgError::UnknownWkbType(other)),
605 }
606}
607
608fn read_u32_pos(data: &[u8], pos: usize, le: bool) -> Result<(u32, usize), GpkgError> {
610 Ok((read_u32(data, pos, le)?, pos + 4))
611}
612
613fn read_u32(data: &[u8], pos: usize, le: bool) -> Result<u32, GpkgError> {
614 if data.len() < pos + 4 {
615 return Err(GpkgError::InsufficientData {
616 needed: pos + 4,
617 available: data.len(),
618 });
619 }
620 let bytes = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]];
621 Ok(if le {
622 u32::from_le_bytes(bytes)
623 } else {
624 u32::from_be_bytes(bytes)
625 })
626}
627
628fn read_f64(data: &[u8], pos: usize, le: bool) -> Result<f64, GpkgError> {
629 if data.len() < pos + 8 {
630 return Err(GpkgError::InsufficientData {
631 needed: pos + 8,
632 available: data.len(),
633 });
634 }
635 let bytes = [
636 data[pos],
637 data[pos + 1],
638 data[pos + 2],
639 data[pos + 3],
640 data[pos + 4],
641 data[pos + 5],
642 data[pos + 6],
643 data[pos + 7],
644 ];
645 Ok(if le {
646 f64::from_le_bytes(bytes)
647 } else {
648 f64::from_be_bytes(bytes)
649 })
650}
651
652fn read_point_coords(data: &[u8], pos: usize, le: bool) -> Result<(f64, f64, usize), GpkgError> {
653 let x = read_f64(data, pos, le)?;
654 let y = read_f64(data, pos + 8, le)?;
655 Ok((x, y, pos + 16))
656}
657
658fn read_coord_sequence(
659 data: &[u8],
660 pos: usize,
661 le: bool,
662) -> Result<(Vec<(f64, f64)>, usize), GpkgError> {
663 let (n, mut cur) = read_u32_pos(data, pos, le)?;
664 let mut coords = Vec::with_capacity(n as usize);
665 for _ in 0..n {
666 let (x, y, new_cur) = read_point_coords(data, cur, le)?;
667 cur = new_cur;
668 coords.push((x, y));
669 }
670 Ok((coords, cur))
671}
672
673type RingsResult = Result<(Vec<Vec<(f64, f64)>>, usize), GpkgError>;
674
675fn read_rings(data: &[u8], pos: usize, le: bool) -> RingsResult {
676 let (n_rings, mut cur) = read_u32_pos(data, pos, le)?;
677 let mut rings = Vec::with_capacity(n_rings as usize);
678 for _ in 0..n_rings {
679 let (coords, new_cur) = read_coord_sequence(data, cur, le)?;
680 cur = new_cur;
681 rings.push(coords);
682 }
683 Ok((rings, cur))
684}
685
686fn write_wkb(geom: &GpkgGeometry, buf: &mut Vec<u8>) {
688 buf.push(1); match geom {
690 GpkgGeometry::Point { x, y } => {
691 buf.extend_from_slice(&WKB_POINT.to_le_bytes());
692 buf.extend_from_slice(&x.to_le_bytes());
693 buf.extend_from_slice(&y.to_le_bytes());
694 }
695 GpkgGeometry::LineString { coords } => {
696 buf.extend_from_slice(&WKB_LINESTRING.to_le_bytes());
697 buf.extend_from_slice(&(coords.len() as u32).to_le_bytes());
698 for (x, y) in coords {
699 buf.extend_from_slice(&x.to_le_bytes());
700 buf.extend_from_slice(&y.to_le_bytes());
701 }
702 }
703 GpkgGeometry::Polygon { rings } => {
704 buf.extend_from_slice(&WKB_POLYGON.to_le_bytes());
705 buf.extend_from_slice(&(rings.len() as u32).to_le_bytes());
706 for ring in rings {
707 buf.extend_from_slice(&(ring.len() as u32).to_le_bytes());
708 for (x, y) in ring {
709 buf.extend_from_slice(&x.to_le_bytes());
710 buf.extend_from_slice(&y.to_le_bytes());
711 }
712 }
713 }
714 GpkgGeometry::MultiPoint { points } => {
715 buf.extend_from_slice(&WKB_MULTIPOINT.to_le_bytes());
716 buf.extend_from_slice(&(points.len() as u32).to_le_bytes());
717 for (x, y) in points {
718 write_wkb(&GpkgGeometry::Point { x: *x, y: *y }, buf);
719 }
720 }
721 GpkgGeometry::MultiLineString { lines } => {
722 buf.extend_from_slice(&WKB_MULTILINESTRING.to_le_bytes());
723 buf.extend_from_slice(&(lines.len() as u32).to_le_bytes());
724 for line in lines {
725 write_wkb(
726 &GpkgGeometry::LineString {
727 coords: line.clone(),
728 },
729 buf,
730 );
731 }
732 }
733 GpkgGeometry::MultiPolygon { polygons } => {
734 buf.extend_from_slice(&WKB_MULTIPOLYGON.to_le_bytes());
735 buf.extend_from_slice(&(polygons.len() as u32).to_le_bytes());
736 for poly in polygons {
737 write_wkb(
738 &GpkgGeometry::Polygon {
739 rings: poly.clone(),
740 },
741 buf,
742 );
743 }
744 }
745 GpkgGeometry::GeometryCollection(geoms) => {
746 buf.extend_from_slice(&WKB_GEOMETRYCOLLECTION.to_le_bytes());
747 buf.extend_from_slice(&(geoms.len() as u32).to_le_bytes());
748 for g in geoms {
749 write_wkb(g, buf);
750 }
751 }
752 GpkgGeometry::Empty => {
753 buf.extend_from_slice(&WKB_GEOMETRYCOLLECTION.to_le_bytes());
755 buf.extend_from_slice(&0u32.to_le_bytes());
756 }
757 }
758}
759
760#[derive(Debug, Clone)]
766pub struct FeatureRow {
767 pub fid: i64,
769 pub geometry: Option<GpkgGeometry>,
771 pub fields: HashMap<String, FieldValue>,
773}
774
775impl FeatureRow {
776 pub fn get_field(&self, name: &str) -> Option<&FieldValue> {
778 self.fields.get(name)
779 }
780
781 pub fn get_integer(&self, name: &str) -> Option<i64> {
783 self.fields.get(name)?.as_integer()
784 }
785
786 pub fn get_real(&self, name: &str) -> Option<f64> {
788 self.fields.get(name)?.as_real()
789 }
790
791 pub fn get_text(&self, name: &str) -> Option<&str> {
793 self.fields.get(name)?.as_text()
794 }
795}
796
797#[derive(Debug, Clone)]
805pub struct FeatureTable {
806 pub name: String,
808 pub geometry_column: String,
810 pub srs_id: Option<i32>,
812 pub schema: Vec<FieldDefinition>,
814 pub features: Vec<FeatureRow>,
816}
817
818impl FeatureTable {
819 pub fn new(name: impl Into<String>, geometry_column: impl Into<String>) -> Self {
821 Self {
822 name: name.into(),
823 geometry_column: geometry_column.into(),
824 srs_id: None,
825 schema: Vec::new(),
826 features: Vec::new(),
827 }
828 }
829
830 pub fn feature_count(&self) -> usize {
832 self.features.len()
833 }
834
835 pub fn add_feature(&mut self, row: FeatureRow) {
837 self.features.push(row);
838 }
839
840 pub fn get_feature(&self, fid: i64) -> Option<&FeatureRow> {
842 self.features.iter().find(|r| r.fid == fid)
843 }
844
845 pub fn bbox(&self) -> Option<(f64, f64, f64, f64)> {
848 let mut min_x = f64::INFINITY;
849 let mut min_y = f64::INFINITY;
850 let mut max_x = f64::NEG_INFINITY;
851 let mut max_y = f64::NEG_INFINITY;
852 let mut found = false;
853
854 for row in &self.features {
855 if let Some(geom) = &row.geometry {
856 if let Some((gx0, gy0, gx1, gy1)) = geom.bbox() {
857 found = true;
858 if gx0 < min_x {
859 min_x = gx0;
860 }
861 if gy0 < min_y {
862 min_y = gy0;
863 }
864 if gx1 > max_x {
865 max_x = gx1;
866 }
867 if gy1 > max_y {
868 max_y = gy1;
869 }
870 }
871 }
872 }
873
874 if found {
875 Some((min_x, min_y, max_x, max_y))
876 } else {
877 None
878 }
879 }
880
881 pub fn features_in_bbox(
886 &self,
887 min_x: f64,
888 min_y: f64,
889 max_x: f64,
890 max_y: f64,
891 ) -> Vec<&FeatureRow> {
892 self.features
893 .iter()
894 .filter(|row| {
895 if let Some(geom) = &row.geometry {
896 if let Some((gx0, gy0, gx1, gy1)) = geom.bbox() {
897 return gx0 <= max_x && gx1 >= min_x && gy0 <= max_y && gy1 >= min_y;
899 }
900 }
901 false
902 })
903 .collect()
904 }
905
906 pub fn distinct_values(&self, field_name: &str) -> Vec<FieldValue> {
910 let mut seen: Vec<FieldValue> = Vec::new();
911 for row in &self.features {
912 if let Some(val) = row.fields.get(field_name) {
913 if !val.is_null() && !seen.contains(val) {
914 seen.push(val.clone());
915 }
916 }
917 }
918 seen
919 }
920
921 pub fn to_geojson(&self) -> String {
925 let features_json: String = self
926 .features
927 .iter()
928 .map(|row| {
929 let geom_json = match &row.geometry {
930 Some(g) => g.to_geojson_geometry(),
931 None => "null".into(),
932 };
933 let props_json = build_properties_json(&row.fields);
934 format!(r#"{{"type":"Feature","geometry":{geom_json},"properties":{props_json}}}"#)
935 })
936 .collect::<Vec<_>>()
937 .join(",");
938
939 format!(r#"{{"type":"FeatureCollection","features":[{features_json}]}}"#)
940 }
941}
942
943#[derive(Debug, Clone, PartialEq)]
949pub struct SrsInfo {
950 pub srs_name: String,
952 pub srs_id: i32,
954 pub organization: String,
956 pub org_coord_sys_id: i32,
958 pub definition: String,
960 pub description: Option<String>,
962}
963
964impl SrsInfo {
965 pub fn wgs84() -> Self {
967 Self {
968 srs_name: "WGS 84".into(),
969 srs_id: 4326,
970 organization: "EPSG".into(),
971 org_coord_sys_id: 4326,
972 definition: concat!(
973 "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",",
974 "SPHEROID[\"WGS 84\",6378137,298.257223563]],",
975 "PRIMEM[\"Greenwich\",0],",
976 "UNIT[\"degree\",0.0174532925199433]]"
977 )
978 .into(),
979 description: Some("World Geodetic System 1984".into()),
980 }
981 }
982
983 pub fn web_mercator() -> Self {
985 Self {
986 srs_name: "WGS 84 / Pseudo-Mercator".into(),
987 srs_id: 3857,
988 organization: "EPSG".into(),
989 org_coord_sys_id: 3857,
990 definition: concat!(
991 "PROJCS[\"WGS 84 / Pseudo-Mercator\",",
992 "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",",
993 "SPHEROID[\"WGS 84\",6378137,298.257223563]],",
994 "PRIMEM[\"Greenwich\",0],",
995 "UNIT[\"degree\",0.0174532925199433]],",
996 "PROJECTION[\"Mercator_1SP\"],",
997 "PARAMETER[\"central_meridian\",0],",
998 "PARAMETER[\"scale_factor\",1],",
999 "PARAMETER[\"false_easting\",0],",
1000 "PARAMETER[\"false_northing\",0],",
1001 "UNIT[\"metre\",1]]"
1002 )
1003 .into(),
1004 description: Some("Web Mercator projection used by many web mapping services".into()),
1005 }
1006 }
1007
1008 pub fn is_geographic(&self) -> bool {
1012 (4000..5000).contains(&self.srs_id)
1013 }
1014
1015 pub fn epsg_code(&self) -> Option<i32> {
1017 if self.organization.eq_ignore_ascii_case("EPSG") {
1018 Some(self.org_coord_sys_id)
1019 } else {
1020 None
1021 }
1022 }
1023}
1024
1025fn json_string_escape(s: &str) -> String {
1031 let mut out = String::with_capacity(s.len() + 2);
1032 out.push('"');
1033 for ch in s.chars() {
1034 match ch {
1035 '"' => out.push_str("\\\""),
1036 '\\' => out.push_str("\\\\"),
1037 '\n' => out.push_str("\\n"),
1038 '\r' => out.push_str("\\r"),
1039 '\t' => out.push_str("\\t"),
1040 c if (c as u32) < 0x20 => {
1041 out.push_str(&format!("\\u{:04x}", c as u32));
1042 }
1043 c => out.push(c),
1044 }
1045 }
1046 out.push('"');
1047 out
1048}
1049
1050fn coords_to_json_array(coords: &[(f64, f64)]) -> String {
1052 let inner: String = coords
1053 .iter()
1054 .map(|(x, y)| format!("[{x},{y}]"))
1055 .collect::<Vec<_>>()
1056 .join(",");
1057 format!("[{inner}]")
1058}
1059
1060fn build_properties_json(fields: &HashMap<String, FieldValue>) -> String {
1062 if fields.is_empty() {
1063 return "{}".into();
1064 }
1065 let mut pairs: Vec<(&String, &FieldValue)> = fields.iter().collect();
1067 pairs.sort_by_key(|(k, _)| k.as_str());
1068 let members: String = pairs
1069 .iter()
1070 .map(|(k, v)| format!("{}:{}", json_string_escape(k), v.to_json()))
1071 .collect::<Vec<_>>()
1072 .join(",");
1073 format!("{{{members}}}")
1074}