1use std::collections::HashMap;
4
5use super::types::{FieldDefinition, FieldValue, GpkgGeometry};
6
7#[derive(Debug, Clone)]
13pub struct FeatureRow {
14 pub fid: i64,
16 pub geometry: Option<GpkgGeometry>,
18 pub fields: HashMap<String, FieldValue>,
20}
21
22impl FeatureRow {
23 pub fn get_field(&self, name: &str) -> Option<&FieldValue> {
25 self.fields.get(name)
26 }
27
28 pub fn get_integer(&self, name: &str) -> Option<i64> {
30 self.fields.get(name)?.as_integer()
31 }
32
33 pub fn get_real(&self, name: &str) -> Option<f64> {
35 self.fields.get(name)?.as_real()
36 }
37
38 pub fn get_text(&self, name: &str) -> Option<&str> {
40 self.fields.get(name)?.as_text()
41 }
42}
43
44#[derive(Debug, Clone)]
52pub struct FeatureTable {
53 pub name: String,
55 pub geometry_column: String,
57 pub srs_id: Option<i32>,
59 pub schema: Vec<FieldDefinition>,
61 pub features: Vec<FeatureRow>,
63}
64
65impl FeatureTable {
66 pub fn new(name: impl Into<String>, geometry_column: impl Into<String>) -> Self {
68 Self {
69 name: name.into(),
70 geometry_column: geometry_column.into(),
71 srs_id: None,
72 schema: Vec::new(),
73 features: Vec::new(),
74 }
75 }
76
77 pub fn feature_count(&self) -> usize {
79 self.features.len()
80 }
81
82 pub fn add_feature(&mut self, row: FeatureRow) {
84 self.features.push(row);
85 }
86
87 pub fn get_feature(&self, fid: i64) -> Option<&FeatureRow> {
89 self.features.iter().find(|r| r.fid == fid)
90 }
91
92 pub fn bbox(&self) -> Option<(f64, f64, f64, f64)> {
95 let mut min_x = f64::INFINITY;
96 let mut min_y = f64::INFINITY;
97 let mut max_x = f64::NEG_INFINITY;
98 let mut max_y = f64::NEG_INFINITY;
99 let mut found = false;
100
101 for row in &self.features {
102 if let Some(geom) = &row.geometry {
103 if let Some((gx0, gy0, gx1, gy1)) = geom.bbox() {
104 found = true;
105 if gx0 < min_x {
106 min_x = gx0;
107 }
108 if gy0 < min_y {
109 min_y = gy0;
110 }
111 if gx1 > max_x {
112 max_x = gx1;
113 }
114 if gy1 > max_y {
115 max_y = gy1;
116 }
117 }
118 }
119 }
120
121 if found {
122 Some((min_x, min_y, max_x, max_y))
123 } else {
124 None
125 }
126 }
127
128 pub fn features_in_bbox(
133 &self,
134 min_x: f64,
135 min_y: f64,
136 max_x: f64,
137 max_y: f64,
138 ) -> Vec<&FeatureRow> {
139 self.features
140 .iter()
141 .filter(|row| {
142 if let Some(geom) = &row.geometry {
143 if let Some((gx0, gy0, gx1, gy1)) = geom.bbox() {
144 return gx0 <= max_x && gx1 >= min_x && gy0 <= max_y && gy1 >= min_y;
146 }
147 }
148 false
149 })
150 .collect()
151 }
152
153 pub fn distinct_values(&self, field_name: &str) -> Vec<FieldValue> {
157 let mut seen: Vec<FieldValue> = Vec::new();
158 for row in &self.features {
159 if let Some(val) = row.fields.get(field_name) {
160 if !val.is_null() && !seen.contains(val) {
161 seen.push(val.clone());
162 }
163 }
164 }
165 seen
166 }
167
168 pub fn to_geojson(&self) -> String {
172 let features_json: String = self
173 .features
174 .iter()
175 .map(|row| {
176 let geom_json = match &row.geometry {
177 Some(g) => g.to_geojson_geometry(),
178 None => "null".into(),
179 };
180 let props_json = build_properties_json(&row.fields);
181 format!(r#"{{"type":"Feature","geometry":{geom_json},"properties":{props_json}}}"#)
182 })
183 .collect::<Vec<_>>()
184 .join(",");
185
186 format!(r#"{{"type":"FeatureCollection","features":[{features_json}]}}"#)
187 }
188}
189
190#[derive(Debug, Clone, PartialEq)]
196pub struct SrsInfo {
197 pub srs_name: String,
199 pub srs_id: i32,
201 pub organization: String,
203 pub org_coord_sys_id: i32,
205 pub definition: String,
207 pub description: Option<String>,
209}
210
211impl SrsInfo {
212 pub fn wgs84() -> Self {
214 Self {
215 srs_name: "WGS 84".into(),
216 srs_id: 4326,
217 organization: "EPSG".into(),
218 org_coord_sys_id: 4326,
219 definition: concat!(
220 "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",",
221 "SPHEROID[\"WGS 84\",6378137,298.257223563]],",
222 "PRIMEM[\"Greenwich\",0],",
223 "UNIT[\"degree\",0.0174532925199433]]"
224 )
225 .into(),
226 description: Some("World Geodetic System 1984".into()),
227 }
228 }
229
230 pub fn web_mercator() -> Self {
232 Self {
233 srs_name: "WGS 84 / Pseudo-Mercator".into(),
234 srs_id: 3857,
235 organization: "EPSG".into(),
236 org_coord_sys_id: 3857,
237 definition: concat!(
238 "PROJCS[\"WGS 84 / Pseudo-Mercator\",",
239 "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",",
240 "SPHEROID[\"WGS 84\",6378137,298.257223563]],",
241 "PRIMEM[\"Greenwich\",0],",
242 "UNIT[\"degree\",0.0174532925199433]],",
243 "PROJECTION[\"Mercator_1SP\"],",
244 "PARAMETER[\"central_meridian\",0],",
245 "PARAMETER[\"scale_factor\",1],",
246 "PARAMETER[\"false_easting\",0],",
247 "PARAMETER[\"false_northing\",0],",
248 "UNIT[\"metre\",1]]"
249 )
250 .into(),
251 description: Some("Web Mercator projection used by many web mapping services".into()),
252 }
253 }
254
255 pub fn is_geographic(&self) -> bool {
259 (4000..5000).contains(&self.srs_id)
260 }
261
262 pub fn epsg_code(&self) -> Option<i32> {
264 if self.organization.eq_ignore_ascii_case("EPSG") {
265 Some(self.org_coord_sys_id)
266 } else {
267 None
268 }
269 }
270}
271
272pub(super) fn build_properties_json(fields: &HashMap<String, FieldValue>) -> String {
278 if fields.is_empty() {
279 return "{}".into();
280 }
281 let mut pairs: Vec<(&String, &FieldValue)> = fields.iter().collect();
283 pairs.sort_by_key(|(k, _)| k.as_str());
284 let members: String = pairs
285 .iter()
286 .map(|(k, v)| format!("{}:{}", super::types::json_string_escape(k), v.to_json()))
287 .collect::<Vec<_>>()
288 .join(",");
289 format!("{{{members}}}")
290}