1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
pub use crate::shapes::ShapePoint;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[serde_with::skip_serializing_none]
#[derive(Serialize, Default, Debug, PartialEq, Clone)]
/// Request to the elevation service.
pub struct Manifest {
id: Option<String>,
height_precision: Option<HeightPrecision>,
range: Option<bool>,
resample_distance: Option<f64>,
shape: Option<Vec<ShapePoint>>,
encoded_polyline: Option<String>,
shape_format: Option<ShapeFormat>,
}
impl Manifest {
/// Creates a new [`Manifest`] builder.
pub fn builder() -> Self {
Default::default()
}
/// Name your route request.
///
/// If id is specified, the naming will be sent through to the response.
pub fn id(mut self, id: impl ToString) -> Self {
self.id = Some(id.to_string());
self
}
/// Controls whether the returned array is one-dimensional (height only, the default) or two-dimensional (range and height).
///
/// This can be used to generate a graph along a route, because a 2D-array has values for x (the range) and y (the height) at each shape point.
/// Steepness or gradient can also be computed from a profile request (e.g. when range = `true`).
///
/// Default: `false`
pub fn include_range(mut self) -> Self {
self.range = Some(true);
self
}
/// Specifying the distance (in meters) at which the input polyline is sampled in order to provide uniform distances between samples along the polyline.
pub fn resample_distance(mut self, resample_distance_meters: f64) -> Self {
self.resample_distance = Some(resample_distance_meters);
self
}
/// Allows increasing the returned precision for heights.
///
/// The default of returning as integer values works fine for most cases.
/// However, when charting elevation results along a nearly flat road can lead to "stair step" changes in elevation.
///
/// Default: [`HeightPrecision::ZeroDecimalPlaces`]
pub fn height_precision(mut self, height_precision: HeightPrecision) -> Self {
self.height_precision = Some(height_precision);
self
}
/// Specifies whether the polyline is encoded with
/// - 6 digit precision ([`ShapeFormat::Polyline6`]) or
/// - 5 digit precision ([`ShapeFormat::Polyline5`]).
///
/// Default: [`ShapeFormat::Polyline6`], meaning the encoded polyline is expected to be 6 digit precision.
pub fn shape_format(mut self, shape_format: ShapeFormat) -> Self {
debug_assert!(self.shape.is_none(), "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.");
self.shape_format = Some(shape_format);
self
}
/// Latitudes/longitudes where the elevation data is desired in decimal degrees.
///
/// The input coordinates can come from many input sources, such as a GPS location, a point or a click on a map, a geocoding service, and so on.
/// The locations are visited in the order specified.
pub fn shape(mut self, shape: impl IntoIterator<Item = impl Into<ShapePoint>>) -> Self {
debug_assert!(self.shape_format.is_none(), "shape_format is set and setting a shape is requested. This combination does not make sense: shapes and encoded_polylines as input are mutually exclusive.");
debug_assert!(self.encoded_polyline.is_none(), "encoded_polyline is set and setting a shape is requested. This combination does not make sense: shapes and encoded_polylines as input are mutually exclusive.");
self.shape = Some(shape.into_iter().map(|s| s.into()).collect());
self
}
/// A set of polyline encoded latitude/longitude pairs of a line or shape where the elevation data is desired.
///
/// Details on polyline encoding and decoding can be found [here](https://valhalla.github.io/valhalla/decoding/).
/// See [`Self::shape_format`] to set the precision of the polyline.
pub fn encoded_polyline(mut self, encoded_polyline: impl ToString) -> Self {
debug_assert!(self.shape.is_none(), "shape is set and setting the encoded_polyline is requested. This combination does not make sense: shapes and encoded_polylines as input are mutually exclusive.");
self.encoded_polyline = Some(encoded_polyline.to_string());
self
}
}
/// Specifies the precision (number of decimal places) of all returned height values.
#[derive(serde_repr::Serialize_repr, Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum HeightPrecision {
/// Zero decimal places (="integer precision") of precision for all height values
///
/// Example: `123`
#[default]
ZeroDecimalPlaces = 0,
/// One decimal places of precision for all height values
///
/// Example: `123.3`
OneDecimalPlace,
/// Two decimal places of precision for all height values
///
/// Example: `123.34`
TwoDecimalPlaces,
}
#[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
/// Specifies the precision (number of decimal places) of the encoded polyline.
pub enum ShapeFormat {
/// polyline is encoded with 6 digit precision
#[serde(rename = "polyline6")]
#[default]
Polyline6,
/// polyline is encoded with 5 digit precision
#[serde(rename = "polyline5")]
Polyline5,
}
#[derive(Deserialize, Debug, Clone)]
/// Response to the request.
pub struct Response {
/// Name of the route request.
///
/// If id is specified via [`Manifest::id`] the naming will be sent through to the response.
pub id: Option<String>,
/// The specified shape coordinates from the input request.
///
/// `None` if requested enabled via [`Manifest::shape`]
pub shape: Option<Vec<ShapePoint>>,
/// The specified encoded polyline, with six degrees of precision, coordinates from the input request.
///
/// `None` if requested enabled via [`Manifest::encoded_polyline`]
pub encoded_polyline: Option<String>,
/// The 2D array of range (x) and height (y) per input latitude, longitude coordinate.
///
/// `None` if not enabled via [`Manifest::include_range`]
pub range_height: Vec<Option<(f64, Option<f64>)>>,
/// The range or distance along the input locations.
///
/// It is the cumulative distance along the previous latitiude, longitude coordinates up to the current coordinate.
/// The x-value for the first coordinate in the shape will always be 0.
pub x_coordinate: Option<f64>,
/// The height or elevation of the associated latitude, longitude pair.
///
/// The height is returned as null if no height data exists for a given location.
pub y_coordinate: Option<f64>,
/// An array of height for the associated latitude, longitude coordinates.
#[serde(default = "Vec::new")]
pub height: Vec<f32>,
/// This array may contain warning objects informing about deprecated
/// - request parameters,
/// - clamped values
/// - etc.
#[serde(default = "Vec::new")]
pub warnings: Vec<Value>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialisation() {
let manifest = Manifest::builder();
assert_eq!(
serde_json::to_value(&manifest).unwrap(),
serde_json::json!({})
);
}
#[test]
fn test_serialize_encoded_polyline() {
let manifest = Manifest::builder()
.id("some_id")
.height_precision(HeightPrecision::OneDecimalPlace)
.include_range()
.encoded_polyline("polyline")
.shape_format(ShapeFormat::Polyline6);
assert_eq!(
serde_json::to_value(&manifest).unwrap(),
serde_json::json!({"id":"some_id","height_precision":1,"range":true,"encoded_polyline":"polyline","shape_format":"polyline6"})
);
}
}