11use geo:: { Geometry , MapCoords , TryConvert } ;
2- use geo_traits:: to_geo:: ToGeoGeometry ;
3- use geojson;
2+ use geozero:: {
3+ geojson:: GeoJsonString , wkt:: Wkt as WktReader , CoordDimensions , ToGeo , ToJson , ToWkb , ToWkt ,
4+ } ;
45use routee_compass:: plugin:: output:: OutputPluginError ;
56use serde:: { Deserialize , Serialize } ;
67use serde_json:: Value ;
7- use wkb;
8- use wkt:: { ToWkt , TryFromWkt } ;
98
109#[ derive( Deserialize , Serialize , Clone , Debug ) ]
1110#[ serde( rename_all = "snake_case" ) ]
@@ -29,8 +28,12 @@ impl IsochroneOutputFormat {
2928 "expected WKT string for geometry deserialization, found: {value:?}"
3029 ) )
3130 } ) ?;
32- let g = Geometry :: try_from_wkt_str ( wkt) . map_err ( |e| OutputPluginError :: OutputPluginFailed ( format ! ( "failure deserializing WKT geometry from output row due to: {e} - WKT string: \" {wkt}\" " ) ) ) ?;
33- Ok ( g)
31+ let geometry_f64 = WktReader ( wkt) . to_geo ( ) . map_err ( |e| {
32+ OutputPluginError :: OutputPluginFailed ( format ! (
33+ "failure deserializing WKT geometry from output row due to: {e} - WKT string: \" {wkt}\" "
34+ ) )
35+ } ) ?;
36+ try_convert_f32 ( & geometry_f64)
3437 }
3538 IsochroneOutputFormat :: Wkb => {
3639 let wkb_str = value. as_str ( ) . ok_or_else ( || {
@@ -44,31 +47,41 @@ impl IsochroneOutputFormat {
4447 "failed to decode WKB hex string: {e} - WKB string: \" {wkb_str}\" "
4548 ) )
4649 } ) ?;
47- // Read geometry as f64, then convert to f32
48- let geom_trait = wkb :: reader :: read_wkb ( & wkb_bytes) . map_err ( |e| OutputPluginError :: OutputPluginFailed ( format ! (
49- "failure deserializing WKB geometry from output row due to: {e} - WKB string: \" {wkb_str} \" "
50- ) ) ) ? ;
51- let geometry_f64 = geom_trait . to_geometry ( ) ;
52- let geometry_f32 = try_convert_f32 ( & geometry_f64 ) ?;
53- Ok ( geometry_f32 )
50+ // Read geometry as f64 via geozero , then convert to f32
51+ let geometry_f64 = geozero :: wkb :: Wkb ( wkb_bytes) . to_geo ( ) . map_err ( |e| {
52+ OutputPluginError :: OutputPluginFailed ( format ! (
53+ "failure deserializing WKB geometry from output row due to: {e} - WKB string: \" {wkb_str} \" "
54+ ) )
55+ } ) ?;
56+ try_convert_f32 ( & geometry_f64 )
5457 }
5558 IsochroneOutputFormat :: GeoJson => {
5659 let geojson_str = value. as_str ( ) . ok_or_else ( || {
5760 OutputPluginError :: OutputPluginFailed ( format ! (
5861 "expected string for geometry deserialization, found: {value:?}"
5962 ) )
6063 } ) ?;
61- let geojson_obj = geojson_str. parse :: < geojson:: GeoJson > ( ) . map_err ( |e| {
64+ // Parse the JSON and extract geometry, handling both raw geometry and Feature format
65+ let parsed: serde_json:: Value = serde_json:: from_str ( geojson_str) . map_err ( |e| {
6266 OutputPluginError :: OutputPluginFailed ( format ! (
63- "failure parsing GeoJSON from geometry string due to : {e}, found: {value:?}"
67+ "failure parsing GeoJSON string: {e}, found: {value:?}"
6468 ) )
6569 } ) ?;
66- let geometry = geo_types:: Geometry :: < f32 > :: try_from ( geojson_obj) . map_err ( |e| {
70+ let geom_json = if parsed[ "type" ] == "Feature" {
71+ serde_json:: to_string ( & parsed[ "geometry" ] ) . map_err ( |e| {
72+ OutputPluginError :: OutputPluginFailed ( format ! (
73+ "failure extracting geometry from GeoJSON Feature: {e}"
74+ ) )
75+ } ) ?
76+ } else {
77+ geojson_str. to_string ( )
78+ } ;
79+ let geometry_f64 = GeoJsonString ( geom_json) . to_geo ( ) . map_err ( |e| {
6780 OutputPluginError :: OutputPluginFailed ( format ! (
68- "failure converting GeoJSON to Geometry due to: {e}"
81+ "failure parsing GeoJSON geometry due to: {e}, found: {value:? }"
6982 ) )
7083 } ) ?;
71- Ok ( geometry )
84+ try_convert_f32 ( & geometry_f64 )
7285 }
7386 }
7487 }
@@ -78,42 +91,55 @@ impl IsochroneOutputFormat {
7891 geometry : & Geometry < f32 > ,
7992 ) -> Result < String , OutputPluginError > {
8093 match self {
81- IsochroneOutputFormat :: Wkt => Ok ( geometry. wkt_string ( ) ) ,
94+ IsochroneOutputFormat :: Wkt => {
95+ let geom: Geometry < f64 > = geometry. try_convert ( ) . map_err ( |e| {
96+ OutputPluginError :: OutputPluginFailed ( format ! (
97+ "unable to convert geometry from f32 to f64: {e}"
98+ ) )
99+ } ) ?;
100+ geom. to_wkt ( ) . map_err ( |e| {
101+ OutputPluginError :: OutputPluginFailed ( format ! (
102+ "failed to write geometry as WKT: {e}"
103+ ) )
104+ } )
105+ }
82106 IsochroneOutputFormat :: Wkb => {
83- let mut out_bytes = vec ! [ ] ;
84107 let geom: Geometry < f64 > = geometry. try_convert ( ) . map_err ( |e| {
85108 OutputPluginError :: OutputPluginFailed ( format ! (
86109 "unable to convert geometry from f32 to f64: {e}"
87110 ) )
88111 } ) ?;
89- let write_options = wkb:: writer:: WriteOptions {
90- endianness : wkb:: Endianness :: BigEndian ,
91- } ;
92- wkb:: writer:: write_geometry ( & mut out_bytes, & geom, & write_options) . map_err (
93- |e| {
94- OutputPluginError :: OutputPluginFailed ( format ! (
95- "failed to write geometry as WKB: {e}"
96- ) )
97- } ,
98- ) ?;
99-
112+ let out_bytes = geom. to_wkb ( CoordDimensions :: xy ( ) ) . map_err ( |e| {
113+ OutputPluginError :: OutputPluginFailed ( format ! (
114+ "failed to write geometry as WKB: {e}"
115+ ) )
116+ } ) ?;
100117 Ok ( out_bytes
101118 . iter ( )
102119 . map ( |b| format ! ( "{b:02X?}" ) )
103120 . collect :: < Vec < String > > ( )
104121 . join ( "" ) )
105122 }
106123 IsochroneOutputFormat :: GeoJson => {
107- let geometry = geojson:: Geometry :: from ( geometry) ;
108- let feature = geojson:: Feature {
109- bbox : None ,
110- geometry : Some ( geometry) ,
111- id : None ,
112- properties : None ,
113- foreign_members : None ,
114- } ;
115- let result = serde_json:: to_value ( feature) ?;
116- Ok ( result. to_string ( ) )
124+ let geom: Geometry < f64 > = geometry. try_convert ( ) . map_err ( |e| {
125+ OutputPluginError :: OutputPluginFailed ( format ! (
126+ "unable to convert geometry from f32 to f64: {e}"
127+ ) )
128+ } ) ?;
129+ let geom_json_str = geom. to_json ( ) . map_err ( |e| {
130+ OutputPluginError :: OutputPluginFailed ( format ! (
131+ "failed to serialize geometry as GeoJSON: {e}"
132+ ) )
133+ } ) ?;
134+ let geom_json: serde_json:: Value = serde_json:: from_str ( & geom_json_str) ?;
135+ let feature = serde_json:: json!( {
136+ "type" : "Feature" ,
137+ "bbox" : null,
138+ "geometry" : geom_json,
139+ "id" : null,
140+ "properties" : null
141+ } ) ;
142+ Ok ( feature. to_string ( ) )
117143 }
118144 }
119145 }
@@ -137,3 +163,94 @@ fn try_convert_f32(g: &Geometry<f64>) -> Result<Geometry<f32>, OutputPluginError
137163 }
138164 } )
139165}
166+
167+ #[ cfg( test) ]
168+ mod tests {
169+ use super :: * ;
170+ use geo:: polygon;
171+ use serde_json:: json;
172+
173+ fn sample_polygon_f32 ( ) -> Geometry < f32 > {
174+ Geometry :: Polygon ( polygon ! [
175+ ( x: 0.0f32 , y: 0.0f32 ) ,
176+ ( x: 1.0f32 , y: 0.0f32 ) ,
177+ ( x: 1.0f32 , y: 1.0f32 ) ,
178+ ( x: 0.0f32 , y: 1.0f32 ) ,
179+ ( x: 0.0f32 , y: 0.0f32 ) ,
180+ ] )
181+ }
182+
183+ #[ test]
184+ fn test_serialize_wkt_roundtrip ( ) {
185+ let fmt = IsochroneOutputFormat :: Wkt ;
186+ let geom = sample_polygon_f32 ( ) ;
187+ let serialized = fmt
188+ . serialize_geometry ( & geom)
189+ . expect ( "wkt serialization failed" ) ;
190+ assert ! (
191+ serialized. starts_with( "POLYGON" ) ,
192+ "expected WKT POLYGON, got: {serialized}"
193+ ) ;
194+ let deserialized = fmt
195+ . deserialize_geometry ( & json ! ( serialized) )
196+ . expect ( "wkt deserialization failed" ) ;
197+ // verify the geometry type is preserved
198+ assert ! ( matches!( deserialized, Geometry :: Polygon ( _) ) ) ;
199+ }
200+
201+ #[ test]
202+ fn test_serialize_wkb_roundtrip ( ) {
203+ let fmt = IsochroneOutputFormat :: Wkb ;
204+ let geom = sample_polygon_f32 ( ) ;
205+ let serialized = fmt
206+ . serialize_geometry ( & geom)
207+ . expect ( "wkb serialization failed" ) ;
208+ // WKB is hex-encoded
209+ assert ! ( serialized. len( ) > 0 , "expected non-empty WKB hex string" ) ;
210+ let deserialized = fmt
211+ . deserialize_geometry ( & json ! ( serialized) )
212+ . expect ( "wkb deserialization failed" ) ;
213+ assert ! ( matches!( deserialized, Geometry :: Polygon ( _) ) ) ;
214+ }
215+
216+ #[ test]
217+ fn test_serialize_geojson_roundtrip ( ) {
218+ let fmt = IsochroneOutputFormat :: GeoJson ;
219+ let geom = sample_polygon_f32 ( ) ;
220+ let serialized = fmt
221+ . serialize_geometry ( & geom)
222+ . expect ( "geojson serialization failed" ) ;
223+ let parsed: serde_json:: Value =
224+ serde_json:: from_str ( & serialized) . expect ( "result should be valid json" ) ;
225+ assert_eq ! ( parsed[ "type" ] , "Feature" ) ;
226+ assert_eq ! ( parsed[ "geometry" ] [ "type" ] , "Polygon" ) ;
227+ let deserialized = fmt
228+ . deserialize_geometry ( & json ! ( serialized) )
229+ . expect ( "geojson deserialization failed" ) ;
230+ assert ! ( matches!( deserialized, Geometry :: Polygon ( _) ) ) ;
231+ }
232+
233+ #[ test]
234+ fn test_empty_geometry_wkt ( ) {
235+ let fmt = IsochroneOutputFormat :: Wkt ;
236+ let result = fmt. empty_geometry ( ) . expect ( "empty geometry wkt failed" ) ;
237+ assert ! (
238+ result. contains( "POLYGON" ) ,
239+ "expected WKT POLYGON, got: {result}"
240+ ) ;
241+ }
242+
243+ #[ test]
244+ fn test_deserialize_wkt_invalid_input ( ) {
245+ let fmt = IsochroneOutputFormat :: Wkt ;
246+ let result = fmt. deserialize_geometry ( & json ! ( "NOT VALID WKT!!" ) ) ;
247+ assert ! ( result. is_err( ) , "expected error for invalid WKT" ) ;
248+ }
249+
250+ #[ test]
251+ fn test_deserialize_wkb_invalid_hex ( ) {
252+ let fmt = IsochroneOutputFormat :: Wkb ;
253+ let result = fmt. deserialize_geometry ( & json ! ( "ZZZNOTVALIDHEX" ) ) ;
254+ assert ! ( result. is_err( ) , "expected error for invalid WKB hex" ) ;
255+ }
256+ }
0 commit comments