extern crate num_traits;
use num_traits::Float;
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct FlatProjection<T: Float> {
kx: T,
ky: T,
lat: T,
lon: T,
}
impl<T: Float> FlatProjection<T> {
pub fn new(longitude: T, latitude: T) -> FlatProjection<T> {
let one = T::one();
let two = T::from(2).unwrap();
let re: T = T::from(6378.137).unwrap(); let fe: T = one / T::from(298.257223563).unwrap(); let e2: T = fe * (two - fe);
let cos_lat = latitude.to_radians().cos();
let w2 = one / (one - e2 * (one - cos_lat * cos_lat));
let w = w2.sqrt();
let kx = (re * w * cos_lat).to_radians(); let ky = (re * w * w2 * (one - e2)).to_radians();
FlatProjection { kx, ky, lat: latitude, lon: longitude }
}
pub fn project(&self, longitude: T, latitude: T) -> FlatPoint<T> {
let x = (longitude - self.lon) * self.kx;
let y = (latitude - self.lat) * self.ky;
FlatPoint { x, y }
}
pub fn unproject(&self, p: &FlatPoint<T>) -> (T, T) {
(p.x / self.kx + self.lon, p.y / self.ky + self.lat)
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct FlatPoint<T> {
pub x: T,
pub y: T,
}
impl<T: Float> FlatPoint<T> {
pub fn distance(&self, other: &FlatPoint<T>) -> T {
self.distance_squared(other).sqrt()
}
pub fn distance_squared(&self, other: &FlatPoint<T>) -> T {
let (dx, dy) = self.delta(other);
distance_squared(dx, dy)
}
pub fn bearing(&self, other: &FlatPoint<T>) -> T {
let (dx, dy) = self.delta(other);
bearing(dx, dy)
}
pub fn distance_bearing(&self, other: &FlatPoint<T>) -> (T, T) {
let (dx, dy) = self.delta(other);
(distance_squared(dx, dy).sqrt(), bearing(dx, dy))
}
fn delta(&self, other: &FlatPoint<T>) -> (T, T) {
(self.x - other.x, self.y - other.y)
}
pub fn destination(&self, dist: T, bearing: T) -> FlatPoint<T> {
let a = bearing.to_radians();
self.offset(a.sin() * dist, a.cos() * dist)
}
pub fn offset(&self, dx: T, dy: T) -> FlatPoint<T> {
FlatPoint {
x: self.x + dx,
y: self.y + dy,
}
}
}
fn distance_squared<T: Float>(dx: T, dy: T) -> T {
dx.powi(2) + dy.powi(2)
}
fn bearing<T: Float>(dx: T, dy: T) -> T {
(-dx).atan2(-dy).to_degrees()
}
#[cfg(test)] #[macro_use] extern crate assert_approx_eq;
#[cfg(test)]
mod tests {
use num_traits::Float;
use ::FlatProjection;
#[test]
fn flatpoint_destination_ne() {
let (lon, lat) = (30.5, 50.5);
let proj = FlatProjection::new(31., 50.);
let p1 = proj.project(lon, lat);
let (distance, bearing) = (1., 45.0);
let p2 = p1.destination(distance, bearing);
let res_distance = p1.distance(&p2);
let (dest_lon, dest_lat) = proj.unproject(&p2);
assert_approx_eq!(dest_lon, 30.5098622, 0.00001);
assert_approx_eq!(dest_lat, 50.5063572, 0.00001);
assert_approx_eq!(distance, res_distance, 0.00001);
}
#[test]
fn flatpoint_destination_se() {
let (lon, lat) = (30.5, 50.5);
let proj = FlatProjection::new(31., 50.);
let p1 = proj.project(lon, lat);
let (distance, bearing) = (1., 135.0);
let p2 = p1.destination(distance, bearing);
let res_distance = p1.distance(&p2);
let (dest_lon, dest_lat) = proj.unproject(&p2);
assert_approx_eq!(dest_lon, 30.5098622, 0.00001);
assert_approx_eq!(dest_lat, 50.4936427, 0.00001);
assert_approx_eq!(distance, res_distance, 0.00001);
}
#[test]
fn flatpoint_destination_sw() {
let (lon, lat) = (30.5, 50.5);
let proj = FlatProjection::new(31., 50.);
let p1 = proj.project(lon, lat);
let (distance, bearing) = (1., 225.0);
let p2 = p1.destination(distance, bearing);
let res_distance = p1.distance(&p2);
let (dest_lon, dest_lat) = proj.unproject(&p2);
assert_approx_eq!(dest_lon, 30.4901377, 0.00001);
assert_approx_eq!(dest_lat, 50.4936427, 0.00001);
assert_approx_eq!(distance, res_distance, 0.00001);
}
#[test]
fn flatpoint_destination_nw() {
let (lon, lat) = (30.5, 50.5);
let proj = FlatProjection::new(31., 50.);
let p1 = proj.project(lon, lat);
let (distance, bearing) = (1., 315.0);
let p2 = p1.destination(distance, bearing);
let res_distance = p1.distance(&p2);
let (dest_lon, dest_lat) = proj.unproject(&p2);
assert_approx_eq!(dest_lon, 30.4901377, 0.00001);
assert_approx_eq!(dest_lat, 50.5063572, 0.00001);
assert_approx_eq!(distance, res_distance, 0.00001);
}
}