oxiproj-core 0.1.2

Foundation types for OxiProj: coordinates, errors, ellipsoids, datums, and units.
Documentation
//! Named prime meridians ported from PROJ `src/datums.cpp` (`pj_prime_meridians`).

use crate::dms::dmstor;
use crate::error::ProjResult;

/// One prime-meridian definition (id, longitude definition string in DMS).
/// Ported from src/datums.cpp.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PrimeMeridianDef {
    /// Short prime-meridian identifier, e.g. `"greenwich"`, `"paris"`.
    pub id: &'static str,
    /// Longitude of the meridian relative to Greenwich, as a DMS string
    /// parseable by [`crate::dms::dmstor`].
    pub defn: &'static str,
}

/// The PROJ named prime-meridian table. Ported verbatim from src/datums.cpp.
pub static PRIME_MERIDIANS: &[PrimeMeridianDef] = &[
    PrimeMeridianDef {
        id: "greenwich",
        defn: "0dE",
    },
    PrimeMeridianDef {
        id: "lisbon",
        defn: "9d07'54.862\"W",
    },
    PrimeMeridianDef {
        id: "paris",
        defn: "2d20'14.025\"E",
    },
    PrimeMeridianDef {
        id: "bogota",
        defn: "74d04'51.3\"W",
    },
    PrimeMeridianDef {
        id: "madrid",
        defn: "3d41'16.58\"W",
    },
    PrimeMeridianDef {
        id: "rome",
        defn: "12d27'8.4\"E",
    },
    PrimeMeridianDef {
        id: "bern",
        defn: "7d26'22.5\"E",
    },
    PrimeMeridianDef {
        id: "jakarta",
        defn: "106d48'27.79\"E",
    },
    PrimeMeridianDef {
        id: "ferro",
        defn: "17d40'W",
    },
    PrimeMeridianDef {
        id: "brussels",
        defn: "4d22'4.71\"E",
    },
    PrimeMeridianDef {
        id: "stockholm",
        defn: "18d3'29.8\"E",
    },
    PrimeMeridianDef {
        id: "athens",
        defn: "23d42'58.815\"E",
    },
    PrimeMeridianDef {
        id: "oslo",
        defn: "10d43'22.5\"E",
    },
    PrimeMeridianDef {
        id: "copenhagen",
        defn: "12d34'40.35\"E",
    },
];

/// Look up a prime meridian by exact id. Ported from src/datums.cpp.
pub fn find_prime_meridian(id: &str) -> Option<&'static PrimeMeridianDef> {
    PRIME_MERIDIANS.iter().find(|p| p.id == id)
}

/// Returns the prime-meridian longitude offset in RADIANS by parsing its
/// definition string with `dmstor`. Ported from src/datums.cpp usage.
pub fn prime_meridian_offset(id: &str) -> ProjResult<f64> {
    match find_prime_meridian(id) {
        Some(pm) => dmstor(pm.defn),
        None => Err(crate::error::ProjError::IllegalArgValue),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::consts::DEG_TO_RAD;

    #[test]
    fn table_has_fourteen_entries() {
        assert_eq!(PRIME_MERIDIANS.len(), 14);
    }

    #[test]
    fn greenwich_is_zero() {
        assert_eq!(prime_meridian_offset("greenwich").expect("greenwich"), 0.0);
    }

    #[test]
    fn paris_offset_matches() {
        // 2d20'14.025" = 2 + 20/60 + 14.025/3600 = 2.3372291666... degrees.
        let expected = 2.337_229_166_666_667 * DEG_TO_RAD;
        assert!((prime_meridian_offset("paris").expect("paris") - expected).abs() < 1e-9);
    }

    #[test]
    fn ferro_is_west_negative() {
        // 17d40' = 17.6666666667 degrees, WEST -> negative.
        let expected = -17.666_666_666_666_67 * DEG_TO_RAD;
        assert!((prime_meridian_offset("ferro").expect("ferro") - expected).abs() < 1e-9);
    }

    #[test]
    fn unknown_id_not_found() {
        assert!(find_prime_meridian("nope").is_none());
    }

    #[test]
    fn unknown_offset_is_error() {
        assert!(prime_meridian_offset("nope").is_err());
    }

    #[test]
    fn lisbon_is_west_negative() {
        assert!(prime_meridian_offset("lisbon").expect("lisbon") < 0.0);
    }

    #[test]
    fn rome_is_east_positive() {
        assert!(prime_meridian_offset("rome").expect("rome") > 0.0);
    }
}