///|
pub fn regular_sample_design(
  extent : GridExtent,
  rows : Int,
  cols : Int,
) -> SampleDesign {
  let safe_rows = Int::max(1, rows)
  let safe_cols = Int::max(1, cols)
  let lat_step = if safe_rows == 1 {
    0.0
  } else {
    (extent.max_lat - extent.min_lat) / Double::from_int(safe_rows - 1)
  }
  let lon_step = if safe_cols == 1 {
    0.0
  } else {
    (extent.max_lon - extent.min_lon) / Double::from_int(safe_cols - 1)
  }
  let points : Array[GeoPoint] = []
  for row in 0.. Array[GeoPoint] {
  regular_sample_design(extent, rows, cols).points
}

///|
pub fn sample_design_grid(
  design : SampleDesign,
  values : ArrayView[Double],
) -> Grid {
  let output : Array[Double] = []
  for i in 0.. Array[GeoPoint] {
  profile_points(start, finish, Int::max(0, count))
}

///|
pub fn transect_sample_by_spacing(
  start : GeoPoint,
  finish : GeoPoint,
  spacing_m : Double,
) -> Array[GeoPoint] {
  geodesic_polyline(start, finish, spacing_m)
}

///|
pub fn radial_sample_points(
  center : GeoPoint,
  radius_m : Double,
  count : Int,
) -> Array[GeoPoint] {
  let output : Array[GeoPoint] = []
  let safe = Int::max(0, count)
  if safe > 0 {
    for i in 0.. Array[GeoPoint] {
  let output : Array[GeoPoint] = [center]
  for radius in radii_m {
    for point in radial_sample_points(center, radius, count_per_ring) {
      output.push(point)
    }
  }
  output
}

///|
pub fn fibonacci_sample_points(
  center : GeoPoint,
  radius_m : Double,
  count : Int,
) -> Array[GeoPoint] {
  let output : Array[GeoPoint] = []
  let safe = Int::max(0, count)
  let golden_angle = 137.50776405003785
  for i in 0.. Array[GeoPoint] {
  points.filter(fn(point) {
    point.lat >= extent.min_lat &&
    point.lat <= extent.max_lat &&
    point.lon >= extent.min_lon &&
    point.lon <= extent.max_lon
  })
}

///|
pub fn sample_points_near(
  points : ArrayView[GeoPoint],
  center : GeoPoint,
  radius_m : Double,
) -> Array[GeoPoint] {
  points.filter(fn(point) {
    radius_m <= 0.0 || haversine_distance_m(center, point) <= radius_m
  })
}

///|
pub fn sample_points_deduplicate(
  points : ArrayView[GeoPoint],
  tolerance_m : Double,
) -> Array[GeoPoint] {
  let output : Array[GeoPoint] = []
  for point in points {
    let mut duplicate = false
    for existing in output {
      if point_approximately_equal(point, existing, tolerance_m) {
        duplicate = true
      }
    }
    if !duplicate {
      output.push(point)
    }
  }
  output
}

///|
pub fn sample_points_sort_by_distance(
  points : ArrayView[GeoPoint],
  origin : GeoPoint,
) -> Array[GeoPoint] {
  let output = points.to_owned()
  output.sort_by(fn(left, right) {
    haversine_distance_m(origin, left).compare(
      haversine_distance_m(origin, right),
    )
  })
  output
}

///|
pub fn sample_point_distances(
  origin : GeoPoint,
  points : ArrayView[GeoPoint],
) -> Array[Double] {
  points.map(fn(point) { haversine_distance_m(origin, point) })
}

///|
pub fn route_distances(points : ArrayView[GeoPoint]) -> Array[Double] {
  let output : Array[Double] = []
  let mut total = 0.0
  if points.length() > 0 {
    output.push(0.0)
    for i in 1.. Double {
  match route_distances(points).last() {
    Some(value) => value
    None => 0.0
  }
}

///|
pub fn route_bearings(points : ArrayView[GeoPoint]) -> Array[Double] {
  let output : Array[Double] = []
  if points.length() > 0 {
    output.push(0.0)
    for i in 1.. Array[GeoPoint] {
  let edge = grid.edge_strength()
  let output : Array[GeoPoint] = []
  for cell in edge.cells() {
    if is_finite(cell.value) && cell.value >= threshold {
      output.push(cell.point)
    }
  }
  output
}

///|
pub fn sample_points_from_grid_percentile(
  grid : Grid,
  fraction : Double,
) -> Array[GeoPoint] {
  let threshold = percentile(grid.values, fraction)
  let output : Array[GeoPoint] = []
  for cell in grid.cells() {
    if is_finite(cell.value) && cell.value >= threshold {
      output.push(cell.point)
    }
  }
  output
}

///|
pub fn samples_to_design(
  points : ArrayView[GeoPoint],
  rows : Int,
  cols : Int,
) -> SampleDesign {
  let safe_rows = Int::max(1, rows)
  let safe_cols = Int::max(1, cols)
  let extent = if points.length() == 0 {
    { min_lat: 0.0, max_lat: 0.0, min_lon: 0.0, max_lon: 0.0 }
  } else {
    let mut min_lat = points[0].lat
    let mut max_lat = points[0].lat
    let mut min_lon = points[0].lon
    let mut max_lon = points[0].lon
    for point in points {
      min_lat = Double::min(min_lat, point.lat)
      max_lat = Double::max(max_lat, point.lat)
      min_lon = Double::min(min_lon, point.lon)
      max_lon = Double::max(max_lon, point.lon)
    }
    { min_lat, max_lat, min_lon, max_lon }
  }
  regular_sample_design(extent, safe_rows, safe_cols)
}

///|
pub fn sample_design_extent(design : SampleDesign) -> GridExtent {
  if design.points.length() == 0 {
    { min_lat: 0.0, max_lat: 0.0, min_lon: 0.0, max_lon: 0.0 }
  } else {
    let first = design.points[0]
    let last = design.points[design.points.length() - 1]
    {
      min_lat: Double::min(first.lat, last.lat),
      max_lat: Double::max(first.lat, last.lat),
      min_lon: Double::min(first.lon, last.lon),
      max_lon: Double::max(first.lon, last.lon),
    }
  }
}

///|
pub fn sample_design_is_rectangular(design : SampleDesign) -> Bool {
  design.rows > 0 &&
  design.cols > 0 &&
  design.points.length() == design.rows * design.cols
}

///|
pub fn sample_design_points_at_row(
  design : SampleDesign,
  row : Int,
) -> Array[GeoPoint] {
  if row < 0 || row >= design.rows {
    []
  } else {
    let output : Array[GeoPoint] = []
    for col in 0.. Array[GeoPoint] {
  if col < 0 || col >= design.cols {
    []
  } else {
    let output : Array[GeoPoint] = []
    for row in 0.. SampleDesign {
  {
    ..design,
    points: design.points.map(fn(point) {
      from_local_enu(point, { x: east_m, y: north_m, z: 0.0 })
    }),
  }
}

///|
pub fn sample_design_to_samples(
  design : SampleDesign,
  values : ArrayView[Double],
  name : String,
) -> Array[FieldSample] {
  let output : Array[FieldSample] = []
  for i in 0..