geocoding/
lib.rs

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