use crate::error::{Error, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Location {
pub lat: f64,
pub lon: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub elevation: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uncertainty_meters: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
impl Location {
pub fn new(lat: f64, lon: f64) -> Self {
Self {
lat,
lon,
elevation: None,
uncertainty_meters: None,
name: None,
}
}
pub fn with_elevation(lat: f64, lon: f64, elevation: f64) -> Self {
Self {
lat,
lon,
elevation: Some(elevation),
uncertainty_meters: None,
name: None,
}
}
pub fn builder() -> LocationBuilder {
LocationBuilder::new()
}
pub fn is_valid(&self) -> bool {
self.lat >= -90.0 && self.lat <= 90.0 && self.lon >= -180.0 && self.lon <= 180.0
}
pub fn validate(&self) -> Result<()> {
if self.lat < -90.0 || self.lat > 90.0 {
return Err(Error::InvalidLatitude(self.lat));
}
if self.lon < -180.0 || self.lon > 180.0 {
return Err(Error::InvalidLongitude(self.lon));
}
Ok(())
}
pub fn as_tuple(&self) -> (f64, f64) {
(self.lat, self.lon)
}
pub fn to_geo_point(&self) -> geo_types::Point<f64> {
geo_types::Point::new(self.lon, self.lat)
}
pub fn from_geo_point(point: geo_types::Point<f64>) -> Self {
Self::new(point.y(), point.x())
}
}
impl Default for Location {
fn default() -> Self {
Self::new(0.0, 0.0)
}
}
impl From<(f64, f64)> for Location {
fn from((lat, lon): (f64, f64)) -> Self {
Self::new(lat, lon)
}
}
impl From<geo_types::Point<f64>> for Location {
fn from(point: geo_types::Point<f64>) -> Self {
Self::from_geo_point(point)
}
}
#[derive(Debug, Default)]
pub struct LocationBuilder {
lat: Option<f64>,
lon: Option<f64>,
elevation: Option<f64>,
uncertainty_meters: Option<f64>,
name: Option<String>,
}
impl LocationBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn lat(mut self, lat: f64) -> Self {
self.lat = Some(lat);
self
}
pub fn lon(mut self, lon: f64) -> Self {
self.lon = Some(lon);
self
}
pub fn coordinates(mut self, lat: f64, lon: f64) -> Self {
self.lat = Some(lat);
self.lon = Some(lon);
self
}
pub fn elevation(mut self, elevation: f64) -> Self {
self.elevation = Some(elevation);
self
}
pub fn uncertainty_meters(mut self, uncertainty: f64) -> Self {
self.uncertainty_meters = Some(uncertainty);
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn build(self) -> Result<Location> {
let lat = self.lat.ok_or(Error::MissingField("lat"))?;
let lon = self.lon.ok_or(Error::MissingField("lon"))?;
let location = Location {
lat,
lon,
elevation: self.elevation,
uncertainty_meters: self.uncertainty_meters,
name: self.name,
};
location.validate()?;
Ok(location)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_location_new() {
let loc = Location::new(40.7128, -74.0060);
assert_eq!(loc.lat, 40.7128);
assert_eq!(loc.lon, -74.0060);
assert!(loc.elevation.is_none());
assert!(loc.is_valid());
}
#[test]
fn test_location_with_elevation() {
let loc = Location::with_elevation(27.9881, 86.9250, 8848.86);
assert_eq!(loc.elevation, Some(8848.86));
}
#[test]
fn test_location_validation() {
let valid = Location::new(45.0, 90.0);
assert!(valid.is_valid());
assert!(valid.validate().is_ok());
let invalid_lat = Location::new(91.0, 0.0);
assert!(!invalid_lat.is_valid());
assert!(invalid_lat.validate().is_err());
let invalid_lon = Location::new(0.0, 181.0);
assert!(!invalid_lon.is_valid());
assert!(invalid_lon.validate().is_err());
}
#[test]
fn test_location_builder() {
let loc = Location::builder()
.coordinates(51.5074, -0.1278)
.elevation(11.0)
.uncertainty_meters(10.0)
.name("London")
.build()
.unwrap();
assert_eq!(loc.lat, 51.5074);
assert_eq!(loc.lon, -0.1278);
assert_eq!(loc.elevation, Some(11.0));
assert_eq!(loc.uncertainty_meters, Some(10.0));
assert_eq!(loc.name, Some("London".to_string()));
}
#[test]
fn test_location_builder_missing_fields() {
let result = Location::builder().lat(40.0).build();
assert!(result.is_err());
}
#[test]
fn test_location_from_tuple() {
let loc: Location = (40.7128, -74.0060).into();
assert_eq!(loc.lat, 40.7128);
assert_eq!(loc.lon, -74.0060);
}
#[test]
fn test_location_to_geo_point() {
let loc = Location::new(40.7128, -74.0060);
let point = loc.to_geo_point();
assert_eq!(point.x(), -74.0060);
assert_eq!(point.y(), 40.7128);
}
#[test]
fn test_location_serialization() {
let loc = Location::new(40.7128, -74.0060);
let json = serde_json::to_string(&loc).unwrap();
let parsed: Location = serde_json::from_str(&json).unwrap();
assert_eq!(loc, parsed);
}
}