1use crate::Deserialize;
21use crate::GeocodingError;
22use crate::InputBounds;
23use crate::Point;
24use crate::UA_STRING;
25use crate::{Client, HeaderMap, HeaderValue, USER_AGENT};
26use crate::{Forward, Reverse};
27use num_traits::{Float, Pow};
28use std::fmt::Debug;
29
30pub struct GeoAdmin {
32 client: Client,
33 endpoint: String,
34 sr: String,
35}
36
37pub struct GeoAdminParams<'a, T>
39where
40 T: Float + Debug,
41{
42 searchtext: &'a str,
43 origins: &'a str,
44 bbox: Option<&'a InputBounds<T>>,
45 limit: Option<u8>,
46}
47
48impl<'a, T> GeoAdminParams<'a, T>
49where
50 T: Float + Debug,
51{
52 pub fn new(searchtext: &'a str) -> GeoAdminParams<'a, T> {
69 GeoAdminParams {
70 searchtext,
71 origins: "zipcode,gg25,district,kantone,gazetteer,address,parcel",
72 bbox: None,
73 limit: Some(50),
74 }
75 }
76
77 pub fn with_origins(&mut self, origins: &'a str) -> &mut Self {
79 self.origins = origins;
80 self
81 }
82
83 pub fn with_bbox(&mut self, bbox: &'a InputBounds<T>) -> &mut Self {
85 self.bbox = Some(bbox);
86 self
87 }
88
89 pub fn with_limit(&mut self, limit: u8) -> &mut Self {
91 self.limit = Some(limit);
92 self
93 }
94
95 pub fn build(&self) -> GeoAdminParams<'a, T> {
97 GeoAdminParams {
98 searchtext: self.searchtext,
99 origins: self.origins,
100 bbox: self.bbox,
101 limit: self.limit,
102 }
103 }
104}
105
106impl GeoAdmin {
107 pub fn new() -> Self {
109 GeoAdmin::default()
110 }
111
112 pub fn with_endpoint(mut self, endpoint: &str) -> Self {
116 endpoint.clone_into(&mut self.endpoint);
117 self
118 }
119
120 pub fn with_sr(mut self, sr: &str) -> Self {
124 sr.clone_into(&mut self.sr);
125 self
126 }
127
128 pub async fn forward_full<T>(
163 &self,
164 params: &GeoAdminParams<'_, T>,
165 ) -> Result<GeoAdminForwardResponse<T>, GeocodingError>
166 where
167 T: Float + Debug,
168 for<'de> T: Deserialize<'de>,
169 {
170 let bbox;
172 let limit;
173
174 let mut query = vec![
175 ("searchText", params.searchtext),
176 ("type", "locations"),
177 ("origins", params.origins),
178 ("sr", &self.sr),
179 ("geometryFormat", "geojson"),
180 ];
181
182 if let Some(bb) = params.bbox.cloned().as_mut() {
183 if ["4326", "3857"].contains(&self.sr.as_str()) {
184 *bb = InputBounds::new(
185 wgs84_to_lv03(&bb.minimum_lonlat),
186 wgs84_to_lv03(&bb.maximum_lonlat),
187 );
188 }
189 bbox = String::from(*bb);
190 query.push(("bbox", &bbox));
191 }
192
193 if let Some(lim) = params.limit {
194 limit = lim.to_string();
195 query.push(("limit", &limit));
196 }
197
198 let resp = self
199 .client
200 .get(&format!("{}SearchServer", self.endpoint))
201 .query(&query)
202 .send()
203 .await?
204 .error_for_status()?;
205 let res: GeoAdminForwardResponse<T> = resp.json().await?;
206 Ok(res)
207 }
208}
209
210impl Default for GeoAdmin {
211 fn default() -> Self {
212 let mut headers = HeaderMap::new();
213 headers.insert(USER_AGENT, HeaderValue::from_static(UA_STRING));
214 let client = Client::builder()
215 .default_headers(headers)
216 .build()
217 .expect("Couldn't build a client!");
218 GeoAdmin {
219 client,
220 endpoint: "https://api3.geo.admin.ch/rest/services/api/".to_string(),
221 sr: "4326".to_string(),
222 }
223 }
224}
225
226impl<T> Forward<T> for GeoAdmin
227where
228 T: Float + Debug,
229 for<'de> T: Deserialize<'de>,
230{
231 async fn forward(&self, place: &str) -> Result<Vec<Point<T>>, GeocodingError> {
235 let resp = self
236 .client
237 .get(&format!("{}SearchServer", self.endpoint))
238 .query(&[
239 ("searchText", place),
240 ("type", "locations"),
241 ("origins", "address"),
242 ("limit", "1"),
243 ("sr", &self.sr),
244 ("geometryFormat", "geojson"),
245 ])
246 .send()
247 .await?
248 .error_for_status()?;
249 let res: GeoAdminForwardResponse<T> = resp.json().await?;
250 let results = if ["2056", "21781"].contains(&self.sr.as_str()) {
252 res.features
253 .iter()
254 .map(|feature| Point::new(feature.properties.y, feature.properties.x)) .collect()
256 } else {
257 res.features
258 .iter()
259 .map(|feature| Point::new(feature.properties.x, feature.properties.y)) .collect()
261 };
262 Ok(results)
263 }
264}
265
266impl<T> Reverse<T> for GeoAdmin
267where
268 T: Float + Debug,
269 for<'de> T: Deserialize<'de>,
270{
271 async fn reverse(&self, point: &Point<T>) -> Result<Option<String>, GeocodingError> {
276 let resp = self
277 .client
278 .get(&format!("{}MapServer/identify", self.endpoint))
279 .query(&[
280 (
281 "geometry",
282 format!(
283 "{},{}",
284 point.x().to_f64().unwrap(),
285 point.y().to_f64().unwrap()
286 )
287 .as_str(),
288 ),
289 ("geometryType", "esriGeometryPoint"),
290 ("layers", "all:ch.bfs.gebaeude_wohnungs_register"),
291 ("mapExtent", "0,0,100,100"),
292 ("imageDisplay", "100,100,100"),
293 ("tolerance", "50"),
294 ("geometryFormat", "geojson"),
295 ("sr", &self.sr),
296 ("lang", "en"),
297 ])
298 .send()
299 .await?
300 .error_for_status()?;
301 let res: GeoAdminReverseResponse = resp.json().await?;
302 if !res.results.is_empty() {
303 let properties = &res.results[0].properties;
304 let address = format!(
305 "{}, {} {}",
306 properties.strname_deinr, properties.dplz4, properties.dplzname
307 );
308 Ok(Some(address))
309 } else {
310 Ok(None)
311 }
312 }
313}
314
315fn wgs84_to_lv03<T>(p: &Point<T>) -> Point<T>
319where
320 T: Float + Debug,
321{
322 let lambda = (p.x().to_f64().unwrap() * 3600.0 - 26782.5) / 10000.0;
323 let phi = (p.y().to_f64().unwrap() * 3600.0 - 169028.66) / 10000.0;
324 let x = 2600072.37 + 211455.93 * lambda
325 - 10938.51 * lambda * phi
326 - 0.36 * lambda * phi.pow(2)
327 - 44.54 * lambda.pow(3);
328 let y = 1200147.07 + 308807.95 * phi + 3745.25 * lambda.pow(2) + 76.63 * phi.pow(2)
329 - 194.56 * lambda.pow(2) * phi
330 + 119.79 * phi.pow(3);
331 Point::new(
332 T::from(x - 2000000.0).unwrap(),
333 T::from(y - 1000000.0).unwrap(),
334 )
335}
336#[derive(Debug, Deserialize)]
365pub struct GeoAdminForwardResponse<T>
366where
367 T: Float + Debug,
368{
369 pub features: Vec<GeoAdminForwardLocation<T>>,
370}
371
372#[derive(Debug, Deserialize)]
374pub struct GeoAdminForwardLocation<T>
375where
376 T: Float + Debug,
377{
378 pub properties: ForwardLocationProperties<T>,
379}
380
381#[derive(Clone, Debug, Deserialize)]
383pub struct ForwardLocationProperties<T> {
384 pub origin: String,
385 pub geom_quadindex: String,
386 pub weight: u32,
387 pub rank: u32,
388 pub detail: String,
389 pub lat: T,
390 pub lon: T,
391 pub num: Option<usize>,
392 pub x: T,
393 pub y: T,
394 pub label: String,
395 pub zoomlevel: u32,
396}
397
398#[derive(Debug, Deserialize)]
419pub struct GeoAdminReverseResponse {
420 pub results: Vec<GeoAdminReverseLocation>,
421}
422
423#[derive(Debug, Deserialize)]
425pub struct GeoAdminReverseLocation {
426 #[serde(rename = "featureId")]
427 pub feature_id: String,
428 #[serde(rename = "layerBodId")]
429 pub layer_bod_id: String,
430 #[serde(rename = "layerName")]
431 pub layer_name: String,
432 pub properties: ReverseLocationAttributes,
433}
434
435#[derive(Clone, Debug, Deserialize)]
437pub struct ReverseLocationAttributes {
438 pub egid: Option<String>,
439 pub ggdenr: u32,
440 pub ggdename: String,
441 pub gdekt: String,
442 pub edid: Option<String>,
443 pub egaid: u32,
444 pub deinr: Option<String>,
445 pub dplz4: u32,
446 pub dplzname: String,
447 pub egrid: Option<String>,
448 pub esid: u32,
449 pub strname: Vec<String>,
450 pub strsp: Vec<String>,
451 pub strname_deinr: String,
452 pub label: String,
453}
454
455#[cfg(test)]
456mod test {
457 use super::*;
458
459 #[tokio::test]
460 async fn new_with_sr_forward_test() {
461 let geoadmin = GeoAdmin::new().with_sr("2056");
462 let address = "Seftigenstrasse 264, 3084 Wabern";
463 let res = geoadmin.forward(&address).await;
464 assert_eq!(res.unwrap(), vec![Point::new(2_600_968.75, 1_197_427.0)]);
465 }
466
467 #[tokio::test]
468 async fn new_with_endpoint_forward_test() {
469 let geoadmin =
470 GeoAdmin::new().with_endpoint("https://api3.geo.admin.ch/rest/services/api/");
471 let address = "Seftigenstrasse 264, 3084 Wabern";
472 let res = geoadmin.forward(&address).await;
473 assert_eq!(
474 res.unwrap(),
475 vec![Point::new(7.451352119445801, 46.92793655395508)]
476 );
477 }
478
479 #[tokio::test]
480 async fn with_sr_forward_full_test() {
481 let geoadmin = GeoAdmin::new().with_sr("2056");
482 let bbox = InputBounds::new((2_600_967.75, 1_197_426.0), (2_600_969.75, 1_197_428.0));
483 let params = GeoAdminParams::new(&"Seftigenstrasse Bern")
484 .with_origins("address")
485 .with_bbox(&bbox)
486 .build();
487 let res: GeoAdminForwardResponse<f64> = geoadmin.forward_full(¶ms).await.unwrap();
488 let result = &res.features[0];
489 assert_eq!(
490 result.properties.label,
491 "Seftigenstrasse 264 <b>3084 Wabern</b>",
492 );
493 }
494
495 #[tokio::test]
496 async fn forward_full_test() {
497 let geoadmin = GeoAdmin::new();
498 let bbox = InputBounds::new((7.4513398, 46.92792859), (7.4513662, 46.9279467));
499 let params = GeoAdminParams::new(&"Seftigenstrasse Bern")
500 .with_origins("address")
501 .with_bbox(&bbox)
502 .build();
503 let res: GeoAdminForwardResponse<f64> = geoadmin.forward_full(¶ms).await.unwrap();
504 let result = &res.features[0];
505 assert_eq!(
506 result.properties.label,
507 "Seftigenstrasse 264 <b>3084 Wabern</b>",
508 );
509 }
510
511 #[tokio::test]
512 async fn forward_test() {
513 let geoadmin = GeoAdmin::new();
514 let address = "Seftigenstrasse 264, 3084 Wabern";
515 let res = geoadmin.forward(&address).await;
516 assert_eq!(
517 res.unwrap(),
518 vec![Point::new(7.451352119445801, 46.92793655395508)]
519 );
520 }
521
522 #[tokio::test]
523 async fn with_sr_reverse_test() {
524 let geoadmin = GeoAdmin::new().with_sr("2056");
525 let p = Point::new(2_600_968.75, 1_197_427.0);
526 let res = geoadmin.reverse(&p).await;
527 assert_eq!(
528 res.unwrap(),
529 Some("Seftigenstrasse 264, 3084 Wabern".to_string()),
530 );
531 }
532
533 #[tokio::test]
534 #[ignore = "https://github.com/georust/geocoding/pull/45#issuecomment-1592395700"]
535 async fn reverse_test() {
536 let geoadmin = GeoAdmin::new();
537 let p = Point::new(7.451352119445801, 46.92793655395508);
538 let res = geoadmin.reverse(&p).await;
539 assert_eq!(
540 res.unwrap(),
541 Some("Seftigenstrasse 264, 3084 Wabern".to_string()),
542 );
543 }
544}