#[cfg(all(feature = "libm", not(feature = "std")))]
use num_traits::float::Float;
const EARTH_CIRCUMFERENCE: f32 = 39_940_653.;
#[must_use]
pub fn point_from_dxy(dx: f32, dy: f32, ref_pos: &geo_types::Point) -> geo_types::Point {
dxy_to_geo(dx, dy, ref_pos).into()
}
#[cfg(feature = "_dsrc_2_2_1")]
impl From<crate::standards::dsrc_2_2_1::etsi_its_dsrc::Position3D> for geo_types::Point {
fn from(other: crate::standards::dsrc_2_2_1::etsi_its_dsrc::Position3D) -> Self {
geo_types::Point::new(other.long.as_deg(), other.lat.as_deg())
}
}
#[cfg(feature = "_dsrc_2_2_1")]
impl From<geo_types::Point> for crate::standards::dsrc_2_2_1::etsi_its_dsrc::Position3D {
fn from(other: geo_types::Point) -> Self {
use crate::standards::cdd_2_2_1::etsi_its_cdd::{Latitude, Longitude};
Self {
lat: Latitude::from_deg(other.y()),
long: Longitude::from_deg(other.x()),
elevation: None,
regional: None,
}
}
}
#[cfg(any(feature = "_cdd_1_3_1_1", feature = "_cdd_2_2_1"))]
macro_rules! refpos_to_point {
($t:ty) => {
impl From<$t> for geo_types::Point {
fn from(other: $t) -> Self {
geo_types::Point::new(other.longitude.as_deg(), other.latitude.as_deg())
}
}
};
}
#[cfg(feature = "_cdd_1_3_1_1")]
refpos_to_point!(crate::standards::cdd_1_3_1_1::its_container::ReferencePosition);
#[cfg(feature = "_cdd_2_2_1")]
refpos_to_point!(crate::standards::cdd_2_2_1::etsi_its_cdd::ReferencePosition);
#[cfg(any(feature = "_cdd_1_3_1_1", feature = "denm_2_2_1"))]
macro_rules! ph_to_line_string {
($t:ty) => {
impl $t {
#[allow(
clippy::missing_panics_doc,
reason = "panic on last().unwrap() is impossible here"
)]
#[must_use]
pub fn to_line_string(&self, ref_pos: geo_types::Point) -> geo_types::LineString {
self.0
.iter()
.fold(alloc::vec![ref_pos], |mut acc, pt| {
let origin = acc.last().unwrap();
let delta = geo_types::Point::new(
pt.path_position.delta_longitude.as_deg(),
pt.path_position.delta_latitude.as_deg(),
);
acc.push(*origin + delta);
acc
})
.into()
}
}
};
}
#[cfg(feature = "_cdd_1_3_1_1")]
ph_to_line_string!(crate::standards::cdd_1_3_1_1::its_container::PathHistory);
#[cfg(feature = "denm_2_2_1")]
ph_to_line_string!(crate::standards::cdd_2_2_1::etsi_its_cdd::Path);
#[cfg(feature = "mapem_2_2_1")]
impl crate::standards::dsrc_2_2_1::etsi_its_dsrc::NodeSetXY {
#[allow(
clippy::missing_panics_doc,
reason = "panic on last().unwrap() is impossible here"
)]
#[must_use]
pub fn to_line_string(&self, ref_pos: geo_types::Point) -> geo_types::LineString {
let mut path = self.0.iter().fold(alloc::vec![ref_pos], |mut acc, pt| {
let origin = acc.last().unwrap();
let pos = pt.delta.to_geo(origin);
acc.push(pos);
acc
});
path.remove(0);
path.into()
}
}
#[cfg(feature = "mapem_2_2_1")]
impl crate::standards::dsrc_2_2_1::etsi_its_dsrc::NodeOffsetPointXY {
#[must_use]
pub fn to_geo(&self, ref_pos: &geo_types::Point) -> geo_types::Point {
use crate::standards::dsrc_2_2_1::etsi_its_dsrc::NodeOffsetPointXY;
let (lon, lat) = match self {
NodeOffsetPointXY::node_XY1(etsi) => {
dxy_to_geo((&etsi.x).into(), (&etsi.y).into(), ref_pos)
}
NodeOffsetPointXY::node_XY2(etsi) => {
dxy_to_geo((&etsi.x).into(), (&etsi.y).into(), ref_pos)
}
NodeOffsetPointXY::node_XY3(etsi) => {
dxy_to_geo((&etsi.x).into(), (&etsi.y).into(), ref_pos)
}
NodeOffsetPointXY::node_XY4(etsi) => {
dxy_to_geo((&etsi.x).into(), (&etsi.y).into(), ref_pos)
}
NodeOffsetPointXY::node_XY5(etsi) => {
dxy_to_geo((&etsi.x).into(), (&etsi.y).into(), ref_pos)
}
NodeOffsetPointXY::node_XY6(etsi) => {
dxy_to_geo((&etsi.x).into(), (&etsi.y).into(), ref_pos)
}
NodeOffsetPointXY::node_LatLon(etsi) => (etsi.lon.as_deg(), etsi.lat.as_deg()),
NodeOffsetPointXY::regional(_) => (0., 0.),
};
geo_types::Point::new(lon, lat)
}
#[must_use]
pub fn to_ddist(&self, ref_pos: &geo_types::Point) -> geo_types::Point {
use crate::standards::dsrc_2_2_1::etsi_its_dsrc::NodeOffsetPointXY;
let (x, y) = match self {
NodeOffsetPointXY::node_XY1(etsi) => ((&etsi.x).into(), (&etsi.y).into()),
NodeOffsetPointXY::node_XY2(etsi) => ((&etsi.x).into(), (&etsi.y).into()),
NodeOffsetPointXY::node_XY3(etsi) => ((&etsi.x).into(), (&etsi.y).into()),
NodeOffsetPointXY::node_XY4(etsi) => ((&etsi.x).into(), (&etsi.y).into()),
NodeOffsetPointXY::node_XY5(etsi) => ((&etsi.x).into(), (&etsi.y).into()),
NodeOffsetPointXY::node_XY6(etsi) => ((&etsi.x).into(), (&etsi.y).into()),
NodeOffsetPointXY::node_LatLon(etsi) => latlon_to_dcart(etsi, ref_pos),
NodeOffsetPointXY::regional(_) => (0., 0.),
};
geo_types::Point::new(x.into(), y.into())
}
}
#[cfg(any(feature = "std", feature = "libm"))]
fn dxy_to_geo(dx: f32, dy: f32, ref_pos: &geo_types::Point) -> (f64, f64) {
let dlat = f64::from(dy) / f64::from(EARTH_CIRCUMFERENCE) * 360.;
let ref_lat_rad = ref_pos.to_radians().y();
let dlon = f64::from(dx) / (f64::from(EARTH_CIRCUMFERENCE) * ref_lat_rad.cos()) * 360.;
(ref_pos.x() + dlon, ref_pos.y() + dlat)
}
#[cfg(all(feature = "mapem_2_2_1", any(feature = "std", feature = "libm")))]
fn latlon_to_dcart(
dpos: &crate::standards::dsrc_2_2_1::etsi_its_dsrc::NodeLLmD64b,
ref_pos: &geo_types::Point,
) -> (f32, f32) {
#[allow(clippy::cast_possible_truncation)]
let dlat_deg = dpos.lat.as_deg() as f32 - ref_pos.y() as f32;
let dy = dlat_deg / 360. * EARTH_CIRCUMFERENCE;
#[allow(clippy::cast_possible_truncation)]
let ref_lat_rad = ref_pos.to_radians().y() as f32;
#[allow(clippy::cast_possible_truncation)]
let dlon_deg = dpos.lon.as_deg() as f32 - ref_pos.x() as f32;
let dx = dlon_deg / 360. * (EARTH_CIRCUMFERENCE * ref_lat_rad.cos());
(dx, dy)
}
#[cfg(feature = "cpm_1")]
impl crate::standards::cpm_1::cpm_pdu_descriptions::NodeOffsetPointZ {
#[must_use]
pub fn to_meters(&self) -> f32 {
use crate::standards::cpm_1::cpm_pdu_descriptions::NodeOffsetPointZ;
match self {
NodeOffsetPointZ::node_Z1(etsi) => etsi.into(),
NodeOffsetPointZ::node_Z2(etsi) => etsi.into(),
NodeOffsetPointZ::node_Z3(etsi) => etsi.into(),
NodeOffsetPointZ::node_Z4(etsi) => etsi.into(),
NodeOffsetPointZ::node_Z5(etsi) => etsi.into(),
NodeOffsetPointZ::node_Z6(etsi) => etsi.into(),
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn dxy_to_geo_test() {
use crate::geo_utils::dxy_to_geo;
let ref_pos = geo_types::point! {x: 9.936_521, y: 53.550_728};
assert_eq!((ref_pos.x(), ref_pos.y()), dxy_to_geo(0., 0., &ref_pos));
let (dlon, dlat) = dxy_to_geo(0., 185., &ref_pos);
assert_float_eq::assert_float_absolute_eq!(ref_pos.x() + 0., dlon);
assert_float_eq::assert_float_absolute_eq!(ref_pos.y() + 0.001_667, dlat);
let (dlon, dlat) = dxy_to_geo(0., -185., &ref_pos);
assert_float_eq::assert_float_absolute_eq!(ref_pos.x() + 0., dlon);
assert_float_eq::assert_float_absolute_eq!(ref_pos.y() + -0.001_667, dlat);
let (dlon, dlat) = dxy_to_geo(66., 0., &ref_pos);
assert_float_eq::assert_float_absolute_eq!(ref_pos.x() + 0.001_001, dlon);
assert_float_eq::assert_float_absolute_eq!(ref_pos.y() + 0., dlat);
let (dlon, dlat) = dxy_to_geo(66., 185., &ref_pos);
assert_float_eq::assert_float_absolute_eq!(ref_pos.x() + 0.001_001, dlon);
assert_float_eq::assert_float_absolute_eq!(ref_pos.y() + 0.001_667, dlat);
}
#[test]
#[cfg(feature = "mapem_2_2_1")]
fn nodeset_to_line_string() {
use crate::standards::cdd_2_2_1::etsi_its_cdd::{Latitude, Longitude};
use crate::standards::dsrc_2_2_1::etsi_its_dsrc::{
NodeLLmD64b,
NodeOffsetPointXY,
NodeSetXY,
NodeXY,
NodeXY32b,
OffsetB16,
};
let ref_pos = geo_types::point! {x: 9.936_521, y: 53.550_728};
{
let point1 = NodeLLmD64b::new(
Longitude::from_deg(ref_pos.x() + 0.001),
Latitude::from_deg(ref_pos.y() + 0.005),
);
let point2 = NodeLLmD64b::new(
Longitude::from_deg(ref_pos.x() + -0.042),
Latitude::from_deg(ref_pos.y() + 0.),
);
let nodes = alloc::vec![
NodeXY::new(NodeOffsetPointXY::node_LatLon(point1), None),
NodeXY::new(NodeOffsetPointXY::node_LatLon(point2), None),
];
let geo_nodes = NodeSetXY(nodes).to_line_string(ref_pos).into_points();
let geo_point1 = geo_types::Point::new(9.936_521 + 0.001, 53.550_728 + 0.005);
assert_float_eq::assert_float_absolute_eq!(geo_point1.x(), geo_nodes[0].x());
assert_float_eq::assert_float_absolute_eq!(geo_point1.y(), geo_nodes[0].y());
let geo_point2 = geo_types::Point::new(9.936_521 - 0.042, 53.550_728 + 0.);
assert_float_eq::assert_float_absolute_eq!(geo_point2.x(), geo_nodes[1].x());
assert_float_eq::assert_float_absolute_eq!(geo_point2.y(), geo_nodes[1].y());
}
{
let point1 = NodeXY32b::new(
OffsetB16::from_meters(0.).unwrap(),
OffsetB16::from_meters(185.).unwrap(),
);
let point2 = NodeXY32b::new(
OffsetB16::from_meters(66.).unwrap(),
OffsetB16::from_meters(0.).unwrap(),
);
let nodes = alloc::vec![
NodeXY::new(NodeOffsetPointXY::node_XY6(point1), None),
NodeXY::new(NodeOffsetPointXY::node_XY6(point2), None),
];
let geo_nodes = NodeSetXY(nodes).to_line_string(ref_pos).into_points();
let geo_point1 = geo_types::Point::new(9.936_521, 53.550_728 + 0.001_667);
assert_float_eq::assert_float_absolute_eq!(geo_point1.x(), geo_nodes[0].x());
assert_float_eq::assert_float_absolute_eq!(geo_point1.y(), geo_nodes[0].y());
let geo_point2 = geo_types::Point::new(9.936_521 + 0.001_001, 53.550_728 + 0.001_667);
assert_float_eq::assert_float_absolute_eq!(geo_point2.x(), geo_nodes[1].x());
assert_float_eq::assert_float_absolute_eq!(geo_point2.y(), geo_nodes[1].y());
}
}
}