///|
pub fn csv_escape(value : String) -> String {
if value.contains(",") || value.contains("\"") || value.contains("\n") {
"\"" + value.replace_all(old="\"", new="\"\"") + "\""
} else {
value
}
}
///|
pub fn samples_to_tsv(samples : ArrayView[FieldSample]) -> String {
let rows : Array[String] = ["lat\tlon\televation_m\tvalue\tname"]
for sample in samples {
rows.push(
"\{sample.point.lat}\t\{sample.point.lon}\t\{sample.point.elevation_m}\t\{sample.value}\t\{sample.name}",
)
}
rows.join("\n")
}
///|
pub fn profile_to_geojson(
profile : ArrayView[ProfileSample],
name? : String = "profile",
) -> String {
let coordinates : Array[String] = []
for sample in profile {
coordinates.push(
"[\{sample.point.lon},\{sample.point.lat},\{sample.point.elevation_m}]",
)
}
"{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[\{coordinates.join(",")}]},\"properties\":{\"name\":\"\{escape_json_string(name)}\",\"samples\":\{profile.length()}}}]}"
}
///|
pub fn grid_to_csv(grid : Grid) -> String {
let rows : Array[String] = ["row,col,lat,lon,value"]
for cell in grid.cells() {
rows.push(
"\{cell.row},\{cell.col},\{cell.point.lat},\{cell.point.lon},\{cell.value}",
)
}
rows.join("\n")
}
///|
pub fn grid_to_xyz(grid : Grid) -> String {
let rows : Array[String] = []
for cell in grid.cells() {
rows.push("\{cell.point.lon} \{cell.point.lat} \{cell.value}")
}
rows.join("\n")
}
///|
pub fn samples_to_geojson_lines(
features : ArrayView[PolylineFeature],
) -> String {
let encoded : Array[String] = []
for feature in features {
let coordinates : Array[String] = []
for point in feature.points {
coordinates.push("[\{point.lon},\{point.lat},\{point.elevation_m}]")
}
encoded.push(
"{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[\{coordinates.join(",")}]},\"properties\":{\"name\":\"\{escape_json_string(feature.name)}\"}}",
)
}
"{\"type\":\"FeatureCollection\",\"features\":[\{encoded.join(",")}] }".replace_all(
old="] }",
new="]}",
)
}
///|
pub fn samples_filter_name(
samples : ArrayView[FieldSample],
name : String,
) -> Array[FieldSample] {
samples.filter(fn(sample) { sample.name == name })
}
///|
pub fn samples_map_value(
samples : ArrayView[FieldSample],
transform : (Double) -> Double,
) -> Array[FieldSample] {
let output : Array[FieldSample] = []
for sample in samples {
output.push({ ..sample, value: transform(sample.value) })
}
output
}
///|
pub fn samples_translate(
samples : ArrayView[FieldSample],
north_m : Double,
east_m : Double,
) -> Array[FieldSample] {
let output : Array[FieldSample] = []
for sample in samples {
output.push({
..sample,
point: from_local_enu(sample.point, { x: east_m, y: north_m, z: 0.0 }),
})
}
output
}
///|
pub fn samples_sort_by_distance(
samples : ArrayView[FieldSample],
origin : GeoPoint,
) -> Array[FieldSample] {
let output = samples.to_owned()
output.sort_by(fn(a, b) {
haversine_distance_m(origin, a.point).compare(
haversine_distance_m(origin, b.point),
)
})
output
}
///|
pub fn samples_deduplicate(
samples : ArrayView[FieldSample],
tolerance_m : Double,
) -> Array[FieldSample] {
let output : Array[FieldSample] = []
for sample in samples {
let mut duplicate = false
for existing in output {
if point_approximately_equal(sample.point, existing.point, tolerance_m) {
duplicate = true
}
}
if !duplicate {
output.push(sample)
}
}
output
}
///|
pub fn samples_bounds(samples : ArrayView[FieldSample]) -> GridExtent {
let mut min_lat = 0.0
let mut max_lat = 0.0
let mut min_lon = 0.0
let mut max_lon = 0.0
let mut found = false
for sample in samples {
if valid_point(sample.point) {
if !found {
min_lat = sample.point.lat
max_lat = sample.point.lat
min_lon = sample.point.lon
max_lon = sample.point.lon
found = true
} else {
min_lat = Double::min(min_lat, sample.point.lat)
max_lat = Double::max(max_lat, sample.point.lat)
min_lon = Double::min(min_lon, sample.point.lon)
max_lon = Double::max(max_lon, sample.point.lon)
}
}
}
{ min_lat, max_lat, min_lon, max_lon }
}
///|
pub fn samples_to_field_series(
samples : ArrayView[FieldSample],
origin : GeoPoint,
name? : String = "samples",
) -> FieldSeries {
let ordered = samples_sort_by_distance(samples, origin)
let points : Array[SeriesPoint] = []
for sample in ordered {
points.push({
coordinate: haversine_distance_m(origin, sample.point),
value: sample.value,
flag: if is_finite(sample.value) {
Good
} else {
Missing
},
})
}
{ name, points }
}
///|
pub fn field_series_to_csv(series : FieldSeries) -> String {
let rows : Array[String] = ["coordinate,value,flag"]
for point in series.points {
rows.push("\{point.coordinate},\{point.value},\{quality_name(point.flag)}")
}
rows.join("\n")
}