radar-g/src/painter/coords/wgs84.rs
2023-07-06 08:44:20 +08:00

335 lines
10 KiB
Rust

use super::Coord;
use geo_macros::Prj;
use num_traits::{AsPrimitive, FromPrimitive, Num};
use proj::{Proj, ProjError};
use thiserror::Error;
type Lat = f64;
type Lon = f64;
#[derive(Clone, Copy)]
pub struct Range(pub f64, pub f64);
impl Range {
pub fn key_points(&self, max_points: usize) -> Vec<f64> {
if max_points == 0 {
return vec![];
}
let range = (self.0.min(self.1) as f64, self.1.max(self.0) as f64);
assert!(!(range.0.is_nan() || range.1.is_nan()));
if (range.0 - range.1).abs() < std::f64::EPSILON {
return vec![range.0 as f64];
}
let mut scale = (10f64).powf((range.1 - range.0).log(10.0).floor());
// The value granularity controls how we round the values.
// To avoid generating key points like 1.00000000001, we round to the nearest multiple of the
// value granularity.
// By default, we make the granularity as the 1/10 of the scale.
let mut value_granularity = scale / 10.0;
fn rem_euclid(a: f64, b: f64) -> f64 {
let ret = if b > 0.0 {
a - (a / b).floor() * b
} else {
a - (a / b).ceil() * b
};
if (ret - b).abs() < std::f64::EPSILON {
0.0
} else {
ret
}
}
// At this point we need to make sure that the loop invariant:
// The scale must yield number of points than requested
if 1 + ((range.1 - range.0) / scale).floor() as usize > max_points {
scale *= 10.0;
value_granularity *= 10.0;
}
'outer: loop {
let old_scale = scale;
for nxt in [2.0, 5.0, 10.0].iter() {
let mut new_left = range.0 - rem_euclid(range.0, old_scale / nxt);
if new_left < range.0 {
new_left += old_scale / nxt;
}
let new_right = range.1 - rem_euclid(range.1, old_scale / nxt);
let npoints = 1.0 + ((new_right - new_left) / old_scale * nxt);
if npoints.round() as usize > max_points {
break 'outer;
}
scale = old_scale / nxt;
}
scale = old_scale / 10.0;
value_granularity /= 10.0;
}
let mut ret = vec![];
// In some extreme cases, left might be too big, so that (left + scale) - left == 0 due to
// floating point error.
// In this case, we may loop forever. To avoid this, we need to use two variables to store
// the current left value. So we need keep a left_base and a left_relative.
let left = {
let mut value = range.0 - rem_euclid(range.0, scale);
if value < range.0 {
value += scale;
}
value
};
let left_base = (left / value_granularity).floor() * value_granularity;
let mut left_relative = left - left_base;
let right = range.1 - rem_euclid(range.1, scale);
while (right - left_relative - left_base) >= -std::f64::EPSILON {
let new_left_relative = (left_relative / value_granularity).round() * value_granularity;
if new_left_relative < 0.0 {
left_relative += value_granularity;
}
ret.push((left_relative + left_base) as f64);
left_relative += scale;
}
return ret;
}
}
#[derive(Error, Debug)]
pub enum CoordError {
#[error("")]
ProjError {
#[from]
source: ProjError,
},
}
impl<T: AsPrimitive<f64> + Num> From<(T, T)> for Range {
fn from(value: (T, T)) -> Self {
let value = (value.0.as_(), value.1.as_());
let (_min, _max) = (value.0.min(value.1), value.0.max(value.1));
Self(_min, _max)
}
}
pub struct LatLonCoord<T: ProjectionS> {
// actual: ((f64, f64), (f64, f64)),
actual: (Range, Range),
logical: (Range, Range),
pcs: PCS<T>,
}
struct PCS<T: ProjectionS> {
pub lon_range: Range,
pub lat_range: Range,
pub proj_param: T,
transformer: Proj,
}
unsafe impl Sync for PCS<Mercator> {}
unsafe impl Send for PCS<Mercator> {}
impl<T: ProjectionS> LatLonCoord<T> {
/// Creates a new `LatLonCoord` instance.
///
/// # Arguments
///
/// * `lon` - An optional longitude range.
/// * `lat` - An optional latitude range.
/// * `actual` - A tuple containing the actual ranges.
/// * `project` - A projection.
///
/// # Returns
///
/// A new `LatLonCoord` instance.
pub fn new(
lon: Option<Range>,
lat: Option<Range>,
actual: ((i32, i32), (i32, i32)),
// actual: (Range, Range),
project: T,
) -> Self {
let pcs = PCS::new(project, lon, lat);
let _box = pcs.bbox().unwrap();
Self {
actual: (Range::from(actual.0), Range::from(actual.1)),
pcs: pcs,
logical: _box,
}
}
pub fn set_actual(&mut self, actual: ((i32, i32), (i32, i32))) {
self.actual = (Range::from(actual.0), Range::from(actual.1));
}
pub fn lon_range(&self) -> Range {
self.pcs.lon_range
}
pub fn lat_range(&self) -> Range {
self.pcs.lat_range
}
}
unsafe impl<T: ProjectionS> Sync for LatLonCoord<T> {}
unsafe impl<T: ProjectionS> Send for LatLonCoord<T> {}
impl<T: ProjectionS> Coord<f64> for LatLonCoord<T> {
fn map(&self, axis_1: f64, axis_2: f64) -> super::ScreenCoord {
let point = self.pcs.map((axis_1, axis_2));
let logical_dim1_span = self.logical.0 .1 - self.logical.0 .0;
let dim1_rate = (point.0 - self.logical.0 .0) / logical_dim1_span;
let logical_dim2_span = self.logical.1 .1 - self.logical.1 .0;
let dim2_rate = (point.1 - self.logical.1 .0) / logical_dim2_span;
(
(dim1_rate * (self.actual.0 .1 - self.actual.0 .0) as f64) + self.actual.0 .0,
(dim2_rate * (self.actual.1 .1 - self.actual.1 .0) as f64) + self.actual.1 .0,
)
}
fn dim1_range(&self) -> (f64, f64) {
let v = self.lon_range();
(v.0, v.1)
}
fn dim2_range(&self) -> (f64, f64) {
let v = self.lat_range();
(v.0, v.1)
}
}
#[derive(Clone, Debug)]
pub enum Projection {
PlateCarree,
LambertConformal,
LambertCylindrical,
Mercator,
}
/// A trait for defining a projection.
pub trait ProjectionS: Sync + Send {
/// Returns a proj-string of the projection.
fn build(&self) -> String;
/// Returns the logical range of the projection.
/// In common, different projections have different logical ranges.
/// # Arguments
///
/// * `lon_range` - An optional longitude range.
/// * `lat_range` - An optional latitude range.
///
/// # Returns
///
/// A tuple containing the longitude and latitude ranges.
fn logic_range(&self, lon_range: Option<Range>, lat_range: Option<Range>) -> (Range, Range);
}
impl<T: ProjectionS> PCS<T> {
fn new(proj_param: T, lon_range: Option<Range>, lat_range: Option<Range>) -> Self {
let (lon_range, lat_range) = proj_param.logic_range(lon_range, lat_range);
Self {
lon_range,
lat_range,
transformer: Proj::new(proj_param.build().as_str()).unwrap(),
proj_param: proj_param,
}
}
fn bbox(&self) -> Result<(Range, Range), CoordError> {
let _proj_transformer = &self.transformer;
let lb = (self.lon_range.0.to_radians(), self.lat_range.0.to_radians());
let rt = (self.lon_range.1.to_radians(), self.lat_range.1.to_radians());
let bl = _proj_transformer.convert(lb)?;
let rt = _proj_transformer.convert(rt)?;
Ok((Range::from((bl.0, rt.0)), Range::from((bl.1, rt.1))))
}
fn map(&self, lon_lat: (Lat, Lon)) -> (f64, f64) {
let _proj_transformer = &self.transformer;
let _lon_lat = _proj_transformer
.convert((lon_lat.0.to_radians(), lon_lat.1.to_radians()))
.unwrap();
_lon_lat
}
}
#[derive(Prj)]
/// A struct representing the Mercator projection.
pub struct Mercator {
/// The central longitude of the projection.
pub central_lon: f64,
/// The minimum latitude of the projection.
pub min_latitude: f64,
/// The maximum latitude of the projection.
pub max_latitude: f64,
/// The false easting of the projection.
pub false_easting: f64,
/// The false northing of the projection.
pub false_northing: f64,
/// The latitude of true scale of the projection.
pub latitude_true_scale: f64,
}
fn proj_string<'a>(vs: Vec<(&'a str, &'a str)>) -> String {
vs.into_iter()
.map(|(option, value)| format!("+{}={}", option, value))
.collect::<Vec<String>>()
.join(" ")
}
impl Mercator {
/// Creates a new instance of the `Mercator` projection with default values.
pub fn new() -> Self {
Self {
central_lon: 0.0,
min_latitude: -82.0,
max_latitude: 82.0,
false_easting: 0.0,
false_northing: 0.0,
latitude_true_scale: 0.0,
}
}
}
impl ProjectionS for Mercator {
fn build(&self) -> String {
let _central_lon = format!("{:.1}", &self.central_lon);
let _false_easting = format!("{:.1}", &self.false_easting);
let _false_northing = format!("{:.1}", &self.false_northing);
let _ts = format!("{:.1}", &self.latitude_true_scale);
let input = vec![
("proj", "merc"),
("lon_0", _central_lon.as_str()),
("x_0", _false_easting.as_str()),
("y_0", _false_northing.as_str()),
("lat_ts", _ts.as_str()),
("units", "m"),
("ellps", "GRS80"),
];
let _proj_string = proj_string(input);
_proj_string
}
fn logic_range(&self, lon_range: Option<Range>, lat_range: Option<Range>) -> (Range, Range) {
let lon_range = lon_range.unwrap_or(Range {
0: -180f64,
1: 180f64,
});
let lat_range = lat_range.unwrap_or(Range {
0: self.min_latitude,
1: self.max_latitude,
});
let lat_range = Range {
0: lat_range.0.max(self.min_latitude),
1: lat_range.1.min(self.max_latitude),
};
(lon_range, lat_range)
}
}