use super::{inject_plugin::InjectInputPlugin, CoordinateOrientation, WriteMode};
use crate::plugin::input::InputPluginError;
use geojson::{Feature, FeatureCollection, GeoJson};
use routee_compass_core::util::geo::{geo_io_utils, PolygonalRTree};
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "format")]
pub enum InjectPluginConfig {
SpatialKeyValue(SpatialInjectPlugin),
KeyValue(BasicInjectPlugin),
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct BasicInjectPlugin {
pub key: String,
pub value: Value,
pub write_mode: WriteMode,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SpatialInjectPlugin {
pub spatial_input_file: String,
pub source_key: Option<String>,
pub key: String,
pub write_mode: WriteMode,
pub orientation: CoordinateOrientation,
pub default: Option<Value>,
}
impl InjectPluginConfig {
pub fn build(&self) -> Result<InjectInputPlugin, InputPluginError> {
match self {
InjectPluginConfig::KeyValue(basic) => Ok(InjectInputPlugin::Basic {
key: basic.key.clone(),
value: basic.value.clone(),
write_mode: basic.write_mode.clone(),
}),
InjectPluginConfig::SpatialKeyValue(spatial) => {
let contents =
std::fs::read_to_string(&spatial.spatial_input_file).map_err(|e| {
InputPluginError::BuildFailed(format!(
"file read failed for file '{}': {}",
spatial.spatial_input_file, e
))
})?;
let geojson = contents.parse::<GeoJson>().map_err(|e| {
InputPluginError::BuildFailed(format!(
"unable to parse file '{}' as GeoJSON: {}",
spatial.spatial_input_file, e
))
})?;
let fc = FeatureCollection::try_from(geojson).map_err(|e| {
InputPluginError::BuildFailed(format!(
"failed to unpack GeoJSON in file '{}' as a FeatureCollection: {}",
spatial.spatial_input_file, e
))
})?;
let geometries_with_properties = fc
.features
.into_iter()
.map(|row| {
match &row.geometry {
None => Err(InputPluginError::BuildFailed(format!(
"row {} from GeoJSON in file '{}' has no geometry",
get_id(&row),
spatial.spatial_input_file
))),
Some(g) => {
let geometry_f64: geo::Geometry<f64> = g.try_into().map_err(|e| {
let msg = format!("row {} from GeoJSON in file '{}' has geometry that cannot be deserialized: {}", get_id(&row), spatial.spatial_input_file, e);
InputPluginError::BuildFailed(msg)
})?;
match geometry_f64 {
geo::Geometry::Polygon(_) | geo::Geometry::MultiPolygon(_) => Ok(()),
_ => Err(InputPluginError::BuildFailed(format!(
"row {} from GeoJSON in file '{}' is not polygonal",
get_id(&row), spatial.spatial_input_file
))),
}?;
let geometry = geo_io_utils::downsample_geometry(geometry_f64).map_err(|e| InputPluginError::BuildFailed(format!("row {} from GeoJSON in file '{}' has geometry that cannot be downsampled to f32: {}", get_id(&row), spatial.spatial_input_file, e)))?;
let properties = match (&row.properties, spatial.source_key.clone()) {
(None, _) => Err(InputPluginError::BuildFailed(format!("row {} from GeoJSON in file '{}' has no properties",
get_id(&row), spatial.spatial_input_file))),
(Some(props), None) => {
Ok(serde_json::json!(props))
},
(Some(props), Some(props_key)) => {
let value = props.get(&props_key).ok_or_else(|| InputPluginError::BuildFailed(format!("row {} from GeoJSON in file '{}' has no properties",
get_id(&row), spatial.spatial_input_file)))?;
Ok(value.clone())
},
}?;
Ok((geometry, properties))
}
}
})
.collect::<Result<Vec<_>, _>>()?;
let spatial_index =
PolygonalRTree::new(geometries_with_properties).map_err(|e| {
InputPluginError::BuildFailed(format!(
"failed to build spatial index over GeoJSON input from file '{}': {}",
spatial.spatial_input_file, e
))
})?;
Ok(InjectInputPlugin::Spatial {
values: spatial_index,
key: spatial.key.clone(),
write_mode: spatial.write_mode.clone(),
orientation: spatial.orientation,
default: spatial.default.clone(),
})
}
}
}
}
fn get_id(row: &Feature) -> String {
row.id
.as_ref()
.map(|i| format!("{i:?}"))
.unwrap_or_default()
}