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 #[serde(default)]
374 pub begin_heading: Option<f64>,
375 #[serde(default)]
377 pub end_heading: Option<f64>,
378 #[serde(default)]
380 pub speed: Option<f64>,
381 #[serde(default)]
383 pub toll: Option<bool>,
384 #[serde(default)]
386 pub tunnel: Option<bool>,
387 #[serde(default)]
389 pub bridge: Option<bool>,
390 #[serde(default)]
392 pub roundabout: Option<bool>,
393 #[serde(default)]
395 pub internal_intersection: Option<bool>,
396 #[serde(default)]
398 pub sign: Option<EdgeSign>,
399}
400
401#[derive(Deserialize, Serialize, Debug, Clone)]
403pub struct EdgeSign {
404 #[serde(default)]
406 pub exit_number_elements: Vec<SignElement>,
407 #[serde(default)]
409 pub exit_branch_elements: Vec<SignElement>,
410 #[serde(default)]
412 pub exit_toward_elements: Vec<SignElement>,
413 #[serde(default)]
415 pub exit_name_elements: Vec<SignElement>,
416}
417
418#[derive(Deserialize, Serialize, Debug, Clone)]
420pub struct SignElement {
421 pub text: String,
423}
424
425#[derive(Deserialize, Serialize, Debug, Clone)]
427pub struct MatchedPoint {
428 pub lat: f64,
430 pub lon: f64,
432 #[serde(default)]
434 pub r#type: Option<String>,
435 #[serde(default)]
437 pub edge_index: Option<u32>,
438 #[serde(default)]
440 pub distance_along_edge: Option<f64>,
441}
442
443#[derive(Deserialize, Serialize, Debug, Clone)]
445pub struct Response {
446 #[serde(default)]
448 pub edges: Vec<Edge>,
449 #[serde(default)]
451 pub matched_points: Vec<MatchedPoint>,
452 #[serde(default)]
454 pub shape: Option<String>,
455 #[serde(default)]
457 pub units: Option<String>,
458 #[serde(default)]
460 pub id: Option<String>,
461 #[serde(default)]
464 pub warnings: Vec<Value>,
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 #[test]
472 fn test_serialize_manifest() {
473 let manifest = Manifest::builder(
474 [TracePoint::new(48.1, 11.5), TracePoint::new(48.2, 11.6)],
475 costing::Costing::Auto(Default::default()),
476 );
477 let value = serde_json::to_value(&manifest).unwrap();
478 assert_eq!(
479 value,
480 serde_json::json!({
481 "shape": [{"lat": 48.1, "lon": 11.5}, {"lat": 48.2, "lon": 11.6}],
482 "costing": "auto",
483 "costing_options": {"auto": {}},
484 "shape_match": "walk_or_snap"
485 })
486 );
487 }
488
489 #[test]
490 fn test_serialize_manifest_encoded_polyline() {
491 let manifest =
492 Manifest::builder_encoded("some_polyline", costing::Costing::Auto(Default::default()))
493 .shape_format(ShapeFormat::Polyline5);
494 let value = serde_json::to_value(&manifest).unwrap();
495 assert_eq!(
496 value,
497 serde_json::json!({
498 "encoded_polyline": "some_polyline",
499 "shape_format": "polyline5",
500 "costing": "auto",
501 "costing_options": {"auto": {}},
502 "shape_match": "walk_or_snap"
503 })
504 );
505 }
506
507 #[test]
508 fn test_serialize_manifest_with_filter() {
509 let manifest = Manifest::builder(
510 [TracePoint::new(48.1, 11.5)],
511 costing::Costing::Pedestrian(Default::default()),
512 )
513 .shape_match(ShapeMatch::MapSnap)
514 .include_attributes(["edge.surface", "edge.road_class"]);
515 let value = serde_json::to_value(&manifest).unwrap();
516 assert_eq!(
517 value,
518 serde_json::json!({
519 "shape": [{"lat": 48.1, "lon": 11.5}],
520 "costing": "pedestrian",
521 "costing_options": {"pedestrian": {}},
522 "shape_match": "map_snap",
523 "filters": {
524 "attributes": ["edge.surface", "edge.road_class"],
525 "action": "include"
526 }
527 })
528 );
529 }
530
531 #[test]
532 fn test_serialize_manifest_exclude_attributes() {
533 let manifest = Manifest::builder(
534 [TracePoint::new(48.1, 11.5)],
535 costing::Costing::Auto(Default::default()),
536 )
537 .exclude_attributes(["edge.names"]);
538 let value = serde_json::to_value(&manifest).unwrap();
539 assert_eq!(
540 value["filters"],
541 serde_json::json!({
542 "attributes": ["edge.names"],
543 "action": "exclude"
544 })
545 );
546 }
547
548 #[test]
549 fn test_serialize_manifest_with_all_options() {
550 let manifest = Manifest::builder(
551 [TracePoint::new(48.1, 11.5)],
552 costing::Costing::Auto(Default::default()),
553 )
554 .units(super::super::Units::Imperial)
555 .id("my-trace")
556 .language("de-DE")
557 .trace_options(TraceOptions {
558 search_radius: Some(50.0),
559 gps_accuracy: Some(10.0),
560 breakage_distance: Some(3000.0),
561 interpolation_distance: Some(20.0),
562 })
563 .durations(vec![0.0, 5.0, 10.0])
564 .use_timestamps(true)
565 .begin_time("2025-01-15T08:30");
566 let value = serde_json::to_value(&manifest).unwrap();
567 assert_eq!(value["units"], serde_json::json!("miles"));
568 assert_eq!(value["id"], serde_json::json!("my-trace"));
569 assert_eq!(value["language"], serde_json::json!("de-DE"));
570 assert_eq!(
571 value["trace_options"]["search_radius"],
572 serde_json::json!(50.0)
573 );
574 assert_eq!(
575 value["trace_options"]["gps_accuracy"],
576 serde_json::json!(10.0)
577 );
578 assert_eq!(
579 value["trace_options"]["breakage_distance"],
580 serde_json::json!(3000.0)
581 );
582 assert_eq!(
583 value["trace_options"]["interpolation_distance"],
584 serde_json::json!(20.0)
585 );
586 assert_eq!(value["durations"], serde_json::json!([0.0, 5.0, 10.0]));
587 assert_eq!(value["use_timestamps"], serde_json::json!(true));
588 assert_eq!(value["begin_time"], serde_json::json!("2025-01-15T08:30"));
589 }
590
591 #[test]
592 fn test_serialize_trace_options_skips_none() {
593 let manifest = Manifest::builder(
594 [TracePoint::new(48.1, 11.5)],
595 costing::Costing::Auto(Default::default()),
596 )
597 .trace_options(TraceOptions {
598 search_radius: Some(50.0),
599 ..Default::default()
600 });
601 let value = serde_json::to_value(&manifest).unwrap();
602 assert_eq!(
603 value["trace_options"],
604 serde_json::json!({"search_radius": 50.0})
605 );
606 }
607
608 #[test]
609 fn test_deserialize_response() {
610 let json = serde_json::json!({
611 "edges": [{
612 "surface": "paved",
613 "road_class": "primary",
614 "use": "road",
615 "length": 0.123,
616 "names": ["Main Street"],
617 "begin_shape_index": 0,
618 "end_shape_index": 5,
619 "way_id": 12345,
620 "source_percent_along": 0.1,
621 "target_percent_along": 0.9
622 }],
623 "matched_points": [{
624 "lat": 48.1,
625 "lon": 11.5,
626 "type": "matched",
627 "edge_index": 0,
628 "distance_along_edge": 0.5
629 }],
630 "shape": "encoded_shape_string",
631 "units": "km",
632 "id": "my-trace",
633 "warnings": [{"message": "some warning"}]
634 });
635 let response: Response = serde_json::from_value(json).unwrap();
636 assert_eq!(response.edges.len(), 1);
637 assert_eq!(response.edges[0].surface, Some(Surface::Paved));
638 assert_eq!(response.edges[0].road_class, Some(RoadClass::Primary));
639 assert_eq!(response.edges[0].r#use, Some(EdgeUse::Road));
640 assert_eq!(response.edges[0].length, Some(0.123));
641 assert_eq!(
642 response.edges[0].names,
643 Some(vec!["Main Street".to_string()])
644 );
645 assert_eq!(response.edges[0].begin_shape_index, Some(0));
646 assert_eq!(response.edges[0].end_shape_index, Some(5));
647 assert_eq!(response.edges[0].way_id, Some(12345));
648 assert_eq!(response.edges[0].source_percent_along, Some(0.1));
649 assert_eq!(response.edges[0].target_percent_along, Some(0.9));
650 assert_eq!(response.matched_points.len(), 1);
651 assert_eq!(response.matched_points[0].lat, 48.1);
652 assert_eq!(response.matched_points[0].lon, 11.5);
653 assert_eq!(
654 response.matched_points[0].r#type,
655 Some("matched".to_string())
656 );
657 assert_eq!(response.matched_points[0].edge_index, Some(0));
658 assert_eq!(response.matched_points[0].distance_along_edge, Some(0.5));
659 assert_eq!(response.shape, Some("encoded_shape_string".to_string()));
660 assert_eq!(response.units, Some("km".to_string()));
661 assert_eq!(response.id, Some("my-trace".to_string()));
662 assert_eq!(response.warnings.len(), 1);
663 }
664
665 #[test]
666 fn test_deserialize_response_with_defaults() {
667 let json = serde_json::json!({});
668 let response: Response = serde_json::from_value(json).unwrap();
669 assert_eq!(response.edges.len(), 0);
670 assert_eq!(response.matched_points.len(), 0);
671 assert_eq!(response.shape, None);
672 assert_eq!(response.units, None);
673 assert_eq!(response.id, None);
674 assert_eq!(response.warnings.len(), 0);
675 }
676
677 #[test]
678 fn test_deserialize_edge_with_defaults() {
679 let json = serde_json::json!({});
680 let edge: Edge = serde_json::from_value(json).unwrap();
681 assert_eq!(edge.surface, None);
682 assert_eq!(edge.road_class, None);
683 assert_eq!(edge.r#use, None);
684 assert_eq!(edge.length, None);
685 assert_eq!(edge.names, None);
686 assert_eq!(edge.way_id, None);
687 }
688
689 #[test]
690 fn test_serialize_shape_match_variants() {
691 assert_eq!(
692 serde_json::to_value(ShapeMatch::WalkOrSnap).unwrap(),
693 serde_json::json!("walk_or_snap")
694 );
695 assert_eq!(
696 serde_json::to_value(ShapeMatch::MapSnap).unwrap(),
697 serde_json::json!("map_snap")
698 );
699 assert_eq!(
700 serde_json::to_value(ShapeMatch::EdgeWalk).unwrap(),
701 serde_json::json!("edge_walk")
702 );
703 }
704}