use geo::{BoundingRect as _, MapCoords as _};
use geo_index::rtree::sort::HilbertSort;
use geo_index::rtree::{RTree, RTreeBuilder};
use geojson::{GeoJson, JsonValue};
use geozero::error::GeozeroError;
use geozero::{ColumnValue, PropertyProcessor};
use martin_tile_utils::{EARTH_CIRCUMFERENCE, wgs84_to_webmercator};
use serde_json::Map;
use crate::tiles::geojson::error::GeoJsonError;
#[derive(Clone)]
pub(crate) struct PreparedFeature {
pub(crate) geom: geo_types::Geometry<f64>,
pub(crate) properties: Option<Map<String, JsonValue>>,
}
type Preprocessed = (
Vec<PreparedFeature>,
RTree<f64>,
Option<geo_types::Rect<f64>>,
);
pub(crate) fn preprocess_geojson(geojson: GeoJson) -> Result<Preprocessed, GeoJsonError> {
let raw = match geojson {
GeoJson::FeatureCollection(fc) => fc
.features
.into_iter()
.filter_map(|f| Some((f.geometry?, f.properties)))
.collect::<Vec<_>>(),
GeoJson::Feature(f) => f.geometry.map(|g| (g, f.properties)).into_iter().collect(),
GeoJson::Geometry(g) => vec![(g, None)],
};
let mut features = Vec::with_capacity(raw.len());
let mut bboxes = Vec::with_capacity(raw.len());
for (geometry, properties) in raw {
let geom = geo_types::Geometry::<f64>::try_from(geometry.value)
.map_err(|e| GeoJsonError::GeoJsonError(Box::new(e)))?;
let geom = geom.map_coords(|c| {
let (x, y) = wgs84_to_webmercator(c.x, c.y);
geo_types::Coord { x, y }
});
let Some(bbox) = geom.bounding_rect() else {
continue;
};
let (min, max) = (bbox.min(), bbox.max());
bboxes.push([min.x, min.y, max.x, max.y]);
features.push(PreparedFeature { geom, properties });
}
let feature_count =
u32::try_from(features.len()).map_err(|_| GeoJsonError::TooManyFeatures(features.len()))?;
let mut builder = RTreeBuilder::<f64>::new(feature_count);
for bbox in &bboxes {
builder.add(bbox[0], bbox[1], bbox[2], bbox[3]);
}
let tree = builder.finish::<HilbertSort>();
let data_bounds = bboxes
.iter()
.copied()
.reduce(|a, b| {
[
a[0].min(b[0]),
a[1].min(b[1]),
a[2].max(b[2]),
a[3].max(b[3]),
]
})
.map(|[min_x, min_y, max_x, max_y]| {
geo_types::Rect::new(
geo_types::Coord { x: min_x, y: min_y },
geo_types::Coord { x: max_x, y: max_y },
)
});
Ok((features, tree, data_bounds))
}
pub(crate) fn tile_length_from_zoom(zoom: u8) -> f64 {
EARTH_CIRCUMFERENCE / f64::from(1_u32 << zoom)
}
pub(crate) fn process_properties<P: PropertyProcessor>(
properties: &Map<String, JsonValue>,
processor: &mut P,
) -> Result<(), GeozeroError> {
for (i, (key, value)) in properties.iter().enumerate() {
match value {
JsonValue::String(v) => processor.property(i, key, &ColumnValue::String(v))?,
JsonValue::Number(v) => {
if let Some(n) = v.as_i64() {
processor.property(i, key, &ColumnValue::Long(n))?
} else if let Some(n) = v.as_u64() {
processor.property(i, key, &ColumnValue::ULong(n))?
} else if let Some(n) = v.as_f64() {
processor.property(i, key, &ColumnValue::Double(n))?
} else {
return Err(GeozeroError::Property(key.clone()));
}
}
JsonValue::Bool(v) => processor.property(i, key, &ColumnValue::Bool(*v))?,
JsonValue::Array(v) => {
let json_string =
serde_json::to_string(v).map_err(|_err| GeozeroError::Property(key.clone()))?;
processor.property(i, key, &ColumnValue::Json(&json_string))?
}
JsonValue::Object(v) => {
let json_string =
serde_json::to_string(v).map_err(|_err| GeozeroError::Property(key.clone()))?;
processor.property(i, key, &ColumnValue::Json(&json_string))?
}
JsonValue::Null => false,
};
}
Ok(())
}