use std::io::{Error, ErrorKind};
use tokio::runtime::Runtime;
#[derive(Clone, Debug, Default)]
pub struct QueryEngine {
client: reqwest::Client,
base_url: String,
way_filters: Vec<String>,
}
impl QueryEngine {
pub fn new() -> Self {
Self {
client: reqwest::Client::new(),
base_url: "https://overpass-api.de/api/interpreter".to_string(),
way_filters: vec![
String::from("motorway"),
String::from("trunk"),
String::from("primary"),
String::from("secondary"),
String::from("tertiary"),
String::from("unclassified"),
String::from("residential"),
String::from("service")
].into_iter().collect()
}
}
pub fn url(&self) -> &str {
&self.base_url
}
pub fn with_url(&self, new_url: String) -> Self {
Self {
base_url: new_url,
..self.clone()
}
}
pub fn filters(&self) -> &Vec<String> {
&self.way_filters
}
pub fn with_filters(&self, new_filters: Vec<String>) -> Self {
Self {
way_filters: new_filters,
..self.clone()
}
}
pub async fn query_place(&self, area_name: String, admin_level: Option<usize>) -> Result<String, Error> {
let this_admin_level: String = match admin_level {
Some(num) => format!("[admin_level={num}]"),
None => "".to_string(),
};
let way_filter: String = match &self.way_filters.len() {
0 => "(area.searchArea)".to_string(),
_ => {
format!("[\"highway\"~\"{}\"](area.searchArea)",
&self.way_filters.join("|"))
}
};
self.query(format!(r#"
[out:json][timeout:25];
area[name="{area_name}"]{this_admin_level}->.searchArea;
//Find all ways according to filter
(
way{way_filter};
);
//Get nodes associated with ways defined before
(._; >;);
out body; >;
out skel qt;"#
)).await
}
pub fn query_place_blocking(&self, area_name: String, admin_level: Option<usize>) -> Result<String, Error> {
Runtime::new()?
.block_on(self.query_place(area_name, admin_level))
}
pub async fn query_poly(&self, polygon: Vec<(f64, f64)>) -> Result<String, Error> {
assert!(polygon[0] == polygon[polygon.len()-1], "Beginning and end of polygon must be the same point!");
let polyline_string: String = polygon
.iter()
.map(|(lat, lon)| format!("{lat} {lon}"))
.collect::<Vec<String>>()
.join(" ");
let way_filter: String = match &self.way_filters.len() {
0 => "".to_string(),
_ => {
format!("[\"highway\"~\"{}\"]",
&self.way_filters.join("|"))
}
};
self.query(format!(r#"
[out:json][timeout:25];
//Get the ways from the polygon
way{way_filter}(poly:"{polyline_string}");
//Get the nodes and anything else on the way
(._; >;);
out body; >;
out skel qt;"#
)).await
}
pub fn query_poly_blocking(&self, polygon: Vec<(f64, f64)>) -> Result<String, Error> {
Runtime::new()?
.block_on(self.query_poly(polygon))
}
pub async fn query(&self, query: String) -> Result<String, Error> {
let response = self.client
.post(&self.base_url)
.header("Content-Type", "application/x-www-form-urlencoded")
.body(format!("data={}", query))
.send()
.await
.map_err(|e| Error::new(ErrorKind::Other, e))?;
let status = response.status();
let content_type = response
.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.map(|s| s.to_owned())
.unwrap_or(String::from(""));
let body = response.text()
.await
.map_err(|e| Error::new(ErrorKind::Other, e))?;
if !status.is_success() || content_type.contains("text/html") || body.trim_start().starts_with('<') {
return Err(Error::new(ErrorKind::Other, body));
}
Ok(body)
}
pub fn query_blocking(&self, query: String) -> Result<String, Error> {
Runtime::new()?
.block_on(self.query(query))
}
}