use crate::Deserialize;
use crate::GeocodingError;
use crate::InputBounds;
use crate::Point;
use crate::UA_STRING;
use crate::{Client, HeaderMap, HeaderValue, USER_AGENT};
use crate::{Forward, Reverse};
use num_traits::{Float, Pow};
use std::fmt::Debug;
pub struct GeoAdmin {
client: Client,
endpoint: String,
sr: String,
}
pub struct GeoAdminParams<'a, T>
where
T: Float + Debug,
{
searchtext: &'a str,
origins: &'a str,
bbox: Option<&'a InputBounds<T>>,
limit: Option<u8>,
}
impl<'a, T> GeoAdminParams<'a, T>
where
T: Float + Debug,
{
pub fn new(searchtext: &'a str) -> GeoAdminParams<'a, T> {
GeoAdminParams {
searchtext,
origins: "zipcode,gg25,district,kantone,gazetteer,address,parcel",
bbox: None,
limit: Some(50),
}
}
pub fn with_origins(&mut self, origins: &'a str) -> &mut Self {
self.origins = origins;
self
}
pub fn with_bbox(&mut self, bbox: &'a InputBounds<T>) -> &mut Self {
self.bbox = Some(bbox);
self
}
pub fn with_limit(&mut self, limit: u8) -> &mut Self {
self.limit = Some(limit);
self
}
pub fn build(&self) -> GeoAdminParams<'a, T> {
GeoAdminParams {
searchtext: self.searchtext,
origins: self.origins,
bbox: self.bbox,
limit: self.limit,
}
}
}
impl GeoAdmin {
pub fn new() -> Self {
GeoAdmin::default()
}
pub fn with_endpoint(mut self, endpoint: &str) -> Self {
self.endpoint = endpoint.to_owned();
self
}
pub fn with_sr(mut self, sr: &str) -> Self {
self.sr = sr.to_owned();
self
}
pub fn forward_full<T>(
&self,
params: &GeoAdminParams<T>,
) -> Result<GeoAdminForwardResponse<T>, GeocodingError>
where
T: Float + Debug,
for<'de> T: Deserialize<'de>,
{
let bbox;
let limit;
let mut query = vec![
("searchText", params.searchtext),
("type", "locations"),
("origins", params.origins),
("sr", &self.sr),
("geometryFormat", "geojson"),
];
if let Some(bb) = params.bbox.cloned().as_mut() {
if vec!["4326", "3857"].contains(&self.sr.as_str()) {
*bb = InputBounds::new(
wgs84_to_lv03(&bb.minimum_lonlat),
wgs84_to_lv03(&bb.maximum_lonlat),
);
}
bbox = String::from(*bb);
query.push(("bbox", &bbox));
}
if let Some(lim) = params.limit {
limit = lim.to_string();
query.push(("limit", &limit));
}
let resp = self
.client
.get(&format!("{}SearchServer", self.endpoint))
.query(&query)
.send()?
.error_for_status()?;
let res: GeoAdminForwardResponse<T> = resp.json()?;
Ok(res)
}
}
impl Default for GeoAdmin {
fn default() -> 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!");
GeoAdmin {
client,
endpoint: "https://api3.geo.admin.ch/rest/services/api/".to_string(),
sr: "4326".to_string(),
}
}
}
impl<T> Forward<T> for GeoAdmin
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!("{}SearchServer", self.endpoint))
.query(&[
("searchText", place),
("type", "locations"),
("origins", "address"),
("limit", "1"),
("sr", &self.sr),
("geometryFormat", "geojson"),
])
.send()?
.error_for_status()?;
let res: GeoAdminForwardResponse<T> = resp.json()?;
let results = if vec!["2056", "21781"].contains(&self.sr.as_str()) {
res.features
.iter()
.map(|feature| Point::new(feature.properties.y, feature.properties.x)) .collect()
} else {
res.features
.iter()
.map(|feature| Point::new(feature.properties.x, feature.properties.y)) .collect()
};
Ok(results)
}
}
impl<T> Reverse<T> for GeoAdmin
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!("{}MapServer/identify", self.endpoint))
.query(&[
(
"geometry",
format!(
"{},{}",
point.x().to_f64().unwrap(),
point.y().to_f64().unwrap()
)
.as_str(),
),
("geometryType", "esriGeometryPoint"),
("layers", "all:ch.bfs.gebaeude_wohnungs_register"),
("mapExtent", "0,0,100,100"),
("imageDisplay", "100,100,100"),
("tolerance", "50"),
("geometryFormat", "geojson"),
("sr", &self.sr),
("lang", "en"),
])
.send()?
.error_for_status()?;
let res: GeoAdminReverseResponse = resp.json()?;
if !res.results.is_empty() {
let properties = &res.results[0].properties;
let address = format!(
"{}, {} {}",
properties.strname_deinr, properties.dplz4, properties.dplzname
);
Ok(Some(address))
} else {
Ok(None)
}
}
}
fn wgs84_to_lv03<T>(p: &Point<T>) -> Point<T>
where
T: Float + Debug,
{
let lambda = (p.x().to_f64().unwrap() * 3600.0 - 26782.5) / 10000.0;
let phi = (p.y().to_f64().unwrap() * 3600.0 - 169028.66) / 10000.0;
let x = 2600072.37 + 211455.93 * lambda
- 10938.51 * lambda * phi
- 0.36 * lambda * phi.pow(2)
- 44.54 * lambda.pow(3);
let y = 1200147.07 + 308807.95 * phi + 3745.25 * lambda.pow(2) + 76.63 * phi.pow(2)
- 194.56 * lambda.pow(2) * phi
+ 119.79 * phi.pow(3);
Point::new(
T::from(x - 2000000.0).unwrap(),
T::from(y - 1000000.0).unwrap(),
)
}
#[derive(Debug, Deserialize)]
pub struct GeoAdminForwardResponse<T>
where
T: Float + Debug,
{
pub features: Vec<GeoAdminForwardLocation<T>>,
}
#[derive(Debug, Deserialize)]
pub struct GeoAdminForwardLocation<T>
where
T: Float + Debug,
{
id: Option<usize>,
pub properties: ForwardLocationProperties<T>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct ForwardLocationProperties<T> {
pub origin: String,
pub geom_quadindex: String,
pub weight: u32,
pub rank: u32,
pub detail: String,
pub lat: T,
pub lon: T,
pub num: Option<usize>,
pub x: T,
pub y: T,
pub label: String,
pub zoomlevel: u32,
}
#[derive(Debug, Deserialize)]
pub struct GeoAdminReverseResponse {
pub results: Vec<GeoAdminReverseLocation>,
}
#[derive(Debug, Deserialize)]
pub struct GeoAdminReverseLocation {
id: String,
#[serde(rename = "featureId")]
pub feature_id: String,
#[serde(rename = "layerBodId")]
pub layer_bod_id: String,
#[serde(rename = "layerName")]
pub layer_name: String,
pub properties: ReverseLocationAttributes,
}
#[derive(Clone, Debug, Deserialize)]
pub struct ReverseLocationAttributes {
pub egid: Option<String>,
pub ggdenr: u32,
pub ggdename: String,
pub gdekt: String,
pub edid: Option<String>,
pub egaid: u32,
pub deinr: Option<String>,
pub dplz4: u32,
pub dplzname: String,
pub egrid: Option<String>,
pub esid: u32,
pub strname: Vec<String>,
pub strsp: Vec<String>,
pub strname_deinr: String,
pub label: String,
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn new_with_sr_forward_test() {
let geoadmin = GeoAdmin::new().with_sr("2056");
let address = "Seftigenstrasse 264, 3084 Wabern";
let res = geoadmin.forward(&address);
assert_eq!(res.unwrap(), vec![Point::new(2_600_968.75, 1_197_427.0)]);
}
#[test]
fn new_with_endpoint_forward_test() {
let geoadmin =
GeoAdmin::new().with_endpoint("https://api3.geo.admin.ch/rest/services/api/");
let address = "Seftigenstrasse 264, 3084 Wabern";
let res = geoadmin.forward(&address);
assert_eq!(
res.unwrap(),
vec![Point::new(7.451352119445801, 46.92793655395508)]
);
}
#[test]
fn with_sr_forward_full_test() {
let geoadmin = GeoAdmin::new().with_sr("2056");
let bbox = InputBounds::new((2_600_967.75, 1_197_426.0), (2_600_969.75, 1_197_428.0));
let params = GeoAdminParams::new(&"Seftigenstrasse Bern")
.with_origins("address")
.with_bbox(&bbox)
.build();
let res: GeoAdminForwardResponse<f64> = geoadmin.forward_full(¶ms).unwrap();
let result = &res.features[0];
assert_eq!(
result.properties.label,
"Seftigenstrasse 264 <b>3084 Wabern</b>",
);
}
#[test]
fn forward_full_test() {
let geoadmin = GeoAdmin::new();
let bbox = InputBounds::new((7.4513398, 46.92792859), (7.4513662, 46.9279467));
let params = GeoAdminParams::new(&"Seftigenstrasse Bern")
.with_origins("address")
.with_bbox(&bbox)
.build();
let res: GeoAdminForwardResponse<f64> = geoadmin.forward_full(¶ms).unwrap();
let result = &res.features[0];
assert_eq!(
result.properties.label,
"Seftigenstrasse 264 <b>3084 Wabern</b>",
);
}
#[test]
fn forward_test() {
let geoadmin = GeoAdmin::new();
let address = "Seftigenstrasse 264, 3084 Wabern";
let res = geoadmin.forward(&address);
assert_eq!(
res.unwrap(),
vec![Point::new(7.451352119445801, 46.92793655395508)]
);
}
#[test]
fn with_sr_reverse_test() {
let geoadmin = GeoAdmin::new().with_sr("2056");
let p = Point::new(2_600_968.75, 1_197_427.0);
let res = geoadmin.reverse(&p);
assert_eq!(
res.unwrap(),
Some("Seftigenstrasse 264, 3084 Wabern".to_string()),
);
}
#[test]
fn reverse_test() {
let geoadmin = GeoAdmin::new();
let p = Point::new(7.451352119445801, 46.92793655395508);
let res = geoadmin.reverse(&p);
assert_eq!(
res.unwrap(),
Some("Seftigenstrasse 264, 3084 Wabern".to_string()),
);
}
}