use std::collections::HashMap;
use oxigdal_geojson_stream::{FeatureCollection, FeatureId, GeoJsonFeature, GeoJsonGeometry};
use crate::error::GpkgError;
use crate::vector::feature::{FeatureRow, FeatureTable};
use crate::vector::types::{FieldDefinition, FieldType, FieldValue, GpkgGeometry};
pub fn gpkg_geom_to_geojson(g: &GpkgGeometry) -> Result<GeoJsonGeometry, GpkgError> {
match g {
GpkgGeometry::Point { x, y } => Ok(GeoJsonGeometry::Point([*x, *y])),
GpkgGeometry::LineString { coords } => {
let pts: Vec<[f64; 2]> = coords.iter().map(|(x, y)| [*x, *y]).collect();
Ok(GeoJsonGeometry::LineString(pts))
}
GpkgGeometry::Polygon { rings } => {
let gj_rings: Vec<Vec<[f64; 2]>> = rings
.iter()
.map(|ring| ring.iter().map(|(x, y)| [*x, *y]).collect())
.collect();
Ok(GeoJsonGeometry::Polygon(gj_rings))
}
GpkgGeometry::MultiPoint { points } => {
let pts: Vec<[f64; 2]> = points.iter().map(|(x, y)| [*x, *y]).collect();
Ok(GeoJsonGeometry::MultiPoint(pts))
}
GpkgGeometry::MultiLineString { lines } => {
let gj_lines: Vec<Vec<[f64; 2]>> = lines
.iter()
.map(|l| l.iter().map(|(x, y)| [*x, *y]).collect())
.collect();
Ok(GeoJsonGeometry::MultiLineString(gj_lines))
}
GpkgGeometry::MultiPolygon { polygons } => {
let gj_polys: Vec<Vec<Vec<[f64; 2]>>> = polygons
.iter()
.map(|poly| {
poly.iter()
.map(|ring| ring.iter().map(|(x, y)| [*x, *y]).collect())
.collect()
})
.collect();
Ok(GeoJsonGeometry::MultiPolygon(gj_polys))
}
GpkgGeometry::PointZ { x, y, z } => Ok(GeoJsonGeometry::PointZ([*x, *y, *z])),
GpkgGeometry::LineStringZ { coords } => {
let pts: Vec<[f64; 3]> = coords.iter().map(|(x, y, z)| [*x, *y, *z]).collect();
Ok(GeoJsonGeometry::LineStringZ(pts))
}
GpkgGeometry::PolygonZ { rings } => {
let gj_rings: Vec<Vec<[f64; 3]>> = rings
.iter()
.map(|ring| ring.iter().map(|(x, y, z)| [*x, *y, *z]).collect())
.collect();
Ok(GeoJsonGeometry::PolygonZ(gj_rings))
}
GpkgGeometry::MultiPointZ { points } => {
let pts: Vec<[f64; 3]> = points.iter().map(|(x, y, z)| [*x, *y, *z]).collect();
Ok(GeoJsonGeometry::MultiPointZ(pts))
}
GpkgGeometry::MultiLineStringZ { lines } => {
let gj_lines: Vec<Vec<[f64; 3]>> = lines
.iter()
.map(|l| l.iter().map(|(x, y, z)| [*x, *y, *z]).collect())
.collect();
Ok(GeoJsonGeometry::MultiLineStringZ(gj_lines))
}
GpkgGeometry::MultiPolygonZ { polygons } => {
let gj_polys: Vec<Vec<Vec<[f64; 3]>>> = polygons
.iter()
.map(|poly| {
poly.iter()
.map(|ring| ring.iter().map(|(x, y, z)| [*x, *y, *z]).collect())
.collect()
})
.collect();
Ok(GeoJsonGeometry::MultiPolygonZ(gj_polys))
}
GpkgGeometry::PointM { x, y, .. } => Ok(GeoJsonGeometry::Point([*x, *y])),
GpkgGeometry::LineStringM { coords } => {
let pts: Vec<[f64; 2]> = coords.iter().map(|(x, y, _m)| [*x, *y]).collect();
Ok(GeoJsonGeometry::LineString(pts))
}
GpkgGeometry::PolygonM { rings } => {
let gj_rings: Vec<Vec<[f64; 2]>> = rings
.iter()
.map(|ring| ring.iter().map(|(x, y, _m)| [*x, *y]).collect())
.collect();
Ok(GeoJsonGeometry::Polygon(gj_rings))
}
GpkgGeometry::MultiPointM { points } => {
let pts: Vec<[f64; 2]> = points.iter().map(|(x, y, _m)| [*x, *y]).collect();
Ok(GeoJsonGeometry::MultiPoint(pts))
}
GpkgGeometry::MultiLineStringM { lines } => {
let gj_lines: Vec<Vec<[f64; 2]>> = lines
.iter()
.map(|l| l.iter().map(|(x, y, _m)| [*x, *y]).collect())
.collect();
Ok(GeoJsonGeometry::MultiLineString(gj_lines))
}
GpkgGeometry::MultiPolygonM { polygons } => {
let gj_polys: Vec<Vec<Vec<[f64; 2]>>> = polygons
.iter()
.map(|poly| {
poly.iter()
.map(|ring| ring.iter().map(|(x, y, _m)| [*x, *y]).collect())
.collect()
})
.collect();
Ok(GeoJsonGeometry::MultiPolygon(gj_polys))
}
GpkgGeometry::PointZM(p) => Ok(GeoJsonGeometry::PointZ([p.x, p.y, p.z.unwrap_or(0.0)])),
GpkgGeometry::LineStringZM { coords } => {
let pts: Vec<[f64; 3]> = coords
.iter()
.map(|p| [p.x, p.y, p.z.unwrap_or(0.0)])
.collect();
Ok(GeoJsonGeometry::LineStringZ(pts))
}
GpkgGeometry::PolygonZM { rings } => {
let gj_rings: Vec<Vec<[f64; 3]>> = rings
.iter()
.map(|ring| {
ring.iter()
.map(|p| [p.x, p.y, p.z.unwrap_or(0.0)])
.collect()
})
.collect();
Ok(GeoJsonGeometry::PolygonZ(gj_rings))
}
GpkgGeometry::MultiPointZM { points } => {
let pts: Vec<[f64; 3]> = points
.iter()
.map(|p| [p.x, p.y, p.z.unwrap_or(0.0)])
.collect();
Ok(GeoJsonGeometry::MultiPointZ(pts))
}
GpkgGeometry::MultiLineStringZM { lines } => {
let gj_lines: Vec<Vec<[f64; 3]>> = lines
.iter()
.map(|l| l.iter().map(|p| [p.x, p.y, p.z.unwrap_or(0.0)]).collect())
.collect();
Ok(GeoJsonGeometry::MultiLineStringZ(gj_lines))
}
GpkgGeometry::MultiPolygonZM { polygons } => {
let gj_polys: Vec<Vec<Vec<[f64; 3]>>> = polygons
.iter()
.map(|poly| {
poly.iter()
.map(|ring| {
ring.iter()
.map(|p| [p.x, p.y, p.z.unwrap_or(0.0)])
.collect()
})
.collect()
})
.collect();
Ok(GeoJsonGeometry::MultiPolygonZ(gj_polys))
}
GpkgGeometry::GeometryCollection(geoms)
| GpkgGeometry::GeometryCollectionZ(geoms)
| GpkgGeometry::GeometryCollectionM(geoms)
| GpkgGeometry::GeometryCollectionZM(geoms) => {
let converted: Result<Vec<GeoJsonGeometry>, GpkgError> =
geoms.iter().map(gpkg_geom_to_geojson).collect();
Ok(GeoJsonGeometry::GeometryCollection(converted?))
}
GpkgGeometry::Empty => Ok(GeoJsonGeometry::Null),
}
}
pub fn geojson_geom_to_gpkg(g: &GeoJsonGeometry) -> Result<GpkgGeometry, GpkgError> {
match g {
GeoJsonGeometry::Point([x, y]) => Ok(GpkgGeometry::Point { x: *x, y: *y }),
GeoJsonGeometry::LineString(pts) => {
let coords: Vec<(f64, f64)> = pts.iter().map(|[x, y]| (*x, *y)).collect();
Ok(GpkgGeometry::LineString { coords })
}
GeoJsonGeometry::Polygon(rings) => {
let gpkg_rings: Vec<Vec<(f64, f64)>> = rings
.iter()
.map(|ring| ring.iter().map(|[x, y]| (*x, *y)).collect())
.collect();
Ok(GpkgGeometry::Polygon { rings: gpkg_rings })
}
GeoJsonGeometry::MultiPoint(pts) => {
let points: Vec<(f64, f64)> = pts.iter().map(|[x, y]| (*x, *y)).collect();
Ok(GpkgGeometry::MultiPoint { points })
}
GeoJsonGeometry::MultiLineString(lines) => {
let gpkg_lines: Vec<Vec<(f64, f64)>> = lines
.iter()
.map(|l| l.iter().map(|[x, y]| (*x, *y)).collect())
.collect();
Ok(GpkgGeometry::MultiLineString { lines: gpkg_lines })
}
GeoJsonGeometry::MultiPolygon(polys) => {
let gpkg_polys: Vec<Vec<Vec<(f64, f64)>>> = polys
.iter()
.map(|poly| {
poly.iter()
.map(|ring| ring.iter().map(|[x, y]| (*x, *y)).collect())
.collect()
})
.collect();
Ok(GpkgGeometry::MultiPolygon {
polygons: gpkg_polys,
})
}
GeoJsonGeometry::PointZ([x, y, z]) => Ok(GpkgGeometry::PointZ {
x: *x,
y: *y,
z: *z,
}),
GeoJsonGeometry::LineStringZ(pts) => {
let coords: Vec<(f64, f64, f64)> = pts.iter().map(|[x, y, z]| (*x, *y, *z)).collect();
Ok(GpkgGeometry::LineStringZ { coords })
}
GeoJsonGeometry::PolygonZ(rings) => {
let gpkg_rings: Vec<Vec<(f64, f64, f64)>> = rings
.iter()
.map(|ring| ring.iter().map(|[x, y, z]| (*x, *y, *z)).collect())
.collect();
Ok(GpkgGeometry::PolygonZ { rings: gpkg_rings })
}
GeoJsonGeometry::MultiPointZ(pts) => {
let points: Vec<(f64, f64, f64)> = pts.iter().map(|[x, y, z]| (*x, *y, *z)).collect();
Ok(GpkgGeometry::MultiPointZ { points })
}
GeoJsonGeometry::MultiLineStringZ(lines) => {
let gpkg_lines: Vec<Vec<(f64, f64, f64)>> = lines
.iter()
.map(|l| l.iter().map(|[x, y, z]| (*x, *y, *z)).collect())
.collect();
Ok(GpkgGeometry::MultiLineStringZ { lines: gpkg_lines })
}
GeoJsonGeometry::MultiPolygonZ(polys) => {
let gpkg_polys: Vec<Vec<Vec<(f64, f64, f64)>>> = polys
.iter()
.map(|poly| {
poly.iter()
.map(|ring| ring.iter().map(|[x, y, z]| (*x, *y, *z)).collect())
.collect()
})
.collect();
Ok(GpkgGeometry::MultiPolygonZ {
polygons: gpkg_polys,
})
}
GeoJsonGeometry::GeometryCollection(geoms) => {
let converted: Result<Vec<GpkgGeometry>, GpkgError> =
geoms.iter().map(geojson_geom_to_gpkg).collect();
Ok(GpkgGeometry::GeometryCollection(converted?))
}
GeoJsonGeometry::Null => Ok(GpkgGeometry::Empty),
}
}
pub fn feature_table_to_geojson(table: &FeatureTable) -> Result<FeatureCollection, GpkgError> {
let mut features = Vec::with_capacity(table.features.len());
for row in &table.features {
let geometry = match &row.geometry {
Some(g) => Some(gpkg_geom_to_geojson(g)?),
None => Some(GeoJsonGeometry::Null),
};
let properties = Some(build_properties_serde_map(&row.fields));
features.push(GeoJsonFeature {
id: Some(FeatureId::Number(row.fid as f64)),
geometry,
properties,
});
}
Ok(FeatureCollection {
features,
bbox: None,
bbox_3d: None,
crs: None,
name: Some(table.name.clone()),
})
}
pub fn feature_table_from_geojson(
fc: &FeatureCollection,
name: &str,
geometry_column: &str,
) -> Result<FeatureTable, GpkgError> {
let mut table = FeatureTable::new(name, geometry_column);
let schema = infer_schema_from_features(&fc.features);
table.schema = schema;
let mut sequential_fid: i64 = 1;
for feat in &fc.features {
let fid = match &feat.id {
Some(FeatureId::Number(n)) => {
let candidate = *n as i64;
sequential_fid = sequential_fid.max(candidate + 1);
candidate
}
_ => {
let fid = sequential_fid;
sequential_fid += 1;
fid
}
};
let geometry = match &feat.geometry {
Some(gj_geom) => {
let gpkg = geojson_geom_to_gpkg(gj_geom)?;
if matches!(gpkg, GpkgGeometry::Empty) {
None
} else {
Some(gpkg)
}
}
None => None,
};
let fields = extract_fields_from_properties(&feat.properties, &table.schema);
table.features.push(FeatureRow {
fid,
geometry,
fields,
});
}
Ok(table)
}
fn build_properties_serde_map(fields: &HashMap<String, FieldValue>) -> serde_json::Value {
let mut map = serde_json::Map::with_capacity(fields.len());
for (key, val) in fields {
map.insert(key.clone(), field_value_to_json(val));
}
serde_json::Value::Object(map)
}
fn field_value_to_json(val: &FieldValue) -> serde_json::Value {
match val {
FieldValue::Integer(i) => serde_json::Value::Number((*i).into()),
FieldValue::Real(f) => {
match serde_json::Number::from_f64(*f) {
Some(n) => serde_json::Value::Number(n),
None => serde_json::Value::Null, }
}
FieldValue::Text(s) => serde_json::Value::String(s.clone()),
FieldValue::Blob(b) => {
let hex: String = b.iter().map(|byte| format!("{byte:02x}")).collect();
serde_json::Value::String(format!("0x{hex}"))
}
FieldValue::Boolean(b) => serde_json::Value::Bool(*b),
FieldValue::Null => serde_json::Value::Null,
}
}
fn json_value_to_field_value(
val: &serde_json::Value,
expected_type: Option<FieldType>,
) -> FieldValue {
match val {
serde_json::Value::Null => FieldValue::Null,
serde_json::Value::Bool(b) => match expected_type {
Some(FieldType::Integer) => FieldValue::Integer(if *b { 1 } else { 0 }),
_ => FieldValue::Boolean(*b),
},
serde_json::Value::Number(n) => {
match expected_type {
Some(FieldType::Real) => FieldValue::Real(n.as_f64().unwrap_or(0.0)),
Some(FieldType::Text) => FieldValue::Text(n.to_string()),
_ => {
if let Some(i) = n.as_i64() {
FieldValue::Integer(i)
} else {
FieldValue::Real(n.as_f64().unwrap_or(0.0))
}
}
}
}
serde_json::Value::String(s) => match expected_type {
Some(FieldType::Integer) => s
.parse::<i64>()
.map(FieldValue::Integer)
.unwrap_or_else(|_| FieldValue::Text(s.clone())),
Some(FieldType::Real) => s
.parse::<f64>()
.map(FieldValue::Real)
.unwrap_or_else(|_| FieldValue::Text(s.clone())),
_ => FieldValue::Text(s.clone()),
},
other => FieldValue::Text(other.to_string()),
}
}
fn json_value_to_field_type(val: &serde_json::Value) -> FieldType {
match val {
serde_json::Value::Bool(_) => FieldType::Boolean,
serde_json::Value::Number(n) => {
if n.is_i64() || n.is_u64() {
FieldType::Integer
} else {
FieldType::Real
}
}
serde_json::Value::String(_) => FieldType::Text,
serde_json::Value::Null => FieldType::Null,
_ => FieldType::Text, }
}
fn infer_schema_from_features(features: &[GeoJsonFeature]) -> Vec<FieldDefinition> {
let props_obj = features.iter().find_map(|feat| {
let props = feat.properties.as_ref()?;
let map = props.as_object()?;
if map.is_empty() { None } else { Some(map) }
});
let Some(map) = props_obj else {
return Vec::new();
};
map.iter()
.map(|(key, val)| {
let field_type = if val.is_null() {
FieldType::Text
} else {
json_value_to_field_type(val)
};
FieldDefinition {
name: key.clone(),
field_type,
not_null: false,
primary_key: false,
default_value: None,
}
})
.collect()
}
fn extract_fields_from_properties(
properties: &Option<serde_json::Value>,
schema: &[FieldDefinition],
) -> HashMap<String, FieldValue> {
let mut fields: HashMap<String, FieldValue> = HashMap::with_capacity(schema.len());
for col in schema {
fields.insert(col.name.clone(), FieldValue::Null);
}
let Some(props) = properties else {
return fields;
};
let Some(map) = props.as_object() else {
return fields;
};
for (key, val) in map {
let expected = schema
.iter()
.find(|col| col.name == *key)
.map(|col| col.field_type);
let field_value = json_value_to_field_value(val, expected);
fields.insert(key.clone(), field_value);
}
fields
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn test_gpkg_point_to_geojson() {
let g = GpkgGeometry::Point { x: 1.0, y: 2.0 };
let gj = gpkg_geom_to_geojson(&g).expect("conversion should succeed");
assert_eq!(gj, GeoJsonGeometry::Point([1.0, 2.0]));
}
#[test]
fn test_geojson_point_to_gpkg() {
let gj = GeoJsonGeometry::Point([3.0, 4.0]);
let g = geojson_geom_to_gpkg(&gj).expect("conversion should succeed");
assert_eq!(g, GpkgGeometry::Point { x: 3.0, y: 4.0 });
}
#[test]
fn test_null_geometry_round_trip() {
let gpkg = GpkgGeometry::Empty;
let gj = gpkg_geom_to_geojson(&gpkg).expect("empty → Null should succeed");
assert_eq!(gj, GeoJsonGeometry::Null);
let back = geojson_geom_to_gpkg(&gj).expect("Null → Empty should succeed");
assert_eq!(back, GpkgGeometry::Empty);
}
#[test]
fn test_m_variant_projects_to_xy() {
let g = GpkgGeometry::PointM {
x: 5.0,
y: 6.0,
m: 99.0,
};
let gj = gpkg_geom_to_geojson(&g).expect("PointM → Point");
assert_eq!(gj, GeoJsonGeometry::Point([5.0, 6.0]));
}
}