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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
//! Cycle routing costing options.
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Default, Debug, Clone, Copy, PartialEq, Eq)]
/// Bicycle type.
pub enum BicycleType {
/// Road
///
/// A road-style bicycle with narrow tires that is generally lightweight and designed for speed on paved surfaces.
#[serde(rename = "Road", alias = "road")]
Road,
/// Hybrid or City
///
/// A bicycle made mostly for city riding or casual riding on roads and paths with good surfaces.
#[default]
#[serde(rename = "Hybrid", alias = "hybrid")]
Hybrid,
/// Cross
///
/// A cyclo-cross bicycle, which is similar to a road bicycle but with wider tires suitable to rougher surfaces.
#[serde(rename = "Cross", alias = "cross")]
Cross,
/// Mountain
///
/// A mountain bicycle suitable for most surfaces but generally heavier and slower on paved surfaces.
#[serde(rename = "Mountain", alias = "mountain")]
Mountain,
}
#[serde_with::skip_serializing_none]
#[derive(Serialize, Debug, Clone, Default, PartialEq)]
struct BicycleCostingOptionsInner {
bicycle_type: Option<BicycleType>,
cycling_speed: Option<f32>,
use_roads: Option<f32>,
use_hills: Option<f32>,
use_ferry: Option<f32>,
use_living_streets: Option<f32>,
avoid_bad_surfaces: Option<f32>,
bss_return_cost: Option<f32>,
bss_return_penalty: Option<f32>,
shortest: Option<bool>,
maneuver_penalty: Option<f32>,
gate_cost: Option<f32>,
gate_penalty: Option<f32>,
country_crossing_cost: Option<f32>,
country_crossing_penalty: Option<f32>,
service_penalty: Option<f32>,
}
#[derive(Serialize, Debug, Clone, Default, PartialEq)]
/// Bicycle costing options.
pub struct BicycleCostingOptions {
bicycle: BicycleCostingOptionsInner,
}
impl BicycleCostingOptions {
#[must_use]
/// Creates a new [`BicycleCostingOptions`] instance.
pub fn builder() -> Self {
Self::default()
}
/// Specifies the [`BicycleType`].
///
/// Adapts routing speeds and penalties.
///
/// Default: [`BicycleType::Hybrid`]
pub fn bicycle_type(mut self, bicycle_type: BicycleType) -> Self {
self.bicycle.bicycle_type = Some(bicycle_type);
self
}
/// Cycling speed is the average travel speed along smooth, flat roads.
///
/// The speed a rider can comfortably maintain over the desired distance of the route.
/// It can be modified (in the costing method) by surface type in conjunction with bicycle
/// type and (coming soon) by hilliness of the road section.
///
/// When no speed is specifically provided, the default speed is determined by the bicycle type
/// and are as follows:
/// - [`BicycleType::Road`] = 25 KPH (15.5 MPH),
/// - [`BicycleType::Cross`] = 20 KPH (13 MPH),
/// - [`BicycleType::Hybrid`] = 18 KPH (11.5 MPH), and
/// - [`BicycleType::Mountain`] = 16 KPH (10 MPH).
pub fn cycling_speed(mut self, speed: f32) -> Self {
self.bicycle.cycling_speed = Some(speed);
self
}
/// A cyclist's propensity to use roads alongside other vehicles.
///
/// This is a range of values from `0` to `1`:
/// - `0` attempts to avoid roads and stay on cycleways and paths,
/// - and `1` indicates the rider is more comfortable riding on roads.
///
/// Based on this factor, roads with certain classifications and higher speeds are penalized
/// in an attempt to avoid them when finding the best path.
///
/// Default: `0.5`
pub fn use_roads(mut self, willingness: f32) -> Self {
self.bicycle.use_roads = Some(willingness);
self
}
/// Desire to tackle hills in routes.
///
/// This is a range of values from 0 to 1:
/// - 0 attempts to avoid hills and steep grades even if it means a longer (time/distance) path,
/// - while `1` indicates the rider does not fear hills and steeper grades.
///
/// Based on the use_hills factor, penalties are applied to roads based on elevation change
/// and grade. These penalties help the path avoid hilly roads in favor of flatter roads or
/// less steep grades where available. Note that it is not always possible to find
/// alternate paths to avoid hills (for example when route locations are in mountainous
/// areas).
///
/// Default: `0.5`
pub fn use_hills(mut self, willingness: f32) -> Self {
self.bicycle.use_hills = Some(willingness);
self
}
/// Willingness to take ferries.
///
/// This is a range of values between `0` and `1`.
/// - Values near `0` attempt to avoid ferries and
/// - values near `1` will favor ferries.
///
/// Note that sometimes ferries are required to complete a route so values of `0` are not
/// guaranteed to avoid ferries entirely.
///
/// Default: `0.5`
pub fn use_ferry(mut self, willingness: f32) -> Self {
self.bicycle.use_ferry = Some(willingness);
self
}
/// Willingness to take living streets.
///
/// This is a range of values between `0` and `1`:
/// - Values near `0` attempt to avoid living streets and
/// - values from `0.5` to `1` will currently have no effect on route selection.
///
/// Note that sometimes living streets are required to complete a route so values of `0` are not
/// guaranteed to avoid living streets entirely.
///
/// Default: `0.5`
pub fn use_living_streets(mut self, willingness: f32) -> Self {
self.bicycle.use_living_streets = Some(willingness);
self
}
/// How much a cyclist wants to avoid roads with poor surfaces relative to the bicycle type used.
///
/// This is a range of values between `0` and 1:
/// - When the value is 0, there is no penalization of roads with different surface types; only
/// bicycle speed on each surface is taken into account.
/// - As the value approaches 1, roads with poor surfaces for the bike are penalized heavier
/// so that they are only taken if they significantly improve travel time.
/// - When the value is equal to 1, all bad surfaces are completely disallowed from routing,
/// including start and end points.
///
/// Default: `0.25`
pub fn avoid_bad_surfaces(mut self, willingness: f32) -> Self {
self.bicycle.avoid_bad_surfaces = Some(willingness);
self
}
/// This value is useful when bikeshare is chosen as travel mode.
///
/// It is meant to give the time will be used to return a rental bike.
/// This value will be displayed in the final directions and used to calculate the whole duration.
///
/// Default: `120` seconds
pub fn bss_return_cost(mut self, cost: f32) -> Self {
self.bicycle.bss_return_cost = Some(cost);
self
}
/// This value is useful when bikeshare is chosen as travel mode.
///
/// It is meant to describe the potential effort to return a rental bike.
/// This value won't be displayed and used only inside the algorithm.
pub fn bss_return_penalty(mut self, penalty: f32) -> Self {
self.bicycle.bss_return_penalty = Some(penalty);
self
}
/// Changes the metric to quasi-shortest, i.e. **purely distance-based costing**.
///
/// Disables ALL other costings & penalties.
/// Also note, shortest will not disable hierarchy pruning, leading to potentially sub-optimal
/// routes for some costing models.
///
/// Default: `false`.
pub fn only_consider_quasi_shortest(mut self) -> Self {
self.bicycle.shortest = Some(true);
self
}
/// A penalty applied when transitioning between roads that do not have consistent naming–in
/// other words, no road names in common.
///
/// This penalty can be used to create simpler routes that tend to have fewer maneuvers or
/// narrative guidance instructions.
///
/// Default: `5` seconds
pub fn maneuver_penalty(mut self, penalty: f32) -> Self {
self.bicycle.maneuver_penalty = Some(penalty);
self
}
/// A cost applied when a gate with undefined or private access is encountered.
///
/// This cost is added to the estimated time / elapsed time.
///
/// Default: `30` seconds
pub fn gate_cost(mut self, cost: f32) -> Self {
self.bicycle.gate_cost = Some(cost);
self
}
/// A penalty applied when a gate with no access information is on the road.
///
/// Default: `300` seconds
pub fn gate_penalty(mut self, penalty: f32) -> Self {
self.bicycle.gate_penalty = Some(penalty);
self
}
/// A cost applied when encountering an international border.
///
/// This cost is added to the estimated and elapsed times.
///
/// Default: `600` seconds
pub fn country_crossing_cost(mut self, cost: f32) -> Self {
self.bicycle.country_crossing_cost = Some(cost);
self
}
/// A penalty applied for a country crossing.
///
/// This penalty can be used to create paths that avoid spanning country boundaries.
///
/// Default: `0`
pub fn country_crossing_penalty(mut self, penalty: f32) -> Self {
self.bicycle.country_crossing_penalty = Some(penalty);
self
}
/// A penalty applied for transition to generic service road.
///
/// Default: `0` for trucks and `15` for cars, buses, motor scooters and motorcycles.
pub fn service_penalty(mut self, penalty: f32) -> Self {
self.bicycle.service_penalty = Some(penalty);
self
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn serialisation() {
assert_eq!(
serde_json::to_value(BicycleCostingOptions::default()).unwrap(),
serde_json::json!({"bicycle":{}})
);
}
#[test]
fn builder_returns_default() {
assert_eq!(
BicycleCostingOptions::builder(),
BicycleCostingOptions::default()
);
}
#[test]
fn bicycle_type_sets_value() {
let opts = BicycleCostingOptions::builder().bicycle_type(BicycleType::Road);
assert_eq!(opts.bicycle.bicycle_type, Some(BicycleType::Road));
}
#[test]
fn cycling_speed_sets_value() {
let opts = BicycleCostingOptions::builder().cycling_speed(20.0);
assert_eq!(opts.bicycle.cycling_speed, Some(20.0));
}
#[test]
fn use_roads_sets_value() {
let opts = BicycleCostingOptions::builder().use_roads(0.5);
assert_eq!(opts.bicycle.use_roads, Some(0.5));
}
#[test]
fn use_hills_sets_value() {
let opts = BicycleCostingOptions::builder().use_hills(0.3);
assert_eq!(opts.bicycle.use_hills, Some(0.3));
}
#[test]
fn use_ferry_sets_value() {
let opts = BicycleCostingOptions::builder().use_ferry(0.8);
assert_eq!(opts.bicycle.use_ferry, Some(0.8));
}
#[test]
fn use_living_streets_sets_value() {
let opts = BicycleCostingOptions::builder().use_living_streets(0.6);
assert_eq!(opts.bicycle.use_living_streets, Some(0.6));
}
#[test]
fn avoid_bad_surfaces_sets_value() {
let opts = BicycleCostingOptions::builder().avoid_bad_surfaces(0.7);
assert_eq!(opts.bicycle.avoid_bad_surfaces, Some(0.7));
}
#[test]
fn bss_return_cost_sets_value() {
let opts = BicycleCostingOptions::builder().bss_return_cost(30.0);
assert_eq!(opts.bicycle.bss_return_cost, Some(30.0));
}
#[test]
fn bss_return_penalty_sets_value() {
let opts = BicycleCostingOptions::builder().bss_return_penalty(200.0);
assert_eq!(opts.bicycle.bss_return_penalty, Some(200.0));
}
#[test]
fn only_consider_quasi_shortest_sets_value() {
let opts = BicycleCostingOptions::builder().only_consider_quasi_shortest();
assert_eq!(opts.bicycle.shortest, Some(true));
}
#[test]
fn maneuver_penalty_sets_value() {
let opts = BicycleCostingOptions::builder().maneuver_penalty(15.0);
assert_eq!(opts.bicycle.maneuver_penalty, Some(15.0));
}
#[test]
fn gate_cost_sets_value() {
let opts = BicycleCostingOptions::builder().gate_cost(10.0);
assert_eq!(opts.bicycle.gate_cost, Some(10.0));
}
#[test]
fn gate_penalty_sets_value() {
let opts = BicycleCostingOptions::builder().gate_penalty(100.0);
assert_eq!(opts.bicycle.gate_penalty, Some(100.0));
}
#[test]
fn country_crossing_cost_sets_value() {
let opts = BicycleCostingOptions::builder().country_crossing_cost(300.0);
assert_eq!(opts.bicycle.country_crossing_cost, Some(300.0));
}
#[test]
fn country_crossing_penalty_sets_value() {
let opts = BicycleCostingOptions::builder().country_crossing_penalty(0.0);
assert_eq!(opts.bicycle.country_crossing_penalty, Some(0.0));
}
#[test]
fn service_penalty_sets_value() {
let opts = BicycleCostingOptions::builder().service_penalty(15.0);
assert_eq!(opts.bicycle.service_penalty, Some(15.0));
}
#[test]
fn chaining_works() {
let opts = BicycleCostingOptions::builder()
.cycling_speed(22.0)
.use_roads(0.6)
.use_hills(0.4);
assert_eq!(opts.bicycle.cycling_speed, Some(22.0));
assert_eq!(opts.bicycle.use_roads, Some(0.6));
assert_eq!(opts.bicycle.use_hills, Some(0.4));
}
}