2022-02-15 14:15:59 +01:00
|
|
|
//! Positions in the geographic coordinate system.
|
|
|
|
//!
|
|
|
|
//! This module contains everything related to geographic coordinate system functionality.
|
|
|
|
|
2022-06-06 14:53:56 +02:00
|
|
|
use std::f64::consts::PI;
|
2022-02-15 14:15:59 +01:00
|
|
|
use std::hash::Hash;
|
|
|
|
|
|
|
|
use cached::proc_macro::cached;
|
|
|
|
use geocoding::{Forward, Openstreetmap, Point};
|
|
|
|
use rocket::tokio;
|
|
|
|
|
2022-06-06 14:53:56 +02:00
|
|
|
use crate::{Error, Result};
|
2022-02-18 22:58:39 +01:00
|
|
|
|
2022-02-15 14:15:59 +01:00
|
|
|
/// 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 {
|
2022-02-19 15:19:46 +01:00
|
|
|
/// The latitude of the position.
|
2022-02-15 14:15:59 +01:00
|
|
|
pub(crate) lat: f64,
|
2022-02-19 15:19:46 +01:00
|
|
|
|
|
|
|
/// The longitude of the position.
|
2022-02-15 14:15:59 +01:00
|
|
|
pub(crate) lon: f64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Position {
|
|
|
|
/// Creates a new (geocoded) position.
|
2022-02-18 21:25:06 +01:00
|
|
|
pub(crate) const fn new(lat: f64, lon: f64) -> Self {
|
2022-02-15 14:15:59 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-02-18 22:58:39 +01:00
|
|
|
/// Returns the latitude in radians.
|
|
|
|
pub(crate) fn lat_as_rad(&self) -> f64 {
|
|
|
|
self.lat * PI / 180.0
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the longitude in radians.
|
|
|
|
pub(crate) fn lon_as_rad(&self) -> f64 {
|
|
|
|
self.lon * PI / 180.0
|
|
|
|
}
|
|
|
|
|
2022-02-15 14:15:59 +01:00
|
|
|
/// 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.
|
|
|
|
///
|
2022-06-06 14:53:56 +02:00
|
|
|
/// If the result is [`Ok`], it will be cached.
|
2022-02-15 14:15:59 +01:00
|
|
|
/// Note that only the 100 least recently used addresses will be cached.
|
2022-06-06 14:53:56 +02:00
|
|
|
#[cached(size = 100, result = true)]
|
|
|
|
pub(crate) async fn resolve_address(address: String) -> Result<Position> {
|
2022-02-15 14:15:59 +01:00
|
|
|
println!("🌍 Geocoding the position of the address: {}", address);
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
|
|
let osm = Openstreetmap::new();
|
2022-06-06 14:53:56 +02:00
|
|
|
let points: Vec<Point<f64>> = osm.forward(&address)?;
|
2022-02-15 14:15:59 +01:00
|
|
|
|
2022-06-06 14:53:56 +02:00
|
|
|
points
|
|
|
|
.get(0)
|
|
|
|
.ok_or(Error::NoPositionFound)
|
|
|
|
.map(Position::from)
|
2022-02-15 14:15:59 +01:00
|
|
|
})
|
2022-06-06 14:53:56 +02:00
|
|
|
.await?
|
2022-02-15 14:15:59 +01:00
|
|
|
}
|