#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
pub use jiff::Timestamp;
pub use waterkit_core::{Latitude, Longitude, OutOfRange};
mod sys;
pub use waterkit_permission::{Permission, PermissionStatus};
#[cfg(target_os = "android")]
pub mod android {
pub use crate::sys::android::get_location_with_context;
}
#[derive(Debug, Clone, PartialEq)]
pub struct Location {
latitude: Latitude,
longitude: Longitude,
altitude: Option<f64>,
horizontal_accuracy: Option<f64>,
vertical_accuracy: Option<f64>,
timestamp: Timestamp,
}
impl Location {
#[must_use]
pub const fn new(latitude: Latitude, longitude: Longitude, timestamp: Timestamp) -> Self {
Self {
latitude,
longitude,
altitude: None,
horizontal_accuracy: None,
vertical_accuracy: None,
timestamp,
}
}
pub fn from_degrees(
latitude: f64,
longitude: f64,
timestamp: Timestamp,
) -> Result<Self, LocationError> {
Ok(Self::new(
Latitude::new(latitude)?,
Longitude::new(longitude)?,
timestamp,
))
}
pub async fn get() -> Result<Self, LocationError> {
sys::get_location().await
}
#[must_use]
pub const fn with_altitude(mut self, altitude: f64) -> Self {
self.altitude = Some(altitude);
self
}
#[must_use]
pub const fn with_horizontal_accuracy(mut self, accuracy: f64) -> Self {
self.horizontal_accuracy = Some(accuracy);
self
}
#[must_use]
pub const fn with_vertical_accuracy(mut self, accuracy: f64) -> Self {
self.vertical_accuracy = Some(accuracy);
self
}
#[must_use]
pub const fn latitude(&self) -> Latitude {
self.latitude
}
#[must_use]
pub const fn longitude(&self) -> Longitude {
self.longitude
}
#[must_use]
pub const fn altitude(&self) -> Option<f64> {
self.altitude
}
#[must_use]
pub const fn horizontal_accuracy(&self) -> Option<f64> {
self.horizontal_accuracy
}
#[must_use]
pub const fn vertical_accuracy(&self) -> Option<f64> {
self.vertical_accuracy
}
#[must_use]
pub const fn timestamp(&self) -> Timestamp {
self.timestamp
}
}
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum LocationError {
#[error("location permission denied")]
PermissionDenied,
#[error("location services disabled")]
ServiceDisabled,
#[error("location request timed out")]
Timeout,
#[error("location not available")]
NotAvailable,
#[error("invalid location coordinate: {0}")]
InvalidCoordinate(#[from] OutOfRange),
#[error("platform error: {0}")]
Platform(String),
}
#[cfg(test)]
mod tests {
use super::{Latitude, Location, LocationError, Longitude, Timestamp};
#[test]
fn new_stores_validated_coordinates() {
let location = Location::new(
Latitude::new(35.0).expect("valid latitude"),
Longitude::new(139.0).expect("valid longitude"),
Timestamp::from_second(0).expect("valid timestamp"),
);
assert!((location.latitude().get() - 35.0).abs() < f64::EPSILON);
assert!((location.longitude().get() - 139.0).abs() < f64::EPSILON);
}
#[test]
fn from_degrees_rejects_invalid_coordinates() {
let err = Location::from_degrees(
91.0,
139.0,
Timestamp::from_second(0).expect("valid timestamp"),
)
.expect_err("latitude should be rejected");
assert!(matches!(err, LocationError::InvalidCoordinate(_)));
}
}