use reqwest::{Client, Error};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug)]
pub struct Config<'a> {
pub url: &'a str,
pub timeout: u8,
pub key: &'a str,
pub val: &'a str,
}
#[derive(Debug, Clone)]
pub struct BoundingBox {
pub xmin: f64,
pub ymin: f64,
pub xmax: f64,
pub ymax: f64,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct OSMMetaData {
pub timestamp_osm_base: String,
pub copyright: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct OverpassResponse {
pub version: f64,
pub generator: String,
pub osm3s: OSMMetaData,
pub elements: Vec<Node>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Node {
pub id: u64,
pub lat: f64,
pub lon: f64,
pub tags: HashMap<String, String>,
}
const WGS84A: f64 = 6378137.0;
const WGS84B: f64 = 6356752.3;
fn wgs84_earth_radius(lat: f64) -> f64 {
let an = WGS84A * WGS84A * lat.cos();
let bn = WGS84B * WGS84B * lat.sin();
let ad = WGS84A * lat.cos();
let bd = WGS84B * lat.sin();
((an * an + bn * bn) / (ad * ad + bd * bd)).sqrt()
}
impl<'a> BoundingBox {
pub fn from_point(lat: f64, lon: f64, dkm: f64) -> Self {
let dm = dkm * 1000.0;
let erad = wgs84_earth_radius(lat);
let prad = erad * lat.cos();
let dx = dm / prad;
let dy = dm / erad;
Self {
xmin: lon - dx,
ymin: lat - dy,
xmax: lon + dx,
ymax: lat + dy,
}
}
pub async fn search(&self, config: &'a Config<'a>) -> Result<OverpassResponse, Error> {
let query = format!(
"[out:json];node[\"{}\"=\"{}\"]({},{},{},{});out center;",
config.key, config.val, self.xmin, self.ymin, self.xmax, self.ymax
);
let client = Client::new();
let resp: OverpassResponse = client
.post(config.url)
.body(query)
.send()
.await?
.json::<OverpassResponse>()
.await?;
Ok(resp)
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio;
#[tokio::test]
async fn test_bounding_box() {
let c: Config = Config {
url: "https://overpass-api.de/api/interpreter",
timeout: 25,
key: "amenity",
val: "cafe",
};
let b: BoundingBox = BoundingBox {
x_min: 51.305219521963295,
y_min: -0.7690429687500001,
x_max: 51.82219818336938,
y_max: 0.5273437500000064,
};
let resp = b.search(&c).await.unwrap();
dbg!(resp);
}
#[test]
fn test_bounding_box_from_center() {
let bbox = BoundingBox::from_center(42.361145, -71.057083, 10.0);
println!(
"({}, {}, {}, {})",
bbox.x_min, bbox.y_min, bbox.x_max, bbox.y_max
);
}
}