use crate::i18n::{
js_error_from_business_code, js_error_from_platform_error, js_internal_error,
js_invalid_parameter_error,
};
use lingxia_platform::error::PlatformError;
use lingxia_platform::traits::location::{Location, LocationRequestConfig};
use lxapp::{LxApp, lx};
use rong::function::Optional;
use rong::{FromJSObj, IntoJSObj, JSContext, JSFunc, JSResult, RongJSError};
use serde_json::Value;
fn handle_location_error(code: u32) -> RongJSError {
js_error_from_business_code(code)
}
fn wgs84_to_gcj02(wgs_lat: f64, wgs_lng: f64) -> (f64, f64) {
const A: f64 = 6378245.0;
const EE: f64 = 0.006_693_421_622_965_943;
if out_of_china(wgs_lat, wgs_lng) {
return (wgs_lat, wgs_lng);
}
let mut d_lat = transform_lat(wgs_lng - 105.0, wgs_lat - 35.0);
let mut d_lng = transform_lng(wgs_lng - 105.0, wgs_lat - 35.0);
let rad_lat = wgs_lat / 180.0 * std::f64::consts::PI;
let mut magic = (1.0 - EE * rad_lat.sin() * rad_lat.sin()).sqrt();
magic = (A * (1.0 - EE)) / (magic * magic * magic);
d_lat = (d_lat * 180.0) / ((A * (1.0 - EE)) / (magic * magic * magic) * std::f64::consts::PI);
d_lng = (d_lng * 180.0) / (A / magic * rad_lat.cos() * std::f64::consts::PI);
(wgs_lat + d_lat, wgs_lng + d_lng)
}
fn out_of_china(lat: f64, lng: f64) -> bool {
!(72.004..=137.8347).contains(&lng) || !(0.8293..=55.8271).contains(&lat)
}
fn transform_lat(lng: f64, lat: f64) -> f64 {
let mut ret =
-100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * lng.abs().sqrt();
ret += (20.0 * (6.0 * lng * std::f64::consts::PI).sin()
+ 20.0 * (2.0 * lng * std::f64::consts::PI).sin())
* 2.0
/ 3.0;
ret += (20.0 * (lat * std::f64::consts::PI).sin()
+ 40.0 * ((lat / 3.0) * std::f64::consts::PI).sin())
* 2.0
/ 3.0;
ret += (160.0 * ((lat / 12.0) * std::f64::consts::PI).sin()
+ 320.0 * ((lat * std::f64::consts::PI) / 30.0).sin())
* 2.0
/ 3.0;
ret
}
fn transform_lng(lng: f64, lat: f64) -> f64 {
let mut ret =
300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * lng.abs().sqrt();
ret += (20.0 * (6.0 * lng * std::f64::consts::PI).sin()
+ 20.0 * (2.0 * lng * std::f64::consts::PI).sin())
* 2.0
/ 3.0;
ret += (20.0 * (lng * std::f64::consts::PI).sin()
+ 40.0 * ((lng / 3.0) * std::f64::consts::PI).sin())
* 2.0
/ 3.0;
ret += (150.0 * ((lng / 12.0) * std::f64::consts::PI).sin()
+ 300.0 * ((lng / 30.0) * std::f64::consts::PI).sin())
* 2.0
/ 3.0;
ret
}
#[derive(FromJSObj)]
struct JSLocationOptions {
#[rename = "type"]
coordinate_type: Option<String>,
altitude: Option<bool>,
#[rename = "isHighAccuracy"]
is_high_accuracy: Option<bool>,
#[rename = "highAccuracyExpireTime"]
high_accuracy_expire_time: Option<u32>,
}
#[derive(Debug, Clone, IntoJSObj)]
pub struct LocationObj {
latitude: f64,
longitude: f64,
speed: Option<f64>,
accuracy: Option<f64>,
altitude: Option<f64>,
#[rename = "verticalAccuracy"]
vertical_accuracy: Option<f64>,
#[rename = "horizontalAccuracy"]
horizontal_accuracy: Option<f64>,
}
fn parse_location_payload(data: &str) -> Result<LocationObj, RongJSError> {
let parsed: Value = serde_json::from_str(data)
.map_err(|e| js_internal_error(format!("getLocation invalid payload: {}", e)))?;
let latitude = parsed
.get("latitude")
.and_then(Value::as_f64)
.ok_or_else(|| js_internal_error("getLocation payload missing numeric `latitude`"))?;
let longitude = parsed
.get("longitude")
.and_then(Value::as_f64)
.ok_or_else(|| js_internal_error("getLocation payload missing numeric `longitude`"))?;
let speed = parsed.get("speed").and_then(Value::as_f64);
let accuracy = parsed.get("accuracy").and_then(Value::as_f64);
let altitude = parsed.get("altitude").and_then(Value::as_f64);
let vertical_accuracy = parsed.get("vertical_accuracy").and_then(Value::as_f64);
let horizontal_accuracy = parsed.get("horizontal_accuracy").and_then(Value::as_f64);
Ok(LocationObj {
latitude,
longitude,
speed,
accuracy,
altitude,
vertical_accuracy,
horizontal_accuracy,
})
}
async fn get_location(
ctx: JSContext,
options: Optional<JSLocationOptions>,
) -> JSResult<LocationObj> {
let lxapp = LxApp::from_ctx(&ctx)?;
let requested_type = options
.as_ref()
.and_then(|opts| opts.coordinate_type.as_deref())
.unwrap_or("wgs84");
if requested_type != "wgs84" && requested_type != "gcj02" {
return Err(js_invalid_parameter_error(format!(
"getLocation invalid type: {}",
requested_type
)));
}
let config = if let Some(opts) = options.as_ref() {
LocationRequestConfig {
is_high_accuracy: opts.is_high_accuracy.unwrap_or(false),
high_accuracy_expire_time: opts.high_accuracy_expire_time,
include_altitude: opts.altitude.unwrap_or(false),
}
} else {
LocationRequestConfig::default()
};
match lxapp.runtime.request_location(config).await {
Ok(data) => {
let mut location = parse_location_payload(&data)?;
if requested_type == "gcj02" {
let (gcj_lat, gcj_lng) = wgs84_to_gcj02(location.latitude, location.longitude);
location.latitude = gcj_lat;
location.longitude = gcj_lng;
}
Ok(location)
}
Err(PlatformError::BusinessError(code)) => Err(handle_location_error(code)),
Err(e) => Err(js_error_from_platform_error(&e)),
}
}
pub fn init(ctx: &JSContext) -> JSResult<()> {
let get_location_func = JSFunc::new(ctx, get_location)?;
lx::register_js_api(ctx, "getLocation", get_location_func)?;
Ok(())
}