use std::collections::HashMap;
use super::types::{FieldDefinition, FieldValue, GpkgGeometry};
#[derive(Debug, Clone)]
pub struct FeatureRow {
pub fid: i64,
pub geometry: Option<GpkgGeometry>,
pub fields: HashMap<String, FieldValue>,
}
impl FeatureRow {
pub fn get_field(&self, name: &str) -> Option<&FieldValue> {
self.fields.get(name)
}
pub fn get_integer(&self, name: &str) -> Option<i64> {
self.fields.get(name)?.as_integer()
}
pub fn get_real(&self, name: &str) -> Option<f64> {
self.fields.get(name)?.as_real()
}
pub fn get_text(&self, name: &str) -> Option<&str> {
self.fields.get(name)?.as_text()
}
}
#[derive(Debug, Clone)]
pub struct FeatureTable {
pub name: String,
pub geometry_column: String,
pub srs_id: Option<i32>,
pub schema: Vec<FieldDefinition>,
pub features: Vec<FeatureRow>,
}
impl FeatureTable {
pub fn new(name: impl Into<String>, geometry_column: impl Into<String>) -> Self {
Self {
name: name.into(),
geometry_column: geometry_column.into(),
srs_id: None,
schema: Vec::new(),
features: Vec::new(),
}
}
pub fn feature_count(&self) -> usize {
self.features.len()
}
pub fn add_feature(&mut self, row: FeatureRow) {
self.features.push(row);
}
pub fn get_feature(&self, fid: i64) -> Option<&FeatureRow> {
self.features.iter().find(|r| r.fid == fid)
}
pub fn bbox(&self) -> Option<(f64, f64, f64, f64)> {
let mut min_x = f64::INFINITY;
let mut min_y = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut max_y = f64::NEG_INFINITY;
let mut found = false;
for row in &self.features {
if let Some(geom) = &row.geometry {
if let Some((gx0, gy0, gx1, gy1)) = geom.bbox() {
found = true;
if gx0 < min_x {
min_x = gx0;
}
if gy0 < min_y {
min_y = gy0;
}
if gx1 > max_x {
max_x = gx1;
}
if gy1 > max_y {
max_y = gy1;
}
}
}
}
if found {
Some((min_x, min_y, max_x, max_y))
} else {
None
}
}
pub fn features_in_bbox(
&self,
min_x: f64,
min_y: f64,
max_x: f64,
max_y: f64,
) -> Vec<&FeatureRow> {
self.features
.iter()
.filter(|row| {
if let Some(geom) = &row.geometry {
if let Some((gx0, gy0, gx1, gy1)) = geom.bbox() {
return gx0 <= max_x && gx1 >= min_x && gy0 <= max_y && gy1 >= min_y;
}
}
false
})
.collect()
}
pub fn distinct_values(&self, field_name: &str) -> Vec<FieldValue> {
let mut seen: Vec<FieldValue> = Vec::new();
for row in &self.features {
if let Some(val) = row.fields.get(field_name) {
if !val.is_null() && !seen.contains(val) {
seen.push(val.clone());
}
}
}
seen
}
pub fn to_geojson(&self) -> String {
let features_json: String = self
.features
.iter()
.map(|row| {
let geom_json = match &row.geometry {
Some(g) => g.to_geojson_geometry(),
None => "null".into(),
};
let props_json = build_properties_json(&row.fields);
format!(r#"{{"type":"Feature","geometry":{geom_json},"properties":{props_json}}}"#)
})
.collect::<Vec<_>>()
.join(",");
format!(r#"{{"type":"FeatureCollection","features":[{features_json}]}}"#)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SrsInfo {
pub srs_name: String,
pub srs_id: i32,
pub organization: String,
pub org_coord_sys_id: i32,
pub definition: String,
pub description: Option<String>,
}
impl SrsInfo {
pub fn wgs84() -> Self {
Self {
srs_name: "WGS 84".into(),
srs_id: 4326,
organization: "EPSG".into(),
org_coord_sys_id: 4326,
definition: concat!(
"GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",",
"SPHEROID[\"WGS 84\",6378137,298.257223563]],",
"PRIMEM[\"Greenwich\",0],",
"UNIT[\"degree\",0.0174532925199433]]"
)
.into(),
description: Some("World Geodetic System 1984".into()),
}
}
pub fn web_mercator() -> Self {
Self {
srs_name: "WGS 84 / Pseudo-Mercator".into(),
srs_id: 3857,
organization: "EPSG".into(),
org_coord_sys_id: 3857,
definition: concat!(
"PROJCS[\"WGS 84 / Pseudo-Mercator\",",
"GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",",
"SPHEROID[\"WGS 84\",6378137,298.257223563]],",
"PRIMEM[\"Greenwich\",0],",
"UNIT[\"degree\",0.0174532925199433]],",
"PROJECTION[\"Mercator_1SP\"],",
"PARAMETER[\"central_meridian\",0],",
"PARAMETER[\"scale_factor\",1],",
"PARAMETER[\"false_easting\",0],",
"PARAMETER[\"false_northing\",0],",
"UNIT[\"metre\",1]]"
)
.into(),
description: Some("Web Mercator projection used by many web mapping services".into()),
}
}
pub fn is_geographic(&self) -> bool {
(4000..5000).contains(&self.srs_id)
}
pub fn epsg_code(&self) -> Option<i32> {
if self.organization.eq_ignore_ascii_case("EPSG") {
Some(self.org_coord_sys_id)
} else {
None
}
}
}
pub(super) fn build_properties_json(fields: &HashMap<String, FieldValue>) -> String {
if fields.is_empty() {
return "{}".into();
}
let mut pairs: Vec<(&String, &FieldValue)> = fields.iter().collect();
pairs.sort_by_key(|(k, _)| k.as_str());
let members: String = pairs
.iter()
.map(|(k, v)| format!("{}:{}", super::types::json_string_escape(k), v.to_json()))
.collect::<Vec<_>>()
.join(",");
format!("{{{members}}}")
}