-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'data-provider/yandex' into main
- Loading branch information
Showing
6 changed files
with
186 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,11 @@ | ||
TELOXIDE_TOKEN=0123456789:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ||
GOOGLE_MAPS_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ||
YANDEX_MAPS_GEOCODER_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ||
YANDEX_MAPS_PLACES_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ||
|
||
RUST_LOG=info | ||
CACHE_TIME=3600 | ||
GAPI_MODE=GeoPlace | ||
YAPI_MODE=Place | ||
MSG_LOC_LIMIT=10 | ||
WEBHOOK_URL= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
use std::str::FromStr; | ||
use anyhow::anyhow; | ||
use async_trait::async_trait; | ||
use once_cell::sync::Lazy; | ||
use strum_macros::EnumString; | ||
use crate::loc::{Location, LocFinder, LocResult}; | ||
use crate::metrics; | ||
|
||
const GEOCODER_ENV_API_KEY: &str = "YANDEX_MAPS_GEOCODER_API_KEY"; | ||
const PLACES_ENV_API_KEY: &str = "YANDEX_MAPS_PLACES_API_KEY"; | ||
|
||
pub static YAPI_MODE: Lazy<YandexAPIMode> = Lazy::new(|| { | ||
let val = std::env::var("YAPI_MODE").expect("YAPI_MODE must be set!"); | ||
log::info!("YAPI_MODE is {val}"); | ||
YandexAPIMode::from_str(val.as_str()).expect("Invalid value of YAPI_MODE") | ||
}); | ||
|
||
#[derive(EnumString)] | ||
pub enum YandexAPIMode { | ||
Geocode, // HTTP Geocoder request | ||
Place, // Places API request | ||
GeoPlace, // Geocoder request first, Places if nothing was found | ||
} | ||
|
||
/// Load and check required parameters at startup | ||
pub fn preload_env_vars() { | ||
let _ = *YAPI_MODE; | ||
} | ||
|
||
pub struct YandexLocFinder { | ||
geocode_api_key: String, | ||
places_api_key: Option<String>, | ||
|
||
geocode_req_counter: prometheus::Counter, | ||
place_req_counter: prometheus::Counter, | ||
} | ||
|
||
impl YandexLocFinder { | ||
pub fn init(geocode_api_key: String, places_api_key: Option<String>) -> YandexLocFinder { | ||
let base_opts = prometheus::Opts::new("yandex_maps_api_requests_total", "count of requests to the Yandex Maps API"); | ||
let geocode_opts = base_opts.clone().const_label("API", "geocode"); | ||
let place_opts = base_opts.clone().const_label("API", "place"); | ||
|
||
YandexLocFinder { | ||
geocode_api_key, | ||
places_api_key, | ||
|
||
geocode_req_counter: metrics::REGISTRY.register_counter("Yandex Maps API (geocode) requests", geocode_opts), | ||
place_req_counter: metrics::REGISTRY.register_counter("Yandex Maps API (place) requests", place_opts), | ||
} | ||
} | ||
|
||
pub fn from_env() -> YandexLocFinder { | ||
let geocode_api_key = std::env::var(GEOCODER_ENV_API_KEY).expect("Yandex Maps Geocoder API key is required!"); | ||
let places_api_key = match *YAPI_MODE { | ||
YandexAPIMode::Place | YandexAPIMode::GeoPlace => { | ||
let api_key = std::env::var(PLACES_ENV_API_KEY).expect("Yandex Maps Places API key is required!"); | ||
Some(api_key) | ||
} | ||
YandexAPIMode::Geocode => None | ||
}; | ||
Self::init(geocode_api_key, places_api_key) | ||
} | ||
|
||
pub async fn find_geo_place(&self, address: &str, lang_code: &str) -> LocResult { | ||
let mut results = self.find_geo(address, lang_code).await?; | ||
if results.is_empty() { | ||
results = self.find_place(address, lang_code).await?; | ||
} | ||
Ok(results) | ||
} | ||
|
||
pub async fn find_geo(&self, address: &str, lang_code: &str) -> LocResult { | ||
self.geocode_req_counter.inc(); | ||
|
||
let url = format!("https://geocode-maps.yandex.ru/1.x?apikey={}&lang={}&geocode={}&format=json", | ||
self.geocode_api_key, lang_code, address); | ||
let resp = reqwest::get(url).await?.json::<serde_json::Value>().await?; | ||
|
||
log::info!("response from Yandex Maps Geocoder: {}", resp); | ||
|
||
let empty: Vec<serde_json::Value> = Vec::new(); | ||
let result = resp["response"]["GeoObjectCollection"]["featureMember"] | ||
.as_array() | ||
.unwrap_or(&empty) | ||
.iter() | ||
.filter_map(geocode_elem_mapper) | ||
.collect(); | ||
Ok(result) | ||
} | ||
|
||
pub async fn find_place(&self, address: &str, lang_code: &str) -> LocResult { | ||
self.place_req_counter.inc(); | ||
|
||
let api_key = self.places_api_key.clone() | ||
.ok_or(anyhow!("unexpected absence of a key for Yandex Maps Places API"))?; | ||
|
||
let url = format!("https://search-maps.yandex.ru/v1/?apikey={}&lang={}&text={}", | ||
api_key, lang_code, address); | ||
let resp = reqwest::get(url).await?.json::<serde_json::Value>().await?; | ||
|
||
log::info!("response from Yandex Maps Places API: {}", resp); | ||
|
||
let empty: Vec<serde_json::Value> = Vec::new(); | ||
let result = resp["features"] | ||
.as_array() | ||
.unwrap_or(&empty) | ||
.iter() | ||
.filter_map(places_elem_mapper) | ||
.collect(); | ||
Ok(result) | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl LocFinder for YandexLocFinder { | ||
async fn find(&self, query: &str, lang_code: &str) -> LocResult { | ||
match *YAPI_MODE { | ||
YandexAPIMode::Geocode => self.find_geo(query, lang_code).await, | ||
YandexAPIMode::Place => self.find_place(query, lang_code).await, | ||
YandexAPIMode::GeoPlace => self.find_geo_place(query, lang_code).await, | ||
} | ||
} | ||
} | ||
|
||
fn geocode_elem_mapper(v: &serde_json::Value) -> Option<Location> { | ||
let obj = &v["GeoObject"]; | ||
let metadata = &obj["metaDataProperty"]["GeocoderMetaData"]; | ||
let address = Some(metadata["text"].as_str()?.to_string()); | ||
|
||
let pos = &obj["Point"]["pos"].as_str()? | ||
.split(' ') | ||
.collect::<Vec<&str>>(); | ||
if pos.len() < 2 { | ||
log::error!("pos length < 2: {pos:?}"); | ||
return None | ||
} | ||
let longitude: f64 = pos[0].parse().ok()?; | ||
let latitude: f64 = pos[1].parse().ok()?; | ||
|
||
Some(Location { | ||
address, latitude, longitude | ||
}) | ||
} | ||
|
||
fn places_elem_mapper(v: &serde_json::Value) -> Option<Location> { | ||
let name = v["properties"]["name"].as_str()?; | ||
let description = v["properties"]["description"].as_str()?; | ||
let address = Some(format!("{}, {}", name, description)); | ||
|
||
let loc = &v["geometry"]["coordinates"]; | ||
let longitude: f64 = loc[0].as_f64()?; | ||
let latitude: f64 = loc[1].as_f64()?; | ||
|
||
Some(Location { | ||
address, latitude, longitude | ||
}) | ||
} |