routee_compass/plugin/input/default/inject/
inject_plugin_config.rs1use super::{inject_plugin::InjectInputPlugin, CoordinateOrientation, WriteMode};
4use crate::plugin::input::InputPluginError;
5use geojson::{Feature, FeatureCollection, GeoJson};
6use routee_compass_core::util::geo::{geo_io_utils, PolygonalRTree};
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case", tag = "format")]
12pub enum InjectPluginConfig {
13 SpatialKeyValue(SpatialInjectPlugin),
14 KeyValue(BasicInjectPlugin),
15}
16
17#[derive(Serialize, Deserialize, Clone, Debug)]
19pub struct BasicInjectPlugin {
20 pub key: String,
21 pub value: Value,
22 pub write_mode: WriteMode,
23}
24#[derive(Serialize, Deserialize, Clone, Debug)]
28pub struct SpatialInjectPlugin {
29 pub spatial_input_file: String,
30 pub source_key: Option<String>,
31 pub key: String,
32 pub write_mode: WriteMode,
33 pub orientation: CoordinateOrientation,
34 pub default: Option<Value>,
35}
36
37impl InjectPluginConfig {
38 pub fn build(&self) -> Result<InjectInputPlugin, InputPluginError> {
39 match self {
40 InjectPluginConfig::KeyValue(basic) => Ok(InjectInputPlugin::Basic {
41 key: basic.key.clone(),
42 value: basic.value.clone(),
43 write_mode: basic.write_mode.clone(),
44 }),
45 InjectPluginConfig::SpatialKeyValue(spatial) => {
46 let contents =
47 std::fs::read_to_string(&spatial.spatial_input_file).map_err(|e| {
48 InputPluginError::BuildFailed(format!(
49 "file read failed for file '{}': {}",
50 spatial.spatial_input_file, e
51 ))
52 })?;
53 let geojson = contents.parse::<GeoJson>().map_err(|e| {
54 InputPluginError::BuildFailed(format!(
55 "unable to parse file '{}' as GeoJSON: {}",
56 spatial.spatial_input_file, e
57 ))
58 })?;
59 let fc = FeatureCollection::try_from(geojson).map_err(|e| {
60 InputPluginError::BuildFailed(format!(
61 "failed to unpack GeoJSON in file '{}' as a FeatureCollection: {}",
62 spatial.spatial_input_file, e
63 ))
64 })?;
65 let geometries_with_properties = fc
66 .features
67 .into_iter()
68 .map(|row| {
69 match &row.geometry {
70 None => Err(InputPluginError::BuildFailed(format!(
71 "row {} from GeoJSON in file '{}' has no geometry",
72 get_id(&row),
73 spatial.spatial_input_file
74 ))),
75 Some(g) => {
76 let geometry_f64: geo::Geometry<f64> = g.try_into().map_err(|e| {
77 let msg = format!("row {} from GeoJSON in file '{}' has geometry that cannot be deserialized: {}", get_id(&row), spatial.spatial_input_file, e);
78 InputPluginError::BuildFailed(msg)
79 })?;
80 match geometry_f64 {
82 geo::Geometry::Polygon(_) | geo::Geometry::MultiPolygon(_) => Ok(()),
83 _ => Err(InputPluginError::BuildFailed(format!(
84 "row {} from GeoJSON in file '{}' is not polygonal",
85 get_id(&row), spatial.spatial_input_file
86 ))),
87 }?;
88 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)))?;
89
90 let properties = match (&row.properties, spatial.source_key.clone()) {
92 (None, _) => Err(InputPluginError::BuildFailed(format!("row {} from GeoJSON in file '{}' has no properties",
93 get_id(&row), spatial.spatial_input_file))),
94 (Some(props), None) => {
95 Ok(serde_json::json!(props))
96 },
97 (Some(props), Some(props_key)) => {
98 let value = props.get(&props_key).ok_or_else(|| InputPluginError::BuildFailed(format!("row {} from GeoJSON in file '{}' has no properties",
99 get_id(&row), spatial.spatial_input_file)))?;
100 Ok(value.clone())
101 },
102 }?;
103
104 Ok((geometry, properties))
105 }
106 }
107 })
108 .collect::<Result<Vec<_>, _>>()?;
109
110 let spatial_index =
111 PolygonalRTree::new(geometries_with_properties).map_err(|e| {
112 InputPluginError::BuildFailed(format!(
113 "failed to build spatial index over GeoJSON input from file '{}': {}",
114 spatial.spatial_input_file, e
115 ))
116 })?;
117
118 Ok(InjectInputPlugin::Spatial {
119 values: spatial_index,
120 key: spatial.key.clone(),
121 write_mode: spatial.write_mode.clone(),
122 orientation: spatial.orientation,
123 default: spatial.default.clone(),
124 })
125 }
126 }
127 }
128}
129
130fn get_id(row: &Feature) -> String {
131 row.id
132 .as_ref()
133 .map(|i| format!("{i:?}"))
134 .unwrap_or_default()
135}