lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

model.rs (5623B)


      1 //! Provider-owned locality result models.
      2 
      3 use crate::Error;
      4 
      5 /// A geographic point in decimal degrees.
      6 #[derive(Clone, Copy, Debug, PartialEq)]
      7 pub struct Point {
      8     latitude: f64,
      9     longitude: f64,
     10 }
     11 
     12 impl Point {
     13     /// Creates a finite point within the WGS84 latitude/longitude bounds.
     14     pub fn new(latitude: f64, longitude: f64) -> Result<Self, Error> {
     15         if !latitude.is_finite()
     16             || !longitude.is_finite()
     17             || !(-90.0..=90.0).contains(&latitude)
     18             || !(-180.0..=180.0).contains(&longitude)
     19         {
     20             return Err(Error::InvalidPoint);
     21         }
     22         Ok(Self {
     23             latitude,
     24             longitude,
     25         })
     26     }
     27 
     28     /// Returns the latitude in decimal degrees.
     29     #[must_use]
     30     pub const fn latitude(self) -> f64 {
     31         self.latitude
     32     }
     33 
     34     /// Returns the longitude in decimal degrees.
     35     #[must_use]
     36     pub const fn longitude(self) -> f64 {
     37         self.longitude
     38     }
     39 }
     40 
     41 /// One deterministic locality candidate returned by GeoNames.
     42 #[derive(Clone, Debug, PartialEq)]
     43 pub struct Candidate {
     44     feature_id: u64,
     45     name: String,
     46     admin1_id: Option<String>,
     47     admin1_name: Option<String>,
     48     country_id: String,
     49     country_name: Option<String>,
     50     point: Point,
     51     display_name: String,
     52 }
     53 
     54 impl Candidate {
     55     pub(crate) fn from_provider_row(
     56         feature_id: u64,
     57         name: String,
     58         admin1_id: Option<String>,
     59         admin1_name: Option<String>,
     60         country_id: String,
     61         country_name: Option<String>,
     62         point: Point,
     63     ) -> Self {
     64         let mut display_parts = vec![name.clone()];
     65         if let Some(admin1_name) = &admin1_name {
     66             display_parts.push(admin1_name.clone());
     67         }
     68         display_parts.push(country_name.clone().unwrap_or_else(|| country_id.clone()));
     69         Self {
     70             feature_id,
     71             name,
     72             admin1_id,
     73             admin1_name,
     74             country_id,
     75             country_name,
     76             point,
     77             display_name: display_parts.join(", "),
     78         }
     79     }
     80 
     81     /// Returns the stable GeoNames feature identifier.
     82     #[must_use]
     83     pub const fn feature_id(&self) -> u64 {
     84         self.feature_id
     85     }
     86 
     87     /// Returns the canonical locality name.
     88     #[must_use]
     89     pub fn name(&self) -> &str {
     90         &self.name
     91     }
     92 
     93     /// Returns the opaque first-level administrative identifier.
     94     #[must_use]
     95     pub fn admin1_id(&self) -> Option<&str> {
     96         self.admin1_id.as_deref()
     97     }
     98 
     99     /// Returns the first-level administrative name when present.
    100     #[must_use]
    101     pub fn admin1_name(&self) -> Option<&str> {
    102         self.admin1_name.as_deref()
    103     }
    104 
    105     /// Returns the ISO-like country identifier stored by the asset.
    106     #[must_use]
    107     pub fn country_id(&self) -> &str {
    108         &self.country_id
    109     }
    110 
    111     /// Returns the country name when present.
    112     #[must_use]
    113     pub fn country_name(&self) -> Option<&str> {
    114         self.country_name.as_deref()
    115     }
    116 
    117     /// Returns the candidate coordinate.
    118     #[must_use]
    119     pub const fn point(&self) -> Point {
    120         self.point
    121     }
    122 
    123     /// Returns the deterministic human-readable label.
    124     #[must_use]
    125     pub fn display_name(&self) -> &str {
    126         &self.display_name
    127     }
    128 }
    129 
    130 /// One country and its deterministic center point.
    131 #[derive(Clone, Debug, PartialEq)]
    132 pub struct Country {
    133     id: String,
    134     name: Option<String>,
    135     center: Point,
    136 }
    137 
    138 impl Country {
    139     pub(crate) fn from_provider_row(id: String, name: Option<String>, center: Point) -> Self {
    140         Self { id, name, center }
    141     }
    142 
    143     /// Returns the country identifier stored by the asset.
    144     #[must_use]
    145     pub fn id(&self) -> &str {
    146         &self.id
    147     }
    148 
    149     /// Returns the country name when present.
    150     #[must_use]
    151     pub fn name(&self) -> Option<&str> {
    152         self.name.as_deref()
    153     }
    154 
    155     /// Returns the average coordinate of the country's locality rows.
    156     #[must_use]
    157     pub const fn center(&self) -> Point {
    158         self.center
    159     }
    160 }
    161 
    162 #[cfg(test)]
    163 mod tests {
    164     use super::{Candidate, Point};
    165     use crate::Error;
    166 
    167     #[test]
    168     fn points_enforce_finite_geographic_bounds() {
    169         assert_eq!(
    170             Point::new(48.4284, -123.3656),
    171             Ok(Point {
    172                 latitude: 48.4284,
    173                 longitude: -123.3656,
    174             })
    175         );
    176         for (latitude, longitude) in [
    177             (f64::NAN, 0.0),
    178             (0.0, f64::INFINITY),
    179             (-90.1, 0.0),
    180             (90.1, 0.0),
    181             (0.0, -180.1),
    182             (0.0, 180.1),
    183         ] {
    184             assert_eq!(Point::new(latitude, longitude), Err(Error::InvalidPoint));
    185         }
    186     }
    187 
    188     #[test]
    189     fn candidates_expose_provider_values_without_public_fields() {
    190         let point = Point::new(48.4284, -123.3656).expect("valid point");
    191         let candidate = Candidate::from_provider_row(
    192             617_4041,
    193             "Victoria".to_owned(),
    194             Some("BC".to_owned()),
    195             Some("British Columbia".to_owned()),
    196             "CA".to_owned(),
    197             Some("Canada".to_owned()),
    198             point,
    199         );
    200         assert_eq!(candidate.feature_id(), 617_4041);
    201         assert_eq!(candidate.name(), "Victoria");
    202         assert_eq!(candidate.admin1_id(), Some("BC"));
    203         assert_eq!(candidate.admin1_name(), Some("British Columbia"));
    204         assert_eq!(candidate.country_id(), "CA");
    205         assert_eq!(candidate.country_name(), Some("Canada"));
    206         assert_eq!(candidate.point(), point);
    207         assert_eq!(
    208             candidate.display_name(),
    209             "Victoria, British Columbia, Canada"
    210         );
    211     }
    212 }