// Ported from georust/geo
// https://github.com/georust/geo/blob/main/geo/src/algorithm/euclidean_distance.rs
// and line_measures/{euclidean,bearing,destination}.rs
///|
/// Default tolerance for approximate Euclidean comparisons.
///
/// Sized to absorb the cumulative round-off of a few floating-point operations
/// (trigonometric / affine transforms / dot products) — about seven orders of
/// magnitude looser than the machine epsilon `@robust.EPSILON ≈ 1.11e-16`.
/// Use this when comparing coordinates that are mathematically equal but went
/// through several float operations; for exact-equality checks compare with
/// `@test.assert_eq` (or `==`) directly.
pub const TOLERANCE : Double = 1.0e-9
///|
/// Euclidean distance between two coordinates.
pub fn euclidean_distance_coords(a : @type.Coord, b : @type.Coord) -> Double {
let dx = b.x() - a.x()
let dy = b.y() - a.y()
(dx * dx + dy * dy).sqrt()
}
///|
/// Euclidean distance between two points.
pub fn euclidean_distance_points(a : @type.Point, b : @type.Point) -> Double {
euclidean_distance_coords(a.coord(), b.coord())
}
///|
/// Squared Euclidean distance between two coordinates (skips the sqrt).
pub fn euclidean_distance_squared_coords(
a : @type.Coord,
b : @type.Coord,
) -> Double {
let dx = b.x() - a.x()
let dy = b.y() - a.y()
dx * dx + dy * dy
}
///|
/// Euclidean distance from a coordinate to a line segment, clamped to the
/// segment's bounds.
pub fn euclidean_distance_coord_to_line(
p : @type.Coord,
l : @type.Line,
) -> Double {
let v = l.delta()
let w = p - l.start()
let v_dot_v = v.dot(v)
if v_dot_v == 0.0 {
return euclidean_distance_coords(p, l.start())
}
let t = w.dot(v) / v_dot_v
let clamped = if t < 0.0 { 0.0 } else if t > 1.0 { 1.0 } else { t }
let projection = l.start() + v.mul(clamped)
euclidean_distance_coords(p, projection)
}
///|
/// Euclidean distance from a point to a line segment.
pub fn euclidean_distance_point_to_line(
p : @type.Point,
l : @type.Line,
) -> Double {
euclidean_distance_coord_to_line(p.coord(), l)
}
///|
/// Euclidean distance from a coordinate to a `LineString`. Returns
/// `Double::infinity()` if the line string has fewer than 2 coordinates.
pub fn euclidean_distance_coord_to_line_string(
p : @type.Coord,
ls : @type.LineString,
) -> Double {
ls
.lines_iter()
.fold(init=@double.infinity, fn(acc, seg) {
let d = euclidean_distance_coord_to_line(p, seg)
if d < acc {
d
} else {
acc
}
})
}
///|
/// Euclidean length of a line segment: `sqrt(dx² + dy²)`.
fn euclidean_length_line(l : @type.Line) -> Double {
euclidean_distance_coords(l.start(), l.end())
}
///|
/// Sum of segment lengths in a `LineString`. An empty or single-coord line
/// string has length zero.
fn euclidean_length_line_string(ls : @type.LineString) -> Double {
ls
.lines_iter()
.fold(init=0.0, fn(acc, line) { acc + euclidean_length_line(line) })
}
///|
/// Sum of segment lengths in a `MultiLineString`.
fn euclidean_length_multi_line_string(mls : @type.MultiLineString) -> Double {
mls
.line_strings()
.iter()
.fold(init=0.0, fn(acc, ls) { acc + euclidean_length_line_string(ls) })
}
///|
/// Bearing in degrees from `origin` to `destination` in a planar Euclidean
/// frame. `0` is east (positive x); angle increases counter-clockwise.
/// Returns NaN if origin == destination.
pub fn euclidean_bearing(
origin : @type.Coord,
destination : @type.Coord,
) -> Double {
let dx = destination.x() - origin.x()
let dy = destination.y() - origin.y()
@math.atan2(dy, dx) * 180.0 / @math.PI
}
///|
/// Returns the destination coordinate when starting at `origin`, heading at
/// `bearing` degrees (0 = east, CCW), and travelling `distance` units in a
/// planar Euclidean frame.
pub fn euclidean_destination(
origin : @type.Coord,
bearing_degrees : Double,
distance : Double,
) -> @type.Coord {
let radians = bearing_degrees * @math.PI / 180.0
@type.Coord(
origin.x() + distance * @math.cos(radians),
origin.y() + distance * @math.sin(radians),
)
}
// MARK: trait HasEuclideanLength + impls ------------------------------------
///|
/// Anything that has a planar Euclidean length (the physical "how long is
/// this thing in coordinate units"): segment length, line string length,
/// multi line string total length. Distinct from `HasLength`, which is the
/// **collection cardinality** (how many components / coords).
pub(open) trait HasEuclideanLength {
fn euclidean_length(Self) -> Double
}
///|
pub impl HasEuclideanLength for @type.Line with fn euclidean_length(self) {
euclidean_length_line(self)
}
///|
pub impl HasEuclideanLength for @type.LineString with fn euclidean_length(self) {
euclidean_length_line_string(self)
}
///|
pub impl HasEuclideanLength for @type.MultiLineString with fn euclidean_length(
self,
) {
euclidean_length_multi_line_string(self)
}