1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! This crate provides forward– and reverse-geocoding functionality for Rust.
//! Over time, a variety of providers will be added. Each provider may implement one or both
//! of the `Forward` and `Reverse` traits, which provide forward– and reverse-geocoding methods.
//!
//! Note that for the `reverse` method, the return type is simply `Option<String>`,
//! as this is the lowest common denominator reverse-geocoding result.
//! Individual providers may implement additional methods, which return more
//! finely-structured and/or extensive data, and enable more specific query tuning.
//! Coordinate data are specified using the [`Point`](struct.Point.html) struct, which has several
//! convenient `From` implementations to allow for easy construction using primitive types.
//!
//! ### A note on Coordinate Order
//! While individual providers may specify coordinates in either `[Longitude, Latitude]` **or**
//! `[Latitude, Longitude`] order,
//! `Geocoding` **always** requires [`Point`](struct.Point.html) data in `[Longitude, Latitude]` (`x, y`) order,
//! and returns data in that order.
//!
//! ### Usage of rustls
//!
//! If you like to use [rustls](https://github.com/ctz/rustls) instead of OpenSSL
//! you can enable the `rustls-tls` feature in your `Cargo.toml`:
//!
//!```toml
//![dependencies]
//!geocoding = { version = "*", default-features = false, features = ["rustls-tls"] }
//!```

static UA_STRING: &str = "Rust-Geocoding";

use chrono;
pub use geo_types::{Coordinate, Point};
use num_traits::Float;
use reqwest::blocking::Client;
use reqwest::header::ToStrError;
use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::num::ParseIntError;
use thiserror::Error;

// The OpenCage geocoding provider
pub mod opencage;
pub use crate::opencage::Opencage;

// The OpenStreetMap Nominatim geocoding provider
pub mod openstreetmap;
pub use crate::openstreetmap::Openstreetmap;

// The GeoAdmin geocoding provider
pub mod geoadmin;
pub use crate::geoadmin::GeoAdmin;

/// Errors that can occur during geocoding operations
#[derive(Error, Debug)]
pub enum GeocodingError {
    #[error("Forward geocoding failed")]
    Forward,
    #[error("Reverse geocoding failed")]
    Reverse,
    #[error("HTTP request error")]
    Request(#[from] reqwest::Error),
    #[error("Error converting headers to String")]
    HeaderConversion(#[from] ToStrError),
    #[error("Error converting int to String")]
    ParseInt(#[from] ParseIntError),
}

/// Reverse-geocode a coordinate.
///
/// This trait represents the most simple and minimal implementation
/// available from a given geocoding provider: some address formatted as Option<String>.
///
/// Examples
///
/// ```
/// use geocoding::{Opencage, Point, Reverse};
///
/// let p = Point::new(2.12870, 41.40139);
/// let oc = Opencage::new("dcdbf0d783374909b3debee728c7cc10".to_string());
/// let res = oc.reverse(&p).unwrap();
/// assert_eq!(
///     res,
///     Some("Carrer de Calatrava, 68, 08017 Barcelona, Spain".to_string())
/// );
/// ```
pub trait Reverse<T>
where
    T: Float + Debug,
{
    // NOTE TO IMPLEMENTERS: Point coordinates are lon, lat (x, y)
    // You may have to provide these coordinates in reverse order,
    // depending on the provider's requirements (see e.g. OpenCage)
    fn reverse(&self, point: &Point<T>) -> Result<Option<String>, GeocodingError>;
}

/// Forward-geocode a coordinate.
///
/// This trait represents the most simple and minimal implementation available
/// from a given geocoding provider: It returns a `Vec` of zero or more `Points`.
///
/// Examples
///
/// ```
/// use geocoding::{Coordinate, Forward, Opencage, Point};
///
/// let oc = Opencage::new("dcdbf0d783374909b3debee728c7cc10".to_string());
/// let address = "Schwabing, München";
/// let res: Vec<Point<f64>> = oc.forward(address).unwrap();
/// assert_eq!(
///     res,
///     vec![Point(Coordinate { x: 11.5884858, y: 48.1700887 })]
/// );
/// ```
pub trait Forward<T>
where
    T: Float + Debug,
{
    // NOTE TO IMPLEMENTERS: while returned provider point data may not be in
    // lon, lat (x, y) order, Geocoding requires this order in its output Point
    // data. Please pay attention when using returned data to construct Points
    fn forward(&self, address: &str) -> Result<Vec<Point<T>>, GeocodingError>;
}

/// Used to specify a bounding box to search within when forward-geocoding
///
/// - `minimum` refers to the **bottom-left** or **south-west** corner of the bounding box
/// - `maximum` refers to the **top-right** or **north-east** corner of the bounding box.
#[derive(Copy, Clone, Debug)]
pub struct InputBounds<T>
where
    T: Float + Debug,
{
    pub minimum_lonlat: Point<T>,
    pub maximum_lonlat: Point<T>,
}

impl<T> InputBounds<T>
where
    T: Float + Debug,
{
    /// Create a new `InputBounds` struct by passing 2 `Point`s defining:
    /// - minimum (bottom-left) longitude and latitude coordinates
    /// - maximum (top-right) longitude and latitude coordinates
    pub fn new<U>(minimum_lonlat: U, maximum_lonlat: U) -> InputBounds<T>
    where
        U: Into<Point<T>>,
    {
        InputBounds {
            minimum_lonlat: minimum_lonlat.into(),
            maximum_lonlat: maximum_lonlat.into(),
        }
    }
}

/// Convert borrowed input bounds into the correct String representation
impl<T> From<InputBounds<T>> for String
where
    T: Float + Debug,
{
    fn from(ip: InputBounds<T>) -> String {
        // Return in lon, lat order
        format!(
            "{},{},{},{}",
            ip.minimum_lonlat.x().to_f64().unwrap(),
            ip.minimum_lonlat.y().to_f64().unwrap(),
            ip.maximum_lonlat.x().to_f64().unwrap(),
            ip.maximum_lonlat.y().to_f64().unwrap()
        )
    }
}