///|
fn parse_cells(line : String, separator : String) -> Array[String] {
let output : Array[String] = []
for cell in line.split(separator) {
output.push(cell.trim().to_owned())
}
output
}
///|
pub fn parse_field_samples_tsv(text : String) -> Array[FieldSample] raise {
let output : Array[FieldSample] = []
for raw in text.split("\n") {
let line = raw.trim()
if line.is_empty() || line.has_prefix("lat") || line.has_prefix("latitude") {
()
} else {
let cells = parse_cells(line.to_owned(), "\t")
if cells.length() >= 4 {
let lat = parse_double_cell(cells[0])
let lon = parse_double_cell(cells[1])
let elevation_m = parse_double_cell(cells[2])
let value = parse_double_cell(cells[3])
let name = if cells.length() > 4 { cells[4] } else { "sample" }
output.push({ point: { lat, lon, elevation_m }, value, name })
}
}
}
output
}
///|
/// Parse whitespace-separated XYZ rows: longitude latitude value, optionally elevation.
pub fn parse_xyz_samples(
text : String,
name? : String = "xyz",
) -> Array[FieldSample] raise {
let output : Array[FieldSample] = []
for raw in text.split("\n") {
let line = raw.trim()
if line.is_empty() || line.has_prefix("#") {
()
} else {
let cells : Array[String] = []
for cell in line.split(" ") {
let value = cell.trim()
if !value.is_empty() {
cells.push(value.to_owned())
}
}
if cells.length() >= 3 {
let lon = parse_double_cell(cells[0])
let lat = parse_double_cell(cells[1])
let elevation_m = if cells.length() >= 4 {
parse_double_cell(cells[3])
} else {
0.0
}
let value = parse_double_cell(cells[2])
output.push({ point: { lat, lon, elevation_m }, value, name })
}
}
}
output
}
///|
pub fn samples_to_xyz(samples : ArrayView[FieldSample]) -> String {
let rows : Array[String] = []
for sample in samples {
rows.push(
"\{sample.point.lon} \{sample.point.lat} \{sample.value} \{sample.point.elevation_m}",
)
}
rows.join("\n")
}
///|
pub fn grid_to_json(grid : Grid, name : String) -> String {
let values : Array[String] = []
for value in grid.values {
values.push(if is_finite(value) { "\{value}" } else { "null" })
}
let encoded_name = escape_json_string(name)
let values_text = values.join(",")
"{\"type\":\"regular-grid\",\"name\":\"\{encoded_name}\",\"origin_lat\":\{grid.origin_lat},\"origin_lon\":\{grid.origin_lon},\"lat_step\":\{grid.lat_step},\"lon_step\":\{grid.lon_step},\"rows\":\{grid.rows},\"cols\":\{grid.cols},\"values\":[\{values_text}]}"
}
///|
pub fn grid_metadata_csv(grid : Grid) -> String {
"origin_lat,origin_lon,lat_step,lon_step,rows,cols\n\{grid.origin_lat},\{grid.origin_lon},\{grid.lat_step},\{grid.lon_step},\{grid.rows},\{grid.cols}"
}
///|
pub fn parse_grid_values_csv(
text : String,
origin_lat : Double,
origin_lon : Double,
lat_step : Double,
lon_step : Double,
rows : Int,
cols : Int,
) -> Grid raise {
let values : Array[Double] = []
for raw in text.split("\n") {
let line = raw.trim()
if line.is_empty() || line.has_prefix("row") || line.has_prefix("lat") {
()
} else {
let cells = parse_cells(line.to_owned(), ",")
let value_cell = if cells.length() >= 5 {
cells[4]
} else {
cells.last().unwrap_or("")
}
if !value_cell.is_empty() {
values.push(parse_double_cell(value_cell))
}
}
}
make_grid(
origin_lat~,
origin_lon~,
lat_step~,
lon_step~,
rows~,
cols~,
values~,
)
}
///|
pub fn grid_to_matrix_rows(grid : Grid) -> Array[Array[Double]] {
let output : Array[Array[Double]] = []
for row in 0.. Grid {
let values : Array[Double] = []
let cols = if rows.length() == 0 { 0 } else { rows[0].length() }
for row in rows {
for col in 0.. String {
let rows : Array[String] = ["distance_m\tlat\tlon\televation_m\tvalue"]
for sample in profile {
rows.push(
"\{sample.distance_m}\t\{sample.point.lat}\t\{sample.point.lon}\t\{sample.point.elevation_m}\t\{sample.value}",
)
}
rows.join("\n")
}
///|
pub fn series_to_json(series : FieldSeries) -> String {
let points : Array[String] = []
for point in series.points {
let value = if is_finite(point.value) { "\{point.value}" } else { "null" }
points.push(
"{\"coordinate\":\{point.coordinate},\"value\":\{value},\"flag\":\"\{quality_name(point.flag)}\"}",
)
}
let series_name = escape_json_string(series.name)
let point_text = points.join(",")
"{\"name\":\"\{series_name}\",\"points\":[\{point_text}]}"
}
///|
pub fn parse_series_csv(
text : String,
name? : String = "series",
) -> FieldSeries raise {
let points : Array[SeriesPoint] = []
for raw in text.split("\n") {
let line = raw.trim()
if line.is_empty() || line.has_prefix("coordinate") {
()
} else {
let cells = parse_cells(line.to_owned(), ",")
if cells.length() >= 2 {
let coordinate = parse_double_cell(cells[0])
let value = parse_double_cell(cells[1])
points.push({
coordinate,
value,
flag: if is_finite(value) {
Good
} else {
Missing
},
})
}
}
}
{ name, points }
}
///|
pub fn parse_series_tsv(
text : String,
name? : String = "series",
) -> FieldSeries raise {
let points : Array[SeriesPoint] = []
for raw in text.split("\n") {
let line = raw.trim()
if line.is_empty() || line.has_prefix("coordinate") {
()
} else {
let cells = parse_cells(line.to_owned(), "\t")
if cells.length() >= 2 {
let coordinate = parse_double_cell(cells[0])
let value = parse_double_cell(cells[1])
points.push({ coordinate, value, flag: Good })
}
}
}
{ name, points }
}
///|
pub fn format_double_table(
values : ArrayView[Double],
separator : String,
) -> String {
let cells : Array[String] = []
for value in values {
cells.push(if is_finite(value) { "\{value}" } else { "NaN" })
}
cells.join(separator)
}
///|
pub fn csv_header(columns : ArrayView[String]) -> String {
let output : Array[String] = []
for column in columns {
output.push(csv_escape(column))
}
output.join(",")
}
///|
pub fn csv_row(values : ArrayView[String]) -> String {
let output : Array[String] = []
for value in values {
output.push(csv_escape(value))
}
output.join(",")
}
///|
pub fn samples_to_csv_safe(samples : ArrayView[FieldSample]) -> String {
let rows : Array[String] = [
csv_header(["lat", "lon", "elevation_m", "value", "name"]),
]
for sample in samples {
rows.push(
csv_row([
"\{sample.point.lat}",
"\{sample.point.lon}",
"\{sample.point.elevation_m}",
"\{sample.value}",
sample.name,
]),
)
}
rows.join("\n")
}
///|
pub fn grid_to_csv_safe(grid : Grid) -> String {
let rows : Array[String] = [csv_header(["row", "col", "lat", "lon", "value"])]
for cell in grid.cells() {
rows.push(
csv_row([
"\{cell.row}",
"\{cell.col}",
"\{cell.point.lat}",
"\{cell.point.lon}",
"\{cell.value}",
]),
)
}
rows.join("\n")
}
///|
pub fn export_samples_bundle(
samples : ArrayView[FieldSample],
name : String,
) -> String {
let encoded_name = escape_json_string(name)
let encoded_csv = escape_json_string(samples_to_csv_safe(samples))
let geojson = samples_to_geojson(samples)
"{\"name\":\"" +
encoded_name +
"\",\"count\":" +
"\{samples.length()}" +
",\"csv\":\"" +
encoded_csv +
"\",\"geojson\":" +
geojson +
"}"
}