1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3pub enum NetworkType {
4 Drive,
5 DriveService,
6 Walk,
7 Bike,
8 All,
9 AllPrivate,
10}
11
12#[derive(Debug)]
14pub enum OverpassError {
15 RequestError(reqwest::Error),
16 InvalidNetworkType,
17}
18
19impl std::fmt::Display for OverpassError {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 match self {
22 OverpassError::RequestError(err) => write!(f, "Request Error: {}", err),
23 OverpassError::InvalidNetworkType => write!(f, "Invalid Network Type"),
24 }
25 }
26}
27
28pub fn get_osm_filter(network_type: NetworkType) -> Result<&'static str, OverpassError> {
30 match network_type {
31 NetworkType::Drive => Ok(
32 "[\"highway\"][\"area\"!~\"yes\"][\"highway\"!~\"abandoned|bridleway|bus_guideway|construction|corridor|cycleway|elevator|escalator|footway|no|path|pedestrian|planned|platform|proposed|raceway|razed|service|steps|track\"][\"motor_vehicle\"!~\"no\"][\"motorcar\"!~\"no\"][\"service\"!~\"alley|driveway|emergency_access|parking|parking_aisle|private\"]"
33 ),
34 NetworkType::DriveService => Ok(
35 "[\"highway\"][\"area\"!~\"yes\"][\"highway\"!~\"abandoned|bridleway|bus_guideway|construction|corridor|cycleway|elevator|escalator|footway|no|path|pedestrian|planned|platform|proposed|raceway|razed|steps|track\"][\"motor_vehicle\"!~\"no\"][\"motorcar\"!~\"no\"][\"service\"!~\"emergency_access|parking|parking_aisle|private\"]"
36 ),
37 NetworkType::Walk => Ok(
38 "[\"highway\"][\"area\"!~\"yes\"][\"highway\"!~\"abandoned|bus_guideway|construction|corridor|elevator|escalator|motor|no|planned|platform|proposed|raceway|razed\"][\"foot\"!~\"no\"][\"service\"!~\"private\"]"
39 ),
40 NetworkType::Bike => Ok(
41 "[\"highway\"][\"area\"!~\"yes\"][\"highway\"!~\"abandoned|bus_guideway|construction|corridor|elevator|escalator|footway|motor|no|planned|platform|proposed|raceway|razed|steps\"][\"bicycle\"!~\"no\"][\"service\"!~\"private\"]"
42 ),
43 NetworkType::All => Ok(
44 "[\"highway\"][\"area\"!~\"yes\"][\"highway\"!~\"abandoned|construction|no|planned|platform|proposed|raceway|razed\"][\"service\"!~\"private\"]"
45 ),
46 NetworkType::AllPrivate => Ok(
47 "[\"highway\"][\"area\"!~\"yes\"][\"highway\"!~\"abandoned|construction|no|planned|platform|proposed|raceway|razed\"]"
48 ),
49 }
50}
51
52pub fn create_overpass_query(polygon_coord_str: &str, network_type: NetworkType) -> String {
54 let filter = get_osm_filter(network_type).unwrap_or("");
55 format!("[out:xml][timeout:50];(way{}({});>;);out;", filter, polygon_coord_str)
56}
57
58lazy_static::lazy_static! {
60 pub(crate) static ref CLIENT: reqwest::Client = reqwest::Client::builder()
61 .user_agent("layover_planner/0.1 (https://github.com/kyleloving/osm_graph)")
62 .build()
63 .expect("failed to build HTTP client");
64}
65
66pub async fn make_request(url: &str, query: &str) -> Result<String, reqwest::Error> {
68 let response = CLIENT
69 .post(url)
70 .form(&[("data", query)])
71 .send()
72 .await?;
73
74 if response.status().is_success() {
75 let response_text = response.text().await?;
76 Ok(response_text)
77 } else {
78 Err(response.error_for_status().unwrap_err())
79 }
80}
81
82pub fn bbox_from_point(lat: f64, lon: f64, dist: f64) -> String {
84 const EARTH_RADIUS_M: f64 = 6_371_009.0;
85
86 let delta_lat = (dist / EARTH_RADIUS_M) * (180.0 / std::f64::consts::PI);
88 let delta_lon = (dist / EARTH_RADIUS_M) * (180.0 / std::f64::consts::PI)
89 / (lat * std::f64::consts::PI / 180.0).cos();
90
91 let north = lat + delta_lat;
93 let south = lat - delta_lat;
94 let east = lon + delta_lon;
95 let west = lon - delta_lon;
96
97 format!("{},{},{},{}", south, west, north, east)
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_bbox_is_symmetric() {
107 let bbox = bbox_from_point(48.0, 11.0, 1000.0);
108 let parts: Vec<f64> = bbox.split(',').map(|s| s.parse().unwrap()).collect();
109 let (south, west, north, east) = (parts[0], parts[1], parts[2], parts[3]);
110 assert!((48.0 - south - (north - 48.0)).abs() < 1e-6);
111 assert!((11.0 - west - (east - 11.0)).abs() < 1e-6);
112 }
113
114 #[test]
115 fn test_bbox_larger_dist_gives_larger_box() {
116 let small = bbox_from_point(48.0, 11.0, 1_000.0);
117 let large = bbox_from_point(48.0, 11.0, 10_000.0);
118 let small_parts: Vec<f64> = small.split(',').map(|s| s.parse().unwrap()).collect();
119 let large_parts: Vec<f64> = large.split(',').map(|s| s.parse().unwrap()).collect();
120 assert!(large_parts[2] > small_parts[2]);
121 }
122}