use oxiproj_projections::ProjParamLookup;
use oxiproj_transformations::TransParamLookup;
#[derive(Debug, Clone)]
pub struct ParamList {
pub entries: Vec<(String, Option<String>)>,
}
pub fn parse(s: &str) -> ParamList {
let mut entries: Vec<(String, Option<String>)> = Vec::new();
for raw in s.split_ascii_whitespace() {
let tok = raw.strip_prefix('+').unwrap_or(raw);
if tok.is_empty() {
continue;
}
match tok.split_once('=') {
Some((key, value)) => entries.push((key.to_string(), Some(value.to_string()))),
None => entries.push((tok.to_string(), None)),
}
}
ParamList { entries }
}
impl ParamList {
pub fn exists(&self, key: &str) -> bool {
self.entries.iter().any(|(k, _)| k == key)
}
pub fn get_str(&self, key: &str) -> Option<&str> {
for (k, v) in &self.entries {
if k == key {
return match v {
Some(val) => Some(val.as_str()),
None => Some(""),
};
}
}
None
}
pub fn get_f64(&self, key: &str) -> Option<f64> {
for (k, v) in &self.entries {
if k == key {
let val = v.as_ref()?;
if let Ok(parsed) = val.trim().parse::<f64>() {
return Some(parsed);
}
return oxiproj_core::parse_leading_f64(val).map(|(x, _)| x);
}
}
None
}
pub fn get_dms(&self, key: &str) -> Option<f64> {
for (k, v) in &self.entries {
if k == key {
let val = v.as_ref()?;
return oxiproj_core::dmstor(val).ok();
}
}
None
}
pub fn get_int(&self, key: &str) -> Option<i64> {
for (k, v) in &self.entries {
if k == key {
let val = v.as_ref()?;
return val.trim().parse::<i64>().ok();
}
}
None
}
pub fn get_bool(&self, key: &str) -> bool {
for (k, v) in &self.entries {
if k == key {
return match v {
None => true,
Some(val) => !matches!(val.as_str(), "f" | "F" | "false"),
};
}
}
false
}
}
pub struct ParamView<'a>(pub &'a ParamList);
impl ProjParamLookup for ParamView<'_> {
fn get_dms(&self, key: &str) -> Option<f64> {
self.0.get_dms(key)
}
fn get_f64(&self, key: &str) -> Option<f64> {
self.0.get_f64(key)
}
fn get_int(&self, key: &str) -> Option<i64> {
self.0.get_int(key)
}
fn get_str(&self, key: &str) -> Option<&str> {
self.0.get_str(key)
}
fn get_bool(&self, key: &str) -> bool {
self.0.get_bool(key)
}
fn exists(&self, key: &str) -> bool {
self.0.exists(key)
}
}
impl TransParamLookup for ParamView<'_> {
fn get_dms(&self, key: &str) -> Option<f64> {
self.0.get_dms(key)
}
fn get_f64(&self, key: &str) -> Option<f64> {
self.0.get_f64(key)
}
fn get_int(&self, key: &str) -> Option<i64> {
self.0.get_int(key)
}
fn get_str(&self, key: &str) -> Option<&str> {
self.0.get_str(key)
}
fn get_bool(&self, key: &str) -> bool {
self.0.get_bool(key)
}
fn exists(&self, key: &str) -> bool {
self.0.exists(key)
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxiproj_core::DEG_TO_RAD;
#[test]
fn parses_basic_merc() {
let pl = parse("+proj=merc +lat_ts=45 +ellps=WGS84");
assert_eq!(pl.entries.len(), 3);
assert_eq!(pl.get_str("proj"), Some("merc"));
let lat_ts = pl.get_dms("lat_ts").unwrap();
assert!((lat_ts - 45.0 * DEG_TO_RAD).abs() < 1e-12);
}
#[test]
fn bare_flag_is_true() {
let pl = parse("+south");
assert_eq!(pl.entries.len(), 1);
assert_eq!(pl.entries[0], ("south".to_string(), None));
assert!(pl.get_bool("south"));
assert!(!pl.get_bool("missing"));
}
#[test]
fn parses_int() {
let pl = parse("+zone=32");
assert_eq!(pl.get_int("zone"), Some(32));
}
#[test]
fn parses_pipeline_tokens() {
let pl = parse("+proj=pipeline +step +proj=utm +zone=32 +step +inv");
let e = &pl.entries;
assert!(e.contains(&("proj".to_string(), Some("pipeline".to_string()))));
assert!(e.contains(&("step".to_string(), None)));
assert!(e.contains(&("proj".to_string(), Some("utm".to_string()))));
assert!(e.contains(&("zone".to_string(), Some("32".to_string()))));
assert!(e.contains(&("inv".to_string(), None)));
assert_eq!(
e.iter().filter(|(k, v)| k == "step" && v.is_none()).count(),
2
);
}
#[test]
fn lon_0_dms() {
let pl = parse("+lon_0=9");
let v = pl.get_dms("lon_0").unwrap();
assert!((v - 9.0 * DEG_TO_RAD).abs() < 1e-12);
assert!((v - 0.157079632679).abs() < 1e-9);
}
#[test]
fn false_values_are_false() {
let pl = parse("+over=false +foo=f +bar=t");
assert!(!pl.get_bool("over"));
assert!(!pl.get_bool("foo"));
assert!(pl.get_bool("bar"));
}
}