1use crate::costing;
6use crate::elevation::ShapeFormat;
7pub use crate::shapes::ShapePoint;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11#[derive(Serialize, Debug, Clone)]
13pub struct TracePoint {
14 pub lat: f64,
16 pub lon: f64,
18}
19
20impl TracePoint {
21 pub fn new(lat: f64, lon: f64) -> Self {
23 Self { lat, lon }
24 }
25}
26
27#[derive(Serialize, Debug, Clone, Default)]
29#[serde(rename_all = "snake_case")]
30pub enum ShapeMatch {
31 #[default]
33 WalkOrSnap,
34 MapSnap,
36 EdgeWalk,
38}
39
40#[derive(Serialize, Debug, Clone)]
42pub struct Filter {
43 pub attributes: Vec<String>,
45 pub action: FilterAction,
47}
48
49#[derive(Serialize, Debug, Clone)]
51#[serde(rename_all = "snake_case")]
52pub enum FilterAction {
53 Include,
55 Exclude,
57}
58
59#[serde_with::skip_serializing_none]
61#[derive(Serialize, Debug, Clone, Default)]
62pub struct TraceOptions {
63 pub search_radius: Option<f64>,
68 pub gps_accuracy: Option<f64>,
72 pub breakage_distance: Option<f64>,
76 pub interpolation_distance: Option<f64>,
80}
81
82#[serde_with::skip_serializing_none]
84#[derive(Serialize, Debug, Clone)]
85pub struct Manifest {
86 shape: Option<Vec<TracePoint>>,
87 encoded_polyline: Option<String>,
88 shape_format: Option<ShapeFormat>,
89 #[serde(flatten)]
90 costing: costing::Costing,
91 shape_match: ShapeMatch,
92 filters: Option<Filter>,
93 trace_options: Option<TraceOptions>,
94 units: Option<super::Units>,
95 id: Option<String>,
96 language: Option<String>,
97 durations: Option<Vec<f64>>,
98 use_timestamps: Option<bool>,
99 begin_time: Option<String>,
100}
101
102impl Manifest {
103 pub fn builder(shape: impl IntoIterator<Item = TracePoint>, costing: costing::Costing) -> Self {
105 Self {
106 shape: Some(shape.into_iter().collect()),
107 encoded_polyline: None,
108 shape_format: None,
109 costing,
110 shape_match: ShapeMatch::default(),
111 filters: None,
112 trace_options: None,
113 units: None,
114 id: None,
115 language: None,
116 durations: None,
117 use_timestamps: None,
118 begin_time: None,
119 }
120 }
121
122 pub fn builder_encoded(encoded_polyline: impl ToString, costing: costing::Costing) -> Self {
126 Self {
127 shape: None,
128 encoded_polyline: Some(encoded_polyline.to_string()),
129 shape_format: None,
130 costing,
131 shape_match: ShapeMatch::default(),
132 filters: None,
133 trace_options: None,
134 units: None,
135 id: None,
136 language: None,
137 durations: None,
138 use_timestamps: None,
139 begin_time: None,
140 }
141 }
142
143 pub fn shape_match(mut self, shape_match: ShapeMatch) -> Self {
145 self.shape_match = shape_match;
146 self
147 }
148
149 pub fn shape_format(mut self, shape_format: ShapeFormat) -> Self {
155 debug_assert!(
156 self.shape.is_none(),
157 "shape is set and setting the shape_format is requested. This combination does not make sense: shapes and encoded_polylines as input are mutually exclusive."
158 );
159 self.shape_format = Some(shape_format);
160 self
161 }
162
163 pub fn include_attributes(
165 mut self,
166 attributes: impl IntoIterator<Item = impl Into<String>>,
167 ) -> Self {
168 self.filters = Some(Filter {
169 attributes: attributes.into_iter().map(|a| a.into()).collect(),
170 action: FilterAction::Include,
171 });
172 self
173 }
174
175 pub fn exclude_attributes(
177 mut self,
178 attributes: impl IntoIterator<Item = impl Into<String>>,
179 ) -> Self {
180 self.filters = Some(Filter {
181 attributes: attributes.into_iter().map(|a| a.into()).collect(),
182 action: FilterAction::Exclude,
183 });
184 self
185 }
186
187 pub fn trace_options(mut self, trace_options: TraceOptions) -> Self {
189 self.trace_options = Some(trace_options);
190 self
191 }
192
193 pub fn units(mut self, units: super::Units) -> Self {
197 self.units = Some(units);
198 self
199 }
200
201 pub fn id(mut self, id: impl ToString) -> Self {
205 self.id = Some(id.to_string());
206 self
207 }
208
209 pub fn language(mut self, language: impl ToString) -> Self {
214 self.language = Some(language.to_string());
215 self
216 }
217
218 pub fn durations(mut self, durations: impl IntoIterator<Item = f64>) -> Self {
223 self.durations = Some(durations.into_iter().collect());
224 self
225 }
226
227 pub fn use_timestamps(mut self, use_timestamps: bool) -> Self {
231 self.use_timestamps = Some(use_timestamps);
232 self
233 }
234
235 pub fn begin_time(mut self, begin_time: impl ToString) -> Self {
239 self.begin_time = Some(begin_time.to_string());
240 self
241 }
242}
243
244#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
246#[serde(rename_all = "snake_case")]
247pub enum Surface {
248 PavedSmooth,
250 Paved,
252 PavedRough,
254 Compacted,
256 Dirt,
258 Gravel,
260 Path,
262 Impassable,
264}
265
266#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
268#[serde(rename_all = "snake_case")]
269pub enum RoadClass {
270 Motorway,
272 Trunk,
274 Primary,
276 Secondary,
278 Tertiary,
280 Unclassified,
282 Residential,
284 ServiceOther,
286}
287
288#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
290#[serde(rename_all = "snake_case")]
291pub enum EdgeUse {
292 Road,
294 Ramp,
296 TurnChannel,
298 Track,
300 Driveway,
302 Alley,
304 ParkingAisle,
306 EmergencyAccess,
308 DriveThrough,
310 Culdesac,
312 Cycleway,
314 MountainBike,
316 Sidewalk,
318 Footway,
320 Steps,
322 Ferry,
324 #[serde(rename = "rail-ferry")]
326 RailFerry,
327 ServiceRoad,
329 Path,
331 LivingStreet,
333 PedestrianCrossing,
335 Other,
337}
338
339#[derive(Deserialize, Serialize, Debug, Clone)]
341pub struct Edge {
342 #[serde(default)]
344 pub surface: Option<Surface>,
345 #[serde(default)]
347 pub road_class: Option<RoadClass>,
348 #[serde(default)]
350 pub r#use: Option<EdgeUse>,
351 #[serde(default)]
353 pub length: Option<f64>,
354 #[serde(default)]
356 pub names: Option<Vec<String>>,
357 #[serde(default)]
359 pub begin_shape_index: Option<u32>,
360 #[serde(default)]
362 pub end_shape_index: Option<u32>,
363 #[serde(default)]
365 pub way_id: Option<u64>,
366 #[serde(default)]
368 pub source_percent_along: Option<f64>,
369 #[serde(default)]
371 pub target_percent_along: Option<f64>,
372}
373
374#[derive(Deserialize, Serialize, Debug, Clone)]
376pub struct MatchedPoint {
377 pub lat: f64,
379 pub lon: f64,
381 #[serde(default)]
383 pub r#type: Option<String>,
384 #[serde(default)]
386 pub edge_index: Option<u32>,
387 #[serde(default)]
389 pub distance_along_edge: Option<f64>,
390}
391
392#[derive(Deserialize, Serialize, Debug, Clone)]
394pub struct Response {
395 #[serde(default)]
397 pub edges: Vec<Edge>,
398 #[serde(default)]
400 pub matched_points: Vec<MatchedPoint>,
401 #[serde(default)]
403 pub shape: Option<String>,
404 #[serde(default)]
406 pub units: Option<String>,
407 #[serde(default)]
409 pub id: Option<String>,
410 #[serde(default)]
413 pub warnings: Vec<Value>,
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 #[test]
421 fn test_serialize_manifest() {
422 let manifest = Manifest::builder(
423 [TracePoint::new(48.1, 11.5), TracePoint::new(48.2, 11.6)],
424 costing::Costing::Auto(Default::default()),
425 );
426 let value = serde_json::to_value(&manifest).unwrap();
427 assert_eq!(
428 value,
429 serde_json::json!({
430 "shape": [{"lat": 48.1, "lon": 11.5}, {"lat": 48.2, "lon": 11.6}],
431 "costing": "auto",
432 "costing_options": {"auto": {}},
433 "shape_match": "walk_or_snap"
434 })
435 );
436 }
437
438 #[test]
439 fn test_serialize_manifest_encoded_polyline() {
440 let manifest =
441 Manifest::builder_encoded("some_polyline", costing::Costing::Auto(Default::default()))
442 .shape_format(ShapeFormat::Polyline5);
443 let value = serde_json::to_value(&manifest).unwrap();
444 assert_eq!(
445 value,
446 serde_json::json!({
447 "encoded_polyline": "some_polyline",
448 "shape_format": "polyline5",
449 "costing": "auto",
450 "costing_options": {"auto": {}},
451 "shape_match": "walk_or_snap"
452 })
453 );
454 }
455
456 #[test]
457 fn test_serialize_manifest_with_filter() {
458 let manifest = Manifest::builder(
459 [TracePoint::new(48.1, 11.5)],
460 costing::Costing::Pedestrian(Default::default()),
461 )
462 .shape_match(ShapeMatch::MapSnap)
463 .include_attributes(["edge.surface", "edge.road_class"]);
464 let value = serde_json::to_value(&manifest).unwrap();
465 assert_eq!(
466 value,
467 serde_json::json!({
468 "shape": [{"lat": 48.1, "lon": 11.5}],
469 "costing": "pedestrian",
470 "costing_options": {"pedestrian": {}},
471 "shape_match": "map_snap",
472 "filters": {
473 "attributes": ["edge.surface", "edge.road_class"],
474 "action": "include"
475 }
476 })
477 );
478 }
479
480 #[test]
481 fn test_serialize_manifest_exclude_attributes() {
482 let manifest = Manifest::builder(
483 [TracePoint::new(48.1, 11.5)],
484 costing::Costing::Auto(Default::default()),
485 )
486 .exclude_attributes(["edge.names"]);
487 let value = serde_json::to_value(&manifest).unwrap();
488 assert_eq!(
489 value["filters"],
490 serde_json::json!({
491 "attributes": ["edge.names"],
492 "action": "exclude"
493 })
494 );
495 }
496
497 #[test]
498 fn test_serialize_manifest_with_all_options() {
499 let manifest = Manifest::builder(
500 [TracePoint::new(48.1, 11.5)],
501 costing::Costing::Auto(Default::default()),
502 )
503 .units(super::super::Units::Imperial)
504 .id("my-trace")
505 .language("de-DE")
506 .trace_options(TraceOptions {
507 search_radius: Some(50.0),
508 gps_accuracy: Some(10.0),
509 breakage_distance: Some(3000.0),
510 interpolation_distance: Some(20.0),
511 })
512 .durations(vec![0.0, 5.0, 10.0])
513 .use_timestamps(true)
514 .begin_time("2025-01-15T08:30");
515 let value = serde_json::to_value(&manifest).unwrap();
516 assert_eq!(value["units"], serde_json::json!("miles"));
517 assert_eq!(value["id"], serde_json::json!("my-trace"));
518 assert_eq!(value["language"], serde_json::json!("de-DE"));
519 assert_eq!(
520 value["trace_options"]["search_radius"],
521 serde_json::json!(50.0)
522 );
523 assert_eq!(
524 value["trace_options"]["gps_accuracy"],
525 serde_json::json!(10.0)
526 );
527 assert_eq!(
528 value["trace_options"]["breakage_distance"],
529 serde_json::json!(3000.0)
530 );
531 assert_eq!(
532 value["trace_options"]["interpolation_distance"],
533 serde_json::json!(20.0)
534 );
535 assert_eq!(value["durations"], serde_json::json!([0.0, 5.0, 10.0]));
536 assert_eq!(value["use_timestamps"], serde_json::json!(true));
537 assert_eq!(value["begin_time"], serde_json::json!("2025-01-15T08:30"));
538 }
539
540 #[test]
541 fn test_serialize_trace_options_skips_none() {
542 let manifest = Manifest::builder(
543 [TracePoint::new(48.1, 11.5)],
544 costing::Costing::Auto(Default::default()),
545 )
546 .trace_options(TraceOptions {
547 search_radius: Some(50.0),
548 ..Default::default()
549 });
550 let value = serde_json::to_value(&manifest).unwrap();
551 assert_eq!(
552 value["trace_options"],
553 serde_json::json!({"search_radius": 50.0})
554 );
555 }
556
557 #[test]
558 fn test_deserialize_response() {
559 let json = serde_json::json!({
560 "edges": [{
561 "surface": "paved",
562 "road_class": "primary",
563 "use": "road",
564 "length": 0.123,
565 "names": ["Main Street"],
566 "begin_shape_index": 0,
567 "end_shape_index": 5,
568 "way_id": 12345,
569 "source_percent_along": 0.1,
570 "target_percent_along": 0.9
571 }],
572 "matched_points": [{
573 "lat": 48.1,
574 "lon": 11.5,
575 "type": "matched",
576 "edge_index": 0,
577 "distance_along_edge": 0.5
578 }],
579 "shape": "encoded_shape_string",
580 "units": "km",
581 "id": "my-trace",
582 "warnings": [{"message": "some warning"}]
583 });
584 let response: Response = serde_json::from_value(json).unwrap();
585 assert_eq!(response.edges.len(), 1);
586 assert_eq!(response.edges[0].surface, Some(Surface::Paved));
587 assert_eq!(response.edges[0].road_class, Some(RoadClass::Primary));
588 assert_eq!(response.edges[0].r#use, Some(EdgeUse::Road));
589 assert_eq!(response.edges[0].length, Some(0.123));
590 assert_eq!(
591 response.edges[0].names,
592 Some(vec!["Main Street".to_string()])
593 );
594 assert_eq!(response.edges[0].begin_shape_index, Some(0));
595 assert_eq!(response.edges[0].end_shape_index, Some(5));
596 assert_eq!(response.edges[0].way_id, Some(12345));
597 assert_eq!(response.edges[0].source_percent_along, Some(0.1));
598 assert_eq!(response.edges[0].target_percent_along, Some(0.9));
599 assert_eq!(response.matched_points.len(), 1);
600 assert_eq!(response.matched_points[0].lat, 48.1);
601 assert_eq!(response.matched_points[0].lon, 11.5);
602 assert_eq!(
603 response.matched_points[0].r#type,
604 Some("matched".to_string())
605 );
606 assert_eq!(response.matched_points[0].edge_index, Some(0));
607 assert_eq!(response.matched_points[0].distance_along_edge, Some(0.5));
608 assert_eq!(response.shape, Some("encoded_shape_string".to_string()));
609 assert_eq!(response.units, Some("km".to_string()));
610 assert_eq!(response.id, Some("my-trace".to_string()));
611 assert_eq!(response.warnings.len(), 1);
612 }
613
614 #[test]
615 fn test_deserialize_response_with_defaults() {
616 let json = serde_json::json!({});
617 let response: Response = serde_json::from_value(json).unwrap();
618 assert_eq!(response.edges.len(), 0);
619 assert_eq!(response.matched_points.len(), 0);
620 assert_eq!(response.shape, None);
621 assert_eq!(response.units, None);
622 assert_eq!(response.id, None);
623 assert_eq!(response.warnings.len(), 0);
624 }
625
626 #[test]
627 fn test_deserialize_edge_with_defaults() {
628 let json = serde_json::json!({});
629 let edge: Edge = serde_json::from_value(json).unwrap();
630 assert_eq!(edge.surface, None);
631 assert_eq!(edge.road_class, None);
632 assert_eq!(edge.r#use, None);
633 assert_eq!(edge.length, None);
634 assert_eq!(edge.names, None);
635 assert_eq!(edge.way_id, None);
636 }
637
638 #[test]
639 fn test_serialize_shape_match_variants() {
640 assert_eq!(
641 serde_json::to_value(ShapeMatch::WalkOrSnap).unwrap(),
642 serde_json::json!("walk_or_snap")
643 );
644 assert_eq!(
645 serde_json::to_value(ShapeMatch::MapSnap).unwrap(),
646 serde_json::json!("map_snap")
647 );
648 assert_eq!(
649 serde_json::to_value(ShapeMatch::EdgeWalk).unwrap(),
650 serde_json::json!("edge_walk")
651 );
652 }
653}