forked from paul/sinoptik
104 lines
3.2 KiB
Rust
104 lines
3.2 KiB
Rust
|
//! Positions in the geographic coordinate system.
|
||
|
//!
|
||
|
//! This module contains everything related to geographic coordinate system functionality.
|
||
|
|
||
|
use std::hash::Hash;
|
||
|
|
||
|
use cached::proc_macro::cached;
|
||
|
use geocoding::{Forward, Openstreetmap, Point};
|
||
|
use rocket::tokio;
|
||
|
|
||
|
/// A (geocoded) position.
|
||
|
///
|
||
|
/// This is used for measuring and communication positions directly on the Earth as latitude and
|
||
|
/// longitude.
|
||
|
///
|
||
|
/// # Position equivalence and hashing
|
||
|
///
|
||
|
/// For caching purposes we need to check equivalence between two positions. If the positions match
|
||
|
/// up to the 5th decimal, we consider them the same (see [`Position::lat_as_i32`] and
|
||
|
/// [`Position::lon_as_i32`]).
|
||
|
#[derive(Clone, Copy, Debug, Default)]
|
||
|
pub(crate) struct Position {
|
||
|
pub(crate) lat: f64,
|
||
|
pub(crate) lon: f64,
|
||
|
}
|
||
|
|
||
|
impl Position {
|
||
|
/// Creates a new (geocoded) position.
|
||
|
pub(crate) fn new(lat: f64, lon: f64) -> Self {
|
||
|
Self { lat, lon }
|
||
|
}
|
||
|
|
||
|
/// Returns the latitude as an integer.
|
||
|
///
|
||
|
/// This is achieved by multiplying it by `10_000` and rounding it. Thus, this gives a
|
||
|
/// precision of 5 decimals.
|
||
|
fn lat_as_i32(&self) -> i32 {
|
||
|
(self.lat * 10_000.0).round() as i32
|
||
|
}
|
||
|
|
||
|
/// Returns the longitude as an integer.
|
||
|
///
|
||
|
/// This is achieved by multiplying it by `10_000` and rounding it. Thus, this gives a
|
||
|
/// precision of 5 decimals.
|
||
|
fn lon_as_i32(&self) -> i32 {
|
||
|
(self.lon * 10_000.0).round() as i32
|
||
|
}
|
||
|
|
||
|
/// Returns the latitude as a string with the given precision.
|
||
|
pub(crate) fn lat_as_str(&self, precision: usize) -> String {
|
||
|
format!("{:.*}", precision, self.lat)
|
||
|
}
|
||
|
|
||
|
/// Returns the longitude as a string with the given precision.
|
||
|
pub(crate) fn lon_as_str(&self, precision: usize) -> String {
|
||
|
format!("{:.*}", precision, self.lon)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl From<&Point<f64>> for Position {
|
||
|
fn from(point: &Point<f64>) -> Self {
|
||
|
// The `geocoding` API always returns (longitude, latitude) as (x, y).
|
||
|
Position::new(point.y(), point.x())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Hash for Position {
|
||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||
|
// Floats cannot be hashed. Use the 5-decimal precision integer representation of the
|
||
|
// coordinates instead.
|
||
|
self.lat_as_i32().hash(state);
|
||
|
self.lon_as_i32().hash(state);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl PartialEq for Position {
|
||
|
fn eq(&self, other: &Self) -> bool {
|
||
|
self.lat_as_i32() == other.lat_as_i32() && self.lon_as_i32() == other.lon_as_i32()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Eq for Position {}
|
||
|
|
||
|
/// Resolves the geocoded position for a given address.
|
||
|
///
|
||
|
/// Returns [`None`] if the address could not be geocoded or the OpenStreetMap Nomatim API could
|
||
|
/// not be contacted.
|
||
|
///
|
||
|
/// If the result is [`Some`], it will be cached.
|
||
|
/// Note that only the 100 least recently used addresses will be cached.
|
||
|
#[cached(size = 100)]
|
||
|
pub(crate) async fn resolve_address(address: String) -> Option<Position> {
|
||
|
println!("🌍 Geocoding the position of the address: {}", address);
|
||
|
tokio::task::spawn_blocking(move || {
|
||
|
let osm = Openstreetmap::new();
|
||
|
let points: Vec<Point<f64>> = osm.forward(&address).ok()?;
|
||
|
|
||
|
points.get(0).map(Position::from)
|
||
|
})
|
||
|
.await
|
||
|
.ok()
|
||
|
.flatten()
|
||
|
}
|