use crate::model::common::{ApplyTransform, ComputeEnvelope, IterGeometries};
use crate::model::geometry::primitives::{AbstractCurve, AsAbstractCurve, AsAbstractCurveMut};
use crate::model::geometry::refs::AbstractGeometryKindRef;
use crate::model::geometry::{DirectPosition, Envelope};
use crate::util::distance::point_segment_distance_2d;
use crate::{
Error, impl_abstract_curve_mut_traits, impl_abstract_curve_traits, impl_has_geometry_type,
};
use nalgebra::{Isometry3, Rotation3, Scale3, Transform3, Vector2, Vector3};
#[derive(Debug, Clone, PartialEq, Default)]
pub struct LineString {
pub abstract_curve: AbstractCurve,
points: Box<[DirectPosition]>,
}
impl LineString {
pub fn new(points: impl IntoIterator<Item = DirectPosition>) -> Result<Self, Error> {
let points: Vec<DirectPosition> = points.into_iter().collect();
Self::validate(&points)?;
Ok(Self {
abstract_curve: AbstractCurve::default(),
points: points.into_boxed_slice(),
})
}
pub fn from_abstract_curve(
abstract_curve: AbstractCurve,
points: impl IntoIterator<Item = DirectPosition>,
) -> Result<Self, Error> {
let points: Vec<DirectPosition> = points.into_iter().collect();
Self::validate(&points)?;
Ok(Self {
abstract_curve,
points: points.into_boxed_slice(),
})
}
fn validate(points: &[DirectPosition]) -> Result<(), Error> {
if let Some((index, window)) = points.windows(2).enumerate().find(|(_, w)| w[0] == w[1]) {
return Err(Error::AdjacentDuplicatePositions {
index,
position: window[0],
});
}
if points.len() < 2 {
return Err(Error::TooFewElements {
geometry: "gml:LineString",
minimum: 2,
spec: Some("OGC 07-036 ยง10.4.4"),
id: None,
detail: None,
});
}
Ok(())
}
pub fn points(&self) -> &[DirectPosition] {
&self.points
}
pub fn set_points(
&mut self,
points: impl IntoIterator<Item = DirectPosition>,
) -> Result<(), Error> {
let points: Vec<DirectPosition> = points.into_iter().collect();
Self::validate(&points)?;
self.points = points.into_boxed_slice();
Ok(())
}
}
impl AsAbstractCurve for LineString {
fn abstract_curve(&self) -> &AbstractCurve {
&self.abstract_curve
}
}
impl AsAbstractCurveMut for LineString {
fn abstract_curve_mut(&mut self) -> &mut AbstractCurve {
&mut self.abstract_curve
}
}
impl_abstract_curve_traits!(LineString);
impl_abstract_curve_mut_traits!(LineString);
impl_has_geometry_type!(LineString, LineString);
impl LineString {
pub fn length_3d(&self) -> f64 {
self.points
.windows(2)
.map(|w| {
let a: Vector3<f64> = w[0].into();
let b: Vector3<f64> = w[1].into();
(b - a).norm()
})
.sum()
}
pub fn distance_2d(&self, point: &DirectPosition) -> f64 {
let p = Vector2::new(point.x(), point.y());
self.points
.windows(2)
.map(|w| {
let a = Vector2::new(w[0].x(), w[0].y());
let b = Vector2::new(w[1].x(), w[1].y());
point_segment_distance_2d(p, a, b)
})
.fold(f64::INFINITY, f64::min)
}
}
impl ApplyTransform for LineString {
fn apply_transform(&mut self, transform: Transform3<f64>) {
self.points.iter_mut().for_each(|p| {
p.apply_transform(transform);
});
}
fn apply_isometry(&mut self, isometry: Isometry3<f64>) {
self.points.iter_mut().for_each(|p| {
p.apply_isometry(isometry);
});
}
fn apply_translation(&mut self, vector: Vector3<f64>) {
self.points.iter_mut().for_each(|p| {
p.apply_translation(vector);
});
}
fn apply_rotation(&mut self, rotation: Rotation3<f64>) {
self.points.iter_mut().for_each(|p| {
p.apply_rotation(rotation);
});
}
fn apply_scale(&mut self, scale: Scale3<f64>) {
self.points.iter_mut().for_each(|p| {
p.apply_scale(scale);
});
}
}
impl ComputeEnvelope for LineString {
fn compute_envelope(&self) -> Option<Envelope> {
Some(Envelope::from_points(&self.points).expect("line string must have valid points"))
}
}
impl IterGeometries for LineString {
fn iter_geometries(&self) -> Box<dyn Iterator<Item = AbstractGeometryKindRef<'_>> + '_> {
Box::new(std::iter::once(self.into()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn length_3d_axis_aligned() {
let ls = LineString::new([
DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
DirectPosition::new(3.0, 0.0, 0.0).unwrap(),
DirectPosition::new(3.0, 4.0, 0.0).unwrap(),
])
.unwrap();
assert!((ls.length_3d() - 7.0).abs() < 1e-10);
}
#[test]
fn length_3d_diagonal() {
let ls = LineString::new([
DirectPosition::new(0.0, 0.0, 0.0).unwrap(),
DirectPosition::new(1.0, 1.0, 1.0).unwrap(),
])
.unwrap();
let expected = 3.0_f64.sqrt();
assert!((ls.length_3d() - expected).abs() < 1e-10);
}
fn pos(x: f64, y: f64, z: f64) -> DirectPosition {
DirectPosition::new(x, y, z).unwrap()
}
#[test]
fn distance_2d_projects_onto_segment_interior() {
let ls = LineString::new([pos(0.0, 0.0, 0.0), pos(10.0, 0.0, 0.0)]).unwrap();
assert!((ls.distance_2d(&pos(5.0, 3.0, 0.0)) - 3.0).abs() < 1e-10);
}
#[test]
fn distance_2d_clamps_to_endpoint() {
let ls = LineString::new([pos(0.0, 0.0, 0.0), pos(10.0, 0.0, 0.0)]).unwrap();
assert!((ls.distance_2d(&pos(13.0, 4.0, 0.0)) - 5.0).abs() < 1e-10);
assert!((ls.distance_2d(&pos(-3.0, -4.0, 0.0)) - 5.0).abs() < 1e-10);
}
#[test]
fn distance_2d_is_zero_on_line() {
let ls = LineString::new([pos(0.0, 0.0, 0.0), pos(4.0, 4.0, 0.0)]).unwrap();
assert!(ls.distance_2d(&pos(2.0, 2.0, 0.0)).abs() < 1e-10);
assert!(ls.distance_2d(&pos(4.0, 4.0, 0.0)).abs() < 1e-10);
}
#[test]
fn distance_2d_takes_minimum_over_segments() {
let ls = LineString::new([
pos(0.0, 0.0, 0.0),
pos(10.0, 0.0, 0.0),
pos(10.0, 10.0, 0.0),
])
.unwrap();
assert!((ls.distance_2d(&pos(8.0, 5.0, 0.0)) - 2.0).abs() < 1e-10);
}
#[test]
fn distance_2d_ignores_z() {
let ls = LineString::new([pos(0.0, 0.0, 100.0), pos(10.0, 0.0, -50.0)]).unwrap();
assert!((ls.distance_2d(&pos(5.0, 3.0, 1000.0)) - 3.0).abs() < 1e-10);
}
#[test]
fn distance_2d_handles_vertical_segment() {
let ls = LineString::new([pos(1.0, 1.0, 0.0), pos(1.0, 1.0, 5.0)]).unwrap();
let d = ls.distance_2d(&pos(4.0, 5.0, 0.0));
assert!(!d.is_nan());
assert!((d - 5.0).abs() < 1e-10);
}
}