use crate::GeocodingError;
use crate::InputBounds;
use crate::Point;
use crate::UA_STRING;
use crate::{Client, HeaderMap, HeaderValue, USER_AGENT};
use crate::{Deserialize, Serialize};
use crate::{Forward, Reverse};
use num_traits::Float;
use std::fmt::Debug;
pub struct Openstreetmap {
client: Client,
endpoint: String,
}
pub struct OpenstreetmapParams<'a, T>
where
T: Float + Debug,
{
query: &'a str,
addressdetails: bool,
viewbox: Option<&'a InputBounds<T>>,
}
impl<'a, T> OpenstreetmapParams<'a, T>
where
T: Float + Debug,
{
pub fn new(query: &'a str) -> OpenstreetmapParams<'a, T> {
OpenstreetmapParams {
query,
addressdetails: false,
viewbox: None,
}
}
pub fn with_addressdetails(&mut self, addressdetails: bool) -> &mut Self {
self.addressdetails = addressdetails;
self
}
pub fn with_viewbox(&mut self, viewbox: &'a InputBounds<T>) -> &mut Self {
self.viewbox = Some(viewbox);
self
}
pub fn build(&self) -> OpenstreetmapParams<'a, T> {
OpenstreetmapParams {
query: self.query,
addressdetails: self.addressdetails,
viewbox: self.viewbox,
}
}
}
impl Openstreetmap {
pub fn new() -> Self {
Openstreetmap::new_with_endpoint("https://nominatim.openstreetmap.org/".to_string())
}
pub fn new_with_endpoint(endpoint: String) -> Self {
let mut headers = HeaderMap::new();
headers.insert(USER_AGENT, HeaderValue::from_static(UA_STRING));
let client = Client::builder()
.default_headers(headers)
.build()
.expect("Couldn't build a client!");
Openstreetmap { client, endpoint }
}
pub fn forward_full<T>(
&self,
params: &OpenstreetmapParams<T>,
) -> Result<OpenstreetmapResponse<T>, GeocodingError>
where
T: Float + Debug,
for<'de> T: Deserialize<'de>,
{
let format = String::from("geojson");
let addressdetails = String::from(if params.addressdetails { "1" } else { "0" });
let viewbox;
let mut query = vec![
(&"q", params.query),
(&"format", &format),
(&"addressdetails", &addressdetails),
];
if let Some(vb) = params.viewbox {
viewbox = String::from(*vb);
query.push((&"viewbox", &viewbox));
}
let resp = self
.client
.get(&format!("{}search", self.endpoint))
.query(&query)
.send()?
.error_for_status()?;
let res: OpenstreetmapResponse<T> = resp.json()?;
Ok(res)
}
}
impl Default for Openstreetmap {
fn default() -> Self {
Self::new()
}
}
impl<T> Forward<T> for Openstreetmap
where
T: Float + Debug,
for<'de> T: Deserialize<'de>,
{
fn forward(&self, place: &str) -> Result<Vec<Point<T>>, GeocodingError> {
let resp = self
.client
.get(&format!("{}search", self.endpoint))
.query(&[(&"q", place), (&"format", &String::from("geojson"))])
.send()?
.error_for_status()?;
let res: OpenstreetmapResponse<T> = resp.json()?;
Ok(res
.features
.iter()
.map(|res| Point::new(res.geometry.coordinates.0, res.geometry.coordinates.1))
.collect())
}
}
impl<T> Reverse<T> for Openstreetmap
where
T: Float + Debug,
for<'de> T: Deserialize<'de>,
{
fn reverse(&self, point: &Point<T>) -> Result<Option<String>, GeocodingError> {
let resp = self
.client
.get(&format!("{}reverse", self.endpoint))
.query(&[
(&"lon", &point.x().to_f64().unwrap().to_string()),
(&"lat", &point.y().to_f64().unwrap().to_string()),
(&"format", &String::from("geojson")),
])
.send()?
.error_for_status()?;
let res: OpenstreetmapResponse<T> = resp.json()?;
let address = &res.features[0];
Ok(Some(address.properties.display_name.to_string()))
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct OpenstreetmapResponse<T>
where
T: Float + Debug,
{
pub r#type: String,
pub licence: String,
pub features: Vec<OpenstreetmapResult<T>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct OpenstreetmapResult<T>
where
T: Float + Debug,
{
pub r#type: String,
pub properties: ResultProperties,
pub bbox: (T, T, T, T),
pub geometry: ResultGeometry<T>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ResultProperties {
pub place_id: u64,
pub osm_type: String,
pub osm_id: u64,
pub display_name: String,
pub place_rank: u64,
pub category: String,
pub r#type: String,
pub importance: f64,
pub address: Option<AddressDetails>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AddressDetails {
pub city: Option<String>,
pub city_district: Option<String>,
pub construction: Option<String>,
pub continent: Option<String>,
pub country: Option<String>,
pub country_code: Option<String>,
pub house_number: Option<String>,
pub neighbourhood: Option<String>,
pub postcode: Option<String>,
pub public_building: Option<String>,
pub state: Option<String>,
pub suburb: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ResultGeometry<T>
where
T: Float + Debug,
{
pub r#type: String,
pub coordinates: (T, T),
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn new_with_endpoint_forward_test() {
let osm =
Openstreetmap::new_with_endpoint("https://nominatim.openstreetmap.org/".to_string());
let address = "Schwabing, München";
let res = osm.forward(&address);
assert_eq!(res.unwrap(), vec![Point::new(11.5884858, 48.1700887)]);
}
#[test]
fn forward_full_test() {
let osm = Openstreetmap::new();
let viewbox = InputBounds::new(
(-0.13806939125061035, 51.51989264641164),
(-0.13427138328552246, 51.52319711775629),
);
let params = OpenstreetmapParams::new(&"UCL CASA")
.with_addressdetails(true)
.with_viewbox(&viewbox)
.build();
let res: OpenstreetmapResponse<f64> = osm.forward_full(¶ms).unwrap();
let result = res.features[0].properties.clone();
assert!(result.display_name.contains("Gordon Square"));
assert_eq!(result.address.unwrap().city.unwrap(), "London");
}
#[test]
fn forward_test() {
let osm = Openstreetmap::new();
let address = "Schwabing, München";
let res = osm.forward(&address);
assert_eq!(res.unwrap(), vec![Point::new(11.5884858, 48.1700887)]);
}
#[test]
fn reverse_test() {
let osm = Openstreetmap::new();
let p = Point::new(2.12870, 41.40139);
let res = osm.reverse(&p);
assert!(res
.unwrap()
.unwrap()
.contains("Barcelona, Barcelonès, Barcelona, Catalunya"));
}
}