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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
use common::CultureCode;
use client::Client;
use error::Error;
use response::Response;
use serde_qs as qs;
use std::collections::HashMap;

// TODO: Maybe use a GeoJson crate here
#[derive(Debug, Deserialize)]
pub struct Point {
    // pub type: String // <-- Always Point for Location
    pub coordinates: Vec<f64>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy)]
pub enum EntityType {
    Address,
    Neighborhood,
    PopulatedPlace,
    Postcode1,
    AdminDivision1,
    AdminDivision2,
    CountryRegion,

    Lake, // missing in MSDN documentation, but exists in the wild
    River, // missing in MSDN documentation, but exists in the wild
    Postcode2, // missing in MSDN documentation, but exists in the wild
}

#[derive(Debug, Deserialize)]
pub struct Address {
    #[serde(rename = "addressLine")]
    pub address_line: Option<String>,
    #[serde(rename = "neighborhood")]
    pub neighborhood: Option<String>,
    #[serde(rename = "locality")]
    pub locality: Option<String>,
    #[serde(rename = "postalCode")]
    pub postal_code: Option<String>,
    #[serde(rename = "adminDistrict")]
    pub admin_district1: Option<String>,
    #[serde(rename = "adminDistrict2")]
    pub admin_district2: Option<String>,
    #[serde(rename = "countryRegion")]
    pub country: Option<String>,
    #[serde(rename = "countryRegionIso2")]
    pub country_iso: Option<String>,
    #[serde(rename = "landmark")]
    pub landmark: Option<String>,
    #[serde(rename = "formattedAddress")]
    pub formatted: Option<String>,
}

#[derive(Debug, Deserialize, PartialEq, Clone, Copy)]
pub enum Confidence {
    High,
    Medium,
    Low,
}

#[derive(Debug, Deserialize, PartialEq, Clone, Copy)]
pub enum MatchCode {
    Good,
    Ambiguous,
    UpHierarchy,
}

#[derive(Default, Serialize)]
pub struct FindPoint {
    pub point: String,
    pub include_entity_types: Vec<EntityType>,
    pub include_neighborhood: bool,
    pub include_ciso2: bool,
}
impl FindPoint {
    pub fn from_latlng(lat: f64, lng: f64) -> FindPoint {
        let mut params = FindPoint::default();
        params.point = format!("{:.5},{:.5}", lat, lng);
        params
    }
    pub fn from_str(latlng: &str) -> FindPoint {
        let mut params = FindPoint::default();
        params.point = latlng.to_owned();
        params
    }
}

#[derive(Default, Serialize)]
pub struct ContextParams {
    pub culture: Option<CultureCode>,
    pub user_map_view: Option<Vec<f64>>,
    pub user_location: Option<Vec<f64>>,
    pub user_ip: Option<String>,
    pub user_region: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct Location {
    pub name: String,
    pub point: Point,
    /// A geographic area that contains the location.
    /// The box is defined by [South Latitude, West Longitude, North Latitude, East Longitude].
    pub bbox: Vec<f64>,
    #[serde(rename = "entityType")]
    pub entity_type: EntityType,
    pub address: Address,
    pub confidence: Confidence,
    #[serde(rename = "matchCodes")]
    pub match_codes: Vec<MatchCode>,
}

impl Location {
    /// Gets the location information associated with latitude and longitude coordinates.
    pub fn find_by_point(client: &Client, find: FindPoint, opts: Option<ContextParams>) -> Result<Vec<Location>, Error> {
        let path = format!("/Locations/{}", find.point);

        // Build optional params
        let entity_types: String;
        let culture: String;
        let user_map_view: String;
        let user_location: String;
        let mut params = HashMap::<&str, &str>::new();
        if find.include_entity_types.len() > 0 {
            let types: Vec<String> = find.include_entity_types.iter().map(|el| format!("{:?}", el)).collect();
            entity_types = types.join(",");
            params.insert("include_entity_types", &entity_types);
        }
        if find.include_neighborhood {
            params.insert("inclnb", "1");
        }
        if find.include_ciso2 {
            params.insert("incl", "ciso2");
        }
        if let Some(ref ctx) = opts {
            if let Some(ref c) = ctx.culture {
                culture = qs::to_string(&c).map_err(|err| Error::from(err))?;
                params.insert("c", &culture);
            }
            if let Some(ref umv) = ctx.user_map_view {
                user_map_view = umv.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(",");
                params.insert("umv", &user_map_view);
            }
            if let Some(ref ul) = ctx.user_location {
                user_location = ul.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(",");
                params.insert("ul", &user_location);
            }
            if let Some(ref uip) = ctx.user_ip { params.insert("uip", &uip); }
            if let Some(ref ur) = ctx.user_region { params.insert("ur", &ur); }
        }

        // Make request and process response
        let response: Response<Location> = client.get(&path, &mut params)?;
        let resource_set = response.resource_sets.into_iter().next();
        if let Some(set) = resource_set {
            Ok(set.resources)
        } else {
            Ok(Vec::new())
        }
    }

    /// Gets latitude and longitude coordinates that correspond to location information provided as a query string.
    pub fn find_by_query(client: &Client, query: &str, opts: Option<ContextParams>) -> Result<Vec<Location>, Error> {
        let culture: String;
        let user_map_view: String;
        let user_location: String;
        let mut params = HashMap::new();
        params.insert("q", query);
        if let Some(ref ctx) = opts {
            if let Some(ref c) = ctx.culture {
                culture = qs::to_string(&c).map_err(|err| Error::from(err))?;
                params.insert("c", &culture);
            }
            if let Some(ref umv) = ctx.user_map_view {
                user_map_view = umv.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(",");
                params.insert("umv", &user_map_view);
            }
            if let Some(ref ul) = ctx.user_location {
                user_location = ul.iter().map(|n| n.to_string()).collect::<Vec<String>>().join(",");
                params.insert("ul", &user_location);
            }
            if let Some(ref uip) = ctx.user_ip { params.insert("uip", &uip); }
            if let Some(ref ur) = ctx.user_region { params.insert("ur", &ur); }
        }

        // Make request and process response
        let response: Response<Location> = client.get("/Locations", &mut params)?;
        let resource_set = response.resource_sets.into_iter().next();
        if let Some(set) = resource_set {
            Ok(set.resources)
        } else {
            Ok(Vec::new())
        }
    }
}