geocoding_async/
lib.rs

1#![allow(async_fn_in_trait)]
2
3//! This crate provides forward– and reverse-geocoding functionality for Rust.
4//! Over time, a variety of providers will be added. Each provider may implement one or both
5//! of the `Forward` and `Reverse` traits, which provide forward– and reverse-geocoding methods.
6//!
7//! Note that for the `reverse` method, the return type is simply `Option<String>`,
8//! as this is the lowest common denominator reverse-geocoding result.
9//! Individual providers may implement additional methods, which return more
10//! finely-structured and/or extensive data, and enable more specific query tuning.
11//! Coordinate data are specified using the [`Point`](struct.Point.html) struct, which has several
12//! convenient `From` implementations to allow for easy construction using primitive types.
13//!
14//! ### A note on Coordinate Order
15//! While individual providers may specify coordinates in either `[Longitude, Latitude]` **or**
16//! `[Latitude, Longitude`] order,
17//! `Geocoding` **always** requires [`Point`](struct.Point.html) data in `[Longitude, Latitude]` (`x, y`) order,
18//! and returns data in that order.
19//!
20//! ### Usage of rustls
21//!
22//! If you like to use [rustls](https://github.com/ctz/rustls) instead of OpenSSL
23//! you can enable the `rustls-tls` feature in your `Cargo.toml`:
24//!
25//!```toml
26//![dependencies]
27//!geocoding = { version = "*", default-features = false, features = ["rustls-tls"] }
28//!```
29
30static UA_STRING: &str = "Rust-Geocoding";
31
32pub use geo_types::{Coord, Point};
33use num_traits::Float;
34use reqwest::header::ToStrError;
35use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
36use reqwest::Client;
37use serde::de::DeserializeOwned;
38use serde::{Deserialize, Serialize};
39use std::fmt::Debug;
40use std::num::ParseIntError;
41use thiserror::Error;
42
43// The OpenCage geocoding provider
44pub mod opencage;
45pub use crate::opencage::Opencage;
46
47// The OpenStreetMap Nominatim geocoding provider
48pub mod openstreetmap;
49pub use crate::openstreetmap::Openstreetmap;
50
51// The GeoAdmin geocoding provider
52pub mod geoadmin;
53pub use crate::geoadmin::GeoAdmin;
54
55/// Errors that can occur during geocoding operations
56#[derive(Error, Debug)]
57pub enum GeocodingError {
58    #[error("Forward geocoding failed")]
59    Forward,
60    #[error("Reverse geocoding failed")]
61    Reverse,
62    #[error("HTTP request error")]
63    Request(#[from] reqwest::Error),
64    #[error("Error converting headers to String")]
65    HeaderConversion(#[from] ToStrError),
66    #[error("Error converting int to String")]
67    ParseInt(#[from] ParseIntError),
68}
69
70/// Reverse-geocode a coordinate.
71///
72/// This trait represents the most simple and minimal implementation
73/// available from a given geocoding provider: some address formatted as Option<String>.
74///
75/// Examples
76///
77/// ```
78/// # tokio_test::block_on(async {
79/// use geocoding_async::{Opencage, Point, Reverse};
80///
81/// let p = Point::new(2.12870, 41.40139);
82/// let oc = Opencage::new("dcdbf0d783374909b3debee728c7cc10".to_string());
83/// let res = oc.reverse(&p).await.unwrap();
84/// assert_eq!(
85///     res,
86///     Some("Carrer de Calatrava, 68, 08017 Barcelona, Spain".to_string())
87/// );
88/// # });
89/// ```
90pub trait Reverse<T>
91where
92    T: Float + Debug,
93{
94    // NOTE TO IMPLEMENTERS: Point coordinates are lon, lat (x, y)
95    // You may have to provide these coordinates in reverse order,
96    // depending on the provider's requirements (see e.g. OpenCage)
97    async fn reverse(&self, point: &Point<T>) -> Result<Option<String>, GeocodingError>;
98}
99
100/// Forward-geocode a coordinate.
101///
102/// This trait represents the most simple and minimal implementation available
103/// from a given geocoding provider: It returns a `Vec` of zero or more `Points`.
104///
105/// Examples
106///
107/// ```
108/// # tokio_test::block_on(async {
109/// use geocoding_async::{Coord, Forward, Opencage, Point};
110///
111/// let oc = Opencage::new("dcdbf0d783374909b3debee728c7cc10".to_string());
112/// let address = "Schwabing, München";
113/// let res: Vec<Point<f64>> = oc.forward(address).await.unwrap();
114/// assert_eq!(
115///     res,
116///     vec![Point(Coord { x: 11.5884858, y: 48.1700887 })]
117/// );
118/// # });
119/// ```
120pub trait Forward<T>
121where
122    T: Float + Debug,
123{
124    // NOTE TO IMPLEMENTERS: while returned provider point data may not be in
125    // lon, lat (x, y) order, Geocoding requires this order in its output Point
126    // data. Please pay attention when using returned data to construct Points
127    async fn forward(&self, address: &str) -> Result<Vec<Point<T>>, GeocodingError>;
128}
129
130/// Used to specify a bounding box to search within when forward-geocoding
131///
132/// - `minimum` refers to the **bottom-left** or **south-west** corner of the bounding box
133/// - `maximum` refers to the **top-right** or **north-east** corner of the bounding box.
134#[derive(Copy, Clone, Debug)]
135pub struct InputBounds<T>
136where
137    T: Float + Debug,
138{
139    pub minimum_lonlat: Point<T>,
140    pub maximum_lonlat: Point<T>,
141}
142
143impl<T> InputBounds<T>
144where
145    T: Float + Debug,
146{
147    /// Create a new `InputBounds` struct by passing 2 `Point`s defining:
148    /// - minimum (bottom-left) longitude and latitude coordinates
149    /// - maximum (top-right) longitude and latitude coordinates
150    pub fn new<U>(minimum_lonlat: U, maximum_lonlat: U) -> InputBounds<T>
151    where
152        U: Into<Point<T>>,
153    {
154        InputBounds {
155            minimum_lonlat: minimum_lonlat.into(),
156            maximum_lonlat: maximum_lonlat.into(),
157        }
158    }
159}
160
161/// Convert borrowed input bounds into the correct String representation
162impl<T> From<InputBounds<T>> for String
163where
164    T: Float + Debug,
165{
166    fn from(ip: InputBounds<T>) -> String {
167        // Return in lon, lat order
168        format!(
169            "{},{},{},{}",
170            ip.minimum_lonlat.x().to_f64().unwrap(),
171            ip.minimum_lonlat.y().to_f64().unwrap(),
172            ip.maximum_lonlat.x().to_f64().unwrap(),
173            ip.maximum_lonlat.y().to_f64().unwrap()
174        )
175    }
176}