pub use lazy_static::lazy_static;
pub use anyhow::Context as _;
pub use actix_web::{web, HttpRequest, HttpResponse, Responder};
pub use futures_util::stream::StreamExt;
pub use google_maps2::prelude::*;
pub use rust_decimal::prelude::ToPrimitive;
pub use serde_querystring_actix::QueryString;
pub use std::collections::HashMap;
pub use std::str::FromStr;
pub use crate::core::error2::Error2;
pub use crate::core::error2::Result;
use crate::core::http_clients::HttpClient;
pub use crate::core::request2::Render;
pub use crate::core::result::R;
pub use crate::server::AppContext;
#[rustfmt::skip]
lazy_static! {
static ref GOOGKE_MAP_APIKEY: String = crate::commons::read_env("RS_ENV_GOOGLE_MAP_APIKEY", "AIzaSyCqGlf2OtZuhRLwnwrtWsePlSFZ3S95mzg");
static ref GOOGLE_API_URL: &'static str = "https://maps.googleapis.com/maps/api";
static ref GOOGLE_MAP_CLIENT: GoogleMapsClient = GoogleMapsClient::try_new(GOOGKE_MAP_APIKEY.clone()).unwrap();
}
pub async fn auto_complete_search(
query: web::Query<HashMap<String, String>>,
request: HttpRequest,
) -> impl Responder {
let input_text = query.get("inputText").ok_or_else(|| {
crate::core::error2::Error::invalid_request("Missing query string parameter: address")
})?;
let mut components: Vec<Country> = vec![];
let _components = query.get("components");
if let Some(c) = _components {
if c.starts_with("country") {
let cc: Vec<&str> = c.split(':').collect();
if Country::from_str(cc[1]).is_err() {
return request.json(200, "");
}
components.push(Country::from_str("CN").unwrap());
}
}
match GOOGLE_MAP_CLIENT
.place_autocomplete(input_text)
.with_components(components)
.with_language(Language::Chinese)
.execute()
.await
{
Ok(location) => request.json(200, location),
Err(e) => {
let err_msg = e.to_string();
if err_msg.contains("non-existent") {
return request.json(200, "");
}
request.text(500, &e.to_string())
}
}
}
pub async fn place_details(
query: web::Query<HashMap<String, String>>,
request: HttpRequest,
) -> impl Responder {
let place_id = query.get("place_id").ok_or_else(|| {
crate::core::error2::Error::invalid_request("Missing query string parameter: place_id")
})?;
match GOOGLE_MAP_CLIENT
.place_details(place_id)
.with_language(Language::Chinese)
.execute()
.await
{
Ok(details) => request.json(200, details),
Err(e) => request.text(500, &e.to_string()),
}
}
pub async fn geocoding(
query: web::Query<HashMap<String, String>>,
request: HttpRequest,
) -> impl Responder {
let address = query.get("address").ok_or_else(|| {
crate::core::error2::Error::invalid_request("Missing query string parameter: address")
})?;
match GOOGLE_MAP_CLIENT
.geocoding()
.with_address(address)
.with_language(Language::Chinese)
.execute()
.await
{
Ok(location) => request.json(200, location),
Err(e) => request.text(500, &e.to_string()),
}
}
pub async fn reverse_geocoding(
query: web::Query<HashMap<String, String>>,
request: HttpRequest,
) -> impl Responder {
let latlng = query.get("latlng").ok_or_else(|| {
crate::core::error2::Error::invalid_request("Missing query string parameter: address")
})?;
let api_url = "https://maps.googleapis.com/maps/api/geocode/json?latlng=".to_owned()
+ latlng
+ "&key="
+ &GOOGKE_MAP_APIKEY;
let http_client = HttpClient::build(5);
match http_client.do_get(&api_url).await {
Ok(location) => request.text(200, &location),
Err(e) => {
let err_msg = e.to_string();
if err_msg.contains("non-existent") {
return request.json(200, "");
}
request.text(500, &err_msg)
}
}
}
pub async fn get_route_between_coordinates(
query: web::Query<HashMap<String, String>>,
request: HttpRequest,
) -> impl Responder {
let origin = query.get("origin").ok_or_else(|| {
crate::core::error2::Error::invalid_request("Missing query string parameter: origin")
})?;
let destination = query.get("destination").ok_or_else(|| {
crate::core::error2::Error::invalid_request("Missing query string parameter: destination")
})?;
let _travel_mode = query.get("travel_mode").ok_or_else(|| {
crate::core::error2::Error::invalid_request("Missing query string parameter: travel_mode")
})?;
let origin = LatLng::from_str(origin).unwrap();
let destination = LatLng::from_str(destination).unwrap();
match GOOGLE_MAP_CLIENT
.directions(
Location::try_from_f64(
origin.lat().to_f64().unwrap(),
origin.lng().to_f64().unwrap(),
)
.unwrap(),
Location::try_from_f64(
destination.lat().to_f64().unwrap(),
destination.lng().to_f64().unwrap(),
)
.unwrap(),
)
.with_travel_mode(TravelMode::Driving)
.with_language(Language::Chinese)
.execute()
.await
{
Ok(directions) => request.json(200, directions),
Err(e) => request.text(500, &e.to_string()),
}
}
#[rustfmt::skip]
pub async fn lookup_address_photo(
query: web::Query<HashMap<String, String>>,
_request: HttpRequest,
) -> crate::core::error2::Result<HttpResponse> {
let latlng = query.get("latlng").ok_or_else(|| {
crate::core::error2::Error::invalid_request("Missing query string parameter: address")
})?;
let f = || {
let _byte_content = std::fs::read("./RichMedias/images/gallery.png").unwrap();
let stream = futures_util::stream::iter(vec![_byte_content])
.map(|chunk| Ok::<_, actix_web::Error>(web::Bytes::from(chunk)))
.boxed();
HttpResponse::Ok()
.content_type("image/jpeg")
.streaming(stream)
};
let _key = format!("__lookup_address_photo_{}", latlng);
let string_array = match _lookup_input_address(latlng).await {
Ok(r) => r,
Err(_e) => {
return Ok(f());
}
};
if let Some(bytes) = crate::core::cacheable::get1h_bytes(&_key) {
return Ok(HttpResponse::Ok().content_type("image/png").body(bytes));
}
let mut index = string_array.len();
let mut photo_reference = crate::core::cacheable::get3600s(&_key).unwrap_or_default();
if photo_reference.is_empty() {
while index > 0 {
let input_address = &string_array[0..index].join("");
let url = format!(
"{}/place/findplacefromtext/json?input={}&inputtype=textquery&fields=photos&key={}",
*GOOGLE_API_URL, input_address, *GOOGKE_MAP_APIKEY
);
let response = reqwest::Client::builder()
.use_rustls_tls()
.danger_accept_invalid_certs(true)
.timeout(std::time::Duration::from_secs(5))
.build()
.unwrap()
.get(&url)
.send()
.await?;
let o = response.error_for_status()?;
let parsed_json: serde_json::Value = serde_json::from_str(&o.text().await?)?;
photo_reference = _photo_reference(input_address, &parsed_json);
if !photo_reference.is_empty() {
crate::core::cacheable::put3600s(&_key, &photo_reference);
break;
}
index -= 1;
}
}
if photo_reference.is_empty() {
return Ok(f());
}
let url = format!(
"{}/place/photo?maxwidth=400&photo_reference={}&key={}",
*GOOGLE_API_URL, photo_reference, *GOOGKE_MAP_APIKEY
);
let response = reqwest::Client::builder()
.use_rustls_tls()
.danger_accept_invalid_certs(true)
.timeout(std::time::Duration::from_secs(5))
.build()
.unwrap()
.get(&url)
.send()
.await?;
match response.bytes().await {
Ok(bytes) => {
crate::core::cacheable::put1h_bytes(&_key, bytes.clone());
Ok(HttpResponse::Ok().content_type("image/png").body(bytes))
}
Err(_e) => {
Ok(f())
}
}
}
async fn _lookup_input_address(latlng: &str) -> Result<Vec<String>> {
let location: GeocodingResponse = GOOGLE_MAP_CLIENT
.reverse_geocoding(
LatLng::from_str(latlng)
.map_err(|e| crate::core::error2::Error::invalid_request(e.to_string()))?,
)
.with_result_type(PlaceType::StreetAddress)
.with_language(Language::Chinese)
.execute()
.await
.map_err(crate::core::error2::Error::run_time)?;
let mut string_array = Vec::<String>::new();
let mut country_name: Option<String> = None;
let mut area_name: Option<String> = None;
let mut locality_name: Option<String> = None;
let mut sublocality_name: Option<String> = None;
let mut neighborhood_name: Option<String> = None;
let mut route_name: Option<String> = None;
for item in location.results[0].address_components.iter() {
let types = &item.types;
let type_string: Vec<String> = types.iter().map(|e| e.to_string()).collect();
let type_string = type_string.join(",");
if type_string.contains("country") {
country_name = Some(item.long_name.to_owned());
} else if type_string.contains("administrative") {
area_name = Some(item.long_name.to_owned());
} else if type_string.contains("locality") {
locality_name = Some(item.long_name.to_owned());
} else if type_string.contains("sublocality") {
sublocality_name = Some(item.long_name.to_owned());
} else if type_string.contains("neighborhood") {
neighborhood_name = Some(item.long_name.to_owned());
} else if type_string.contains("route") {
route_name = Some(item.long_name.to_owned());
}
}
if let Some(l) = country_name {
string_array.push(l);
}
if let Some(l) = area_name {
string_array.push(l);
}
if let Some(l) = locality_name {
string_array.push(l);
}
if let Some(l) = sublocality_name {
string_array.push(l);
}
if let Some(l) = neighborhood_name {
string_array.push(l);
}
if let Some(l) = route_name {
string_array.push(l);
}
Ok(string_array)
}
#[rustfmt::skip]
fn _photo_reference(_input_address: &str, parsed_json: &serde_json::Value) -> String {
let mut photo_reference = String::new();
if let serde_json::Value::Object(map) = parsed_json {
if let Some(serde_json::Value::Array(candidates)) = map.get("candidates") {
if let Some(serde_json::Value::Object(o)) = candidates.first() {
if let Some(serde_json::Value::Array(photos)) = o.get("photos") {
if let Some(serde_json::Value::Object(photo)) = photos.first() {
if let Some(serde_json::Value::String(photo)) = photo.get("photo_reference") {
photo_reference = photo.to_string();
}
}
}
}
}
}
photo_reference
}