///|
pub fn interpolate_point(
  start : Point,
  finish : Point,
  fraction : Double,
) -> Result[Point, RouteError] {
  match validate_point(start, 0) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  match validate_point(finish, 1) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  match validate_fraction(fraction) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  Ok(interpolate_point_unchecked(start, finish, fraction))
}

///|
pub fn point_at_distance(
  points : ArrayView[Point],
  distance_m : Double,
) -> Result[RouteSample, RouteError] {
  match require_points(points) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  match validate_distance(distance_m) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  if points.length() == 1 {
    return Ok(RouteSample(points[0], 0.0, 0, 0.0))
  }
  let total = match route_distance_meters(points) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  if total == 0.0 || distance_m <= 0.0 {
    return Ok(RouteSample(points[0], 0.0, 0, 0.0))
  }
  if distance_m >= total {
    return Ok(
      RouteSample(points[points.length() - 1], total, points.length() - 2, 1.0),
    )
  }
  let mut walked = 0.0
  for i in 1.. value
      Err(err) => return Err(err)
    }
    if segment_m == 0.0 {
      ()
    } else if walked + segment_m >= distance_m {
      let fraction = (distance_m - walked) / segment_m
      let point = interpolate_point_unchecked(
        points[i - 1],
        points[i],
        fraction,
      )
      return Ok(RouteSample(point, distance_m, i - 1, fraction))
    }
    walked = walked + segment_m
  }
  Ok(RouteSample(points[points.length() - 1], total, points.length() - 2, 1.0))
}

///|
pub fn sample_route_by_count(
  points : ArrayView[Point],
  count : Int,
) -> Result[Array[RouteSample], RouteError] {
  match require_points(points) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  match validate_sample_count(count) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let out = Array::new(capacity=count)
  if count == 1 {
    match point_at_distance(points, 0.0) {
      Ok(sample) => out.push(sample)
      Err(err) => return Err(err)
    }
    return Ok(out)
  }
  let total = match route_distance_meters(points) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let step = total / (count - 1).to_double()
  for i in 0.. out.push(sample)
      Err(err) => return Err(err)
    }
  }
  Ok(out)
}

///|
pub fn sample_route_every(
  points : ArrayView[Point],
  interval_m : Double,
  include_end? : Bool = true,
) -> Result[Array[RouteSample], RouteError] {
  match require_points(points) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  match validate_positive_distance(interval_m) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let total = match route_distance_meters(points) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let out = Array::new()
  match point_at_distance(points, 0.0) {
    Ok(sample) => out.push(sample)
    Err(err) => return Err(err)
  }
  if total == 0.0 {
    return Ok(out)
  }
  let mut distance = interval_m
  while distance < total {
    match point_at_distance(points, distance) {
      Ok(sample) => out.push(sample)
      Err(err) => return Err(err)
    }
    distance = distance + interval_m
  }
  if include_end {
    match point_at_distance(points, total) {
      Ok(sample) =>
        if out.length() == 0 ||
          !points_equal(out[out.length() - 1].point, sample.point) {
          out.push(sample)
        }
      Err(err) => return Err(err)
    }
  }
  Ok(out)
}

///|
pub fn resample_route_by_count(
  points : ArrayView[Point],
  count : Int,
) -> Result[Array[Point], RouteError] {
  let samples = match sample_route_by_count(points, count) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let out = Array::new(capacity=samples.length())
  for sample in samples {
    out.push(sample.point)
  }
  Ok(out)
}

///|
pub fn resample_route_every(
  points : ArrayView[Point],
  interval_m : Double,
  include_end? : Bool = true,
) -> Result[Array[Point], RouteError] {
  let samples = match sample_route_every(points, interval_m, include_end~) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let out = Array::new(capacity=samples.length())
  for sample in samples {
    out.push(sample.point)
  }
  Ok(out)
}

///|
pub fn densify_route(
  points : ArrayView[Point],
  max_segment_m : Double,
) -> Result[Array[Point], RouteError] {
  match require_points(points) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  match validate_positive_distance(max_segment_m) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let out = Array::new()
  out.push(points[0])
  if points.length() < 2 {
    return Ok(out)
  }
  for i in 1.. value
      Err(err) => return Err(err)
    }
    if distance > max_segment_m {
      let pieces = (distance / max_segment_m).ceil().to_int()
      for step in 1.. Result[RouteSlice, RouteError] {
  match require_points(points) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  match validate_distance(start_m) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  match validate_distance(end_m) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  if end_m < start_m {
    return Err(InvalidDistance(distance_m=end_m))
  }
  let total = match route_distance_meters(points) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let start_clamped = min_double(start_m, total)
  let end_clamped = min_double(end_m, total)
  let out = Array::new()
  let start_sample = match point_at_distance(points, start_clamped) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let end_sample = match point_at_distance(points, end_clamped) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  out.push(start_sample.point)
  let distances = match cumulative_distances(points) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  for i in 0.. start_clamped && distance < end_clamped {
      if !points_equal(out[out.length() - 1], points[i]) {
        out.push(points[i])
      }
    }
  }
  if !points_equal(out[out.length() - 1], end_sample.point) {
    out.push(end_sample.point)
  }
  Ok(RouteSlice(start_clamped, end_clamped, end_clamped - start_clamped, out))
}

///|
pub fn split_route_by_distance(
  points : ArrayView[Point],
  max_slice_m : Double,
) -> Result[Array[RouteSlice], RouteError] {
  match require_points(points) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  match validate_positive_distance(max_slice_m) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let total = match route_distance_meters(points) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let out = Array::new()
  if total == 0.0 {
    match slice_route(points, 0.0, 0.0) {
      Ok(value) => out.push(value)
      Err(err) => return Err(err)
    }
    return Ok(out)
  }
  let mut start = 0.0
  while start < total {
    let finish = min_double(start + max_slice_m, total)
    match slice_route(points, start, finish) {
      Ok(value) => out.push(value)
      Err(err) => return Err(err)
    }
    start = finish
  }
  Ok(out)
}

///|
pub fn split_route_by_count(
  points : ArrayView[Point],
  count : Int,
) -> Result[Array[RouteSlice], RouteError] {
  match validate_sample_count(count) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let total = match route_distance_meters(points) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  let out = Array::new(capacity=count)
  if count == 1 || total == 0.0 {
    match slice_route(points, 0.0, total) {
      Ok(value) => out.push(value)
      Err(err) => return Err(err)
    }
    return Ok(out)
  }
  let step = total / count.to_double()
  for i in 0.. out.push(value)
      Err(err) => return Err(err)
    }
  }
  Ok(out)
}

///|
pub fn prefix_route(
  points : ArrayView[Point],
  distance_m : Double,
) -> Result[RouteSlice, RouteError] {
  slice_route(points, 0.0, distance_m)
}

///|
pub fn suffix_route(
  points : ArrayView[Point],
  distance_m : Double,
) -> Result[RouteSlice, RouteError] {
  let total = match route_distance_meters(points) {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  match validate_distance(distance_m) {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  let start = if distance_m >= total { 0.0 } else { total - distance_m }
  slice_route(points, start, total)
}