use std::collections::BTreeMap;
use lora_store::{resolve_srid, srid_is_3d, LoraPoint, PointKeyFamily};
use crate::value::LoraValue;
pub(super) fn build_point_from_map(
map: &BTreeMap<String, LoraValue>,
) -> Result<Option<LoraPoint>, String> {
const KNOWN_KEYS: &[&str] = &[
"x",
"y",
"z",
"longitude",
"latitude",
"height",
"crs",
"srid",
];
for k in map.keys() {
if !KNOWN_KEYS.iter().any(|known| known.eq_ignore_ascii_case(k)) {
return Err(format!("CAST( AS POINT) got unknown key '{k}'"));
}
}
let x = take_numeric(map, "x")?;
let y = take_numeric(map, "y")?;
let z = take_numeric(map, "z")?;
let longitude = take_numeric(map, "longitude")?;
let latitude = take_numeric(map, "latitude")?;
let height = take_numeric(map, "height")?;
let crs = take_string(map, "crs")?;
let srid = take_integer(map, "srid")?;
if matches!(x, Some(None))
|| matches!(y, Some(None))
|| matches!(z, Some(None))
|| matches!(longitude, Some(None))
|| matches!(latitude, Some(None))
|| matches!(height, Some(None))
|| matches!(crs, Some(None))
|| matches!(srid, Some(None))
{
return Ok(None);
}
let x = x.and_then(|v| v);
let y = y.and_then(|v| v);
let z = z.and_then(|v| v);
let longitude = longitude.and_then(|v| v);
let latitude = latitude.and_then(|v| v);
let height = height.and_then(|v| v);
let crs = crs.and_then(|v| v);
let srid = srid.and_then(|v| v);
let has_cartesian = x.is_some() || y.is_some();
let has_geographic = longitude.is_some() || latitude.is_some();
if has_cartesian && has_geographic {
return Err(
"CAST( AS POINT) cannot mix cartesian (x/y) and geographic (longitude/latitude) keys"
.to_string(),
);
}
let (family, first, second) = if has_geographic {
(
PointKeyFamily::Geographic,
longitude.ok_or_else(|| "CAST( AS POINT) is missing longitude".to_string())?,
latitude.ok_or_else(|| "CAST( AS POINT) is missing latitude".to_string())?,
)
} else if has_cartesian {
(
PointKeyFamily::Cartesian,
x.ok_or_else(|| "CAST( AS POINT) is missing x".to_string())?,
y.ok_or_else(|| "CAST( AS POINT) is missing y".to_string())?,
)
} else {
return Err(
"CAST( AS POINT) requires coordinates — either {x, y} or {longitude, latitude}"
.to_string(),
);
};
let third = match (z, height) {
(Some(_), Some(_)) => {
return Err(
"CAST( AS POINT) cannot specify both 'z' and 'height' — they are aliases"
.to_string(),
);
}
(Some(v), None) | (None, Some(v)) => Some(v),
(None, None) => None,
};
let is_3d = third.is_some();
let final_srid = resolve_srid(crs.as_deref(), srid, family, is_3d)?;
let point = LoraPoint {
x: first,
y: second,
z: if srid_is_3d(final_srid) { third } else { None },
srid: final_srid,
};
Ok(Some(point))
}
fn take_numeric(
map: &BTreeMap<String, LoraValue>,
key: &str,
) -> Result<Option<Option<f64>>, String> {
match map.get(key) {
None => Ok(None),
Some(LoraValue::Null) => Ok(Some(None)),
Some(LoraValue::Int(v)) => Ok(Some(Some(*v as f64))),
Some(LoraValue::Float(v)) => Ok(Some(Some(*v))),
Some(other) => Err(format!(
"CAST( AS POINT) field '{key}' must be numeric, got {}",
crate::errors::value_kind(other)
)),
}
}
fn take_string(
map: &BTreeMap<String, LoraValue>,
key: &str,
) -> Result<Option<Option<String>>, String> {
match map.get(key) {
None => Ok(None),
Some(LoraValue::Null) => Ok(Some(None)),
Some(LoraValue::String(s)) => Ok(Some(Some(s.clone()))),
Some(other) => Err(format!(
"CAST( AS POINT) field '{key}' must be a string, got {}",
crate::errors::value_kind(other)
)),
}
}
fn take_integer(
map: &BTreeMap<String, LoraValue>,
key: &str,
) -> Result<Option<Option<i64>>, String> {
match map.get(key) {
None => Ok(None),
Some(LoraValue::Null) => Ok(Some(None)),
Some(LoraValue::Int(v)) => Ok(Some(Some(*v))),
Some(other) => Err(format!(
"CAST( AS POINT) field '{key}' must be an integer, got {}",
crate::errors::value_kind(other)
)),
}
}
pub(super) fn timezone_name_to_offset(name: &str) -> i32 {
match name {
"UTC" | "GMT" | "Z" => 0,
"Europe/London" => 0, "Europe/Amsterdam" | "Europe/Berlin" | "Europe/Paris" | "CET" => 3600, "Europe/Moscow" => 10800, "US/Eastern" | "America/New_York" | "EST" => -18000, "US/Central" | "America/Chicago" | "CST" => -21600, "US/Mountain" | "America/Denver" | "MST" => -25200, "US/Pacific" | "America/Los_Angeles" | "PST" => -28800, "Asia/Tokyo" | "JST" => 32400, "Asia/Shanghai" | "Asia/Hong_Kong" => 28800, _ => 0, }
}