///|
pub fn nearest_point_on_route(
query : Point,
route : ArrayView[Point],
) -> Result[NearestPoint, RouteError] {
match validate_point(query, 0) {
Ok(_) => ()
Err(err) => return Err(err)
}
match require_points(route) {
Ok(_) => ()
Err(err) => return Err(err)
}
if route.length() == 1 {
let distance = match haversine_meters(query, route[0]) {
Ok(value) => value
Err(err) => return Err(err)
}
return Ok(NearestPoint(route[0], distance, 0.0, 0, 0.0))
}
let mut best = NearestPoint(route[0], 999999999999.0, 0.0, 0, 0.0)
let mut walked = 0.0
for i in 1.. value
Err(err) => return Err(err)
}
if candidate.distance_to_route_m < best.distance_to_route_m {
best = candidate
}
match haversine_meters(route[i - 1], route[i]) {
Ok(value) => walked = walked + value
Err(err) => return Err(err)
}
}
Ok(best)
}
///|
pub fn distance_to_route_meters(
query : Point,
route : ArrayView[Point],
) -> Result[Double, RouteError] {
match nearest_point_on_route(query, route) {
Ok(value) => Ok(value.distance_to_route_m)
Err(err) => Err(err)
}
}
///|
pub fn route_distance_along_for_point(
query : Point,
route : ArrayView[Point],
) -> Result[Double, RouteError] {
match nearest_point_on_route(query, route) {
Ok(value) => Ok(value.distance_along_route_m)
Err(err) => Err(err)
}
}
///|
pub fn route_progress_fraction(
query : Point,
route : ArrayView[Point],
) -> Result[Double, RouteError] {
let nearest = match nearest_point_on_route(query, route) {
Ok(value) => value
Err(err) => return Err(err)
}
let total = match route_distance_meters(route) {
Ok(value) => value
Err(err) => return Err(err)
}
if total == 0.0 {
Ok(0.0)
} else {
Ok(nearest.distance_along_route_m / total)
}
}
///|
pub fn is_point_near_route(
query : Point,
route : ArrayView[Point],
tolerance_m : Double,
) -> Result[Bool, RouteError] {
match validate_distance(tolerance_m) {
Ok(_) => ()
Err(err) => return Err(err)
}
let distance = match distance_to_route_meters(query, route) {
Ok(value) => value
Err(err) => return Err(err)
}
Ok(distance <= tolerance_m)
}
///|
pub fn snap_point_to_route(
query : Point,
route : ArrayView[Point],
) -> Result[Point, RouteError] {
match nearest_point_on_route(query, route) {
Ok(value) => Ok(value.point)
Err(err) => Err(err)
}
}
///|
pub fn snap_points_to_route(
queries : ArrayView[Point],
route : ArrayView[Point],
) -> Result[Array[NearestPoint], RouteError] {
match validate_points(queries) {
Ok(_) => ()
Err(err) => return Err(err)
}
match require_points(route) {
Ok(_) => ()
Err(err) => return Err(err)
}
let out = Array::new(capacity=queries.length())
for query in queries {
match nearest_point_on_route(query, route) {
Ok(value) => out.push(value)
Err(err) => return Err(err)
}
}
Ok(out)
}
///|
pub fn points_within_route_buffer(
queries : ArrayView[Point],
route : ArrayView[Point],
tolerance_m : Double,
) -> Result[Array[Point], RouteError] {
match validate_distance(tolerance_m) {
Ok(_) => ()
Err(err) => return Err(err)
}
match validate_points(queries) {
Ok(_) => ()
Err(err) => return Err(err)
}
let out = Array::new()
for query in queries {
match is_point_near_route(query, route, tolerance_m) {
Ok(true) => out.push(query)
Ok(false) => ()
Err(err) => return Err(err)
}
}
Ok(out)
}
///|
pub fn route_station_for_fraction(
route : ArrayView[Point],
fraction : Double,
) -> Result[RouteSample, RouteError] {
match validate_fraction(fraction) {
Ok(_) => ()
Err(err) => return Err(err)
}
let total = match route_distance_meters(route) {
Ok(value) => value
Err(err) => return Err(err)
}
point_at_distance(route, total * fraction)
}
///|
pub fn route_station_points(
route : ArrayView[Point],
fractions : ArrayView[Double],
) -> Result[Array[RouteSample], RouteError] {
let out = Array::new(capacity=fractions.length())
for fraction in fractions {
match route_station_for_fraction(route, fraction) {
Ok(value) => out.push(value)
Err(err) => return Err(err)
}
}
Ok(out)
}
///|
pub fn cross_track_distance_meters(
query : Point,
start : Point,
finish : Point,
) -> Result[Double, RouteError] {
match validate_point(query, 0) {
Ok(_) => ()
Err(err) => return Err(err)
}
let d13 = match haversine_meters(start, query) {
Ok(value) => value / EARTH_RADIUS_M
Err(err) => return Err(err)
}
let bearing13 = match initial_bearing_degrees(start, query) {
Ok(value) => deg_to_rad(value)
Err(err) => return Err(err)
}
let bearing12 = match initial_bearing_degrees(start, finish) {
Ok(value) => deg_to_rad(value)
Err(err) => return Err(err)
}
Ok(
@math.asin(@math.sin(d13) * @math.sin(bearing13 - bearing12)) *
EARTH_RADIUS_M,
)
}
///|
pub fn along_track_distance_meters(
query : Point,
start : Point,
finish : Point,
) -> Result[Double, RouteError] {
let cross_track = match cross_track_distance_meters(query, start, finish) {
Ok(value) => value / EARTH_RADIUS_M
Err(err) => return Err(err)
}
let d13 = match haversine_meters(start, query) {
Ok(value) => value / EARTH_RADIUS_M
Err(err) => return Err(err)
}
let value = @math.acos(@math.cos(d13) / @math.cos(cross_track)) *
EARTH_RADIUS_M
if value.is_nan() || value.is_inf() {
Ok(0.0)
} else {
Ok(value)
}
}
///|
pub fn segment_fraction_for_projection(
query : Point,
start : Point,
finish : Point,
) -> Result[Double, RouteError] {
match validate_point(query, 0) {
Ok(_) => ()
Err(err) => return Err(err)
}
match validate_point(start, 1) {
Ok(_) => ()
Err(err) => return Err(err)
}
match validate_point(finish, 2) {
Ok(_) => ()
Err(err) => return Err(err)
}
let ref_lat = (query.lat + start.lat + finish.lat) / 3.0
let (px, py) = project_for_distance(query, ref_lat)
let (ax, ay) = project_for_distance(start, ref_lat)
let (bx, by) = project_for_distance(finish, ref_lat)
let dx = bx - ax
let dy = by - ay
if dx == 0.0 && dy == 0.0 {
Ok(0.0)
} else {
Ok(
clamp_double(
((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy),
0.0,
1.0,
),
)
}
}
///|
fn nearest_point_on_segment(
query : Point,
start : Point,
finish : Point,
segment_index : Int,
distance_before_segment_m : Double,
) -> Result[NearestPoint, RouteError] {
let fraction = match segment_fraction_for_projection(query, start, finish) {
Ok(value) => value
Err(err) => return Err(err)
}
let snapped = interpolate_point_unchecked(start, finish, fraction)
let off_distance = match haversine_meters(query, snapped) {
Ok(value) => value
Err(err) => return Err(err)
}
let segment_m = match haversine_meters(start, finish) {
Ok(value) => value
Err(err) => return Err(err)
}
Ok(
NearestPoint(
snapped,
off_distance,
distance_before_segment_m + segment_m * fraction,
segment_index,
fraction,
),
)
}