1use crate::field::{Wind, WindField};
12use crate::{Error, Result};
13
14#[derive(Debug, Clone)]
18pub struct HeightGrid {
19 pub origin_x: f64,
21 pub origin_y: f64,
23 pub dx: f64,
25 pub dy: f64,
27 pub ncols: usize,
29 pub nrows: usize,
31 pub heights: Vec<f64>,
33}
34
35impl HeightGrid {
36 pub fn new(
38 origin_x: f64,
39 origin_y: f64,
40 dx: f64,
41 dy: f64,
42 ncols: usize,
43 nrows: usize,
44 heights: Vec<f64>,
45 ) -> Result<Self> {
46 if dx <= 0.0 || dy <= 0.0 || !dx.is_finite() || !dy.is_finite() {
47 return Err(Error::invalid("height grid", "spacing must be positive"));
48 }
49 if ncols < 2 || nrows < 2 {
50 return Err(Error::invalid("height grid", "needs at least 2x2 cells"));
51 }
52 if heights.len() != ncols * nrows {
53 return Err(Error::invalid(
54 "height grid",
55 "height count does not match the grid shape",
56 ));
57 }
58 if !heights.iter().all(|h| h.is_finite()) {
59 return Err(Error::invalid("height grid", "heights must be finite"));
60 }
61 Ok(Self {
62 origin_x,
63 origin_y,
64 dx,
65 dy,
66 ncols,
67 nrows,
68 heights,
69 })
70 }
71
72 pub fn height_at(&self, x: f64, z: f64) -> f64 {
75 let fx = ((x - self.origin_x) / self.dx).clamp(0.0, self.ncols as f64 - 1.0);
76 let fy = ((z - self.origin_y) / self.dy).clamp(0.0, self.nrows as f64 - 1.0);
77 let (j, i) = (fx.floor() as usize, fy.floor() as usize);
78 let (rx, ry) = (fx - j as f64, fy - i as f64);
79 let j1 = (j + 1).min(self.ncols - 1);
80 let i1 = (i + 1).min(self.nrows - 1);
81 let f = |i: usize, j: usize| self.heights[i * self.ncols + j];
82 (1.0 - ry) * ((1.0 - rx) * f(i, j) + rx * f(i, j1))
83 + ry * ((1.0 - rx) * f(i1, j) + rx * f(i1, j1))
84 }
85
86 pub fn from_ascii(text: &str) -> Result<Self> {
90 let mut ncols = None;
91 let mut nrows = None;
92 let mut xll = None;
93 let mut yll = None;
94 let mut cellsize = None;
95 let mut heights = Vec::new();
96 for line in text.lines() {
97 let line = line.trim();
98 if line.is_empty() {
99 continue;
100 }
101 let mut parts = line.split_whitespace();
102 match parts.next().unwrap_or("").to_ascii_lowercase().as_str() {
103 "ncols" => ncols = parse_asc_num(parts.next()),
104 "nrows" => nrows = parse_asc_num(parts.next()),
105 "xllcorner" | "xllcenter" => xll = parse_asc_num(parts.next()),
106 "yllcorner" | "yllcenter" => yll = parse_asc_num(parts.next()),
107 "cellsize" => cellsize = parse_asc_num(parts.next()),
108 "nodata_value" => continue,
109 _ => {
110 for token in line.split_whitespace() {
111 if let Ok(v) = token.parse::<f64>() {
112 heights.push(v);
113 }
114 }
115 }
116 }
117 }
118 let ncols = ncols.ok_or_else(|| Error::invalid("ascii dem", "missing ncols"))? as usize;
119 let nrows = nrows.ok_or_else(|| Error::invalid("ascii dem", "missing nrows"))? as usize;
120 let cellsize = cellsize.ok_or_else(|| Error::invalid("ascii dem", "missing cellsize"))?;
121 let (xll, yll) = (
122 xll.ok_or_else(|| Error::invalid("ascii dem", "missing xllcorner"))?,
123 yll.ok_or_else(|| Error::invalid("ascii dem", "missing yllcorner"))?,
124 );
125 if heights.len() != ncols * nrows {
126 return Err(Error::invalid(
127 "ascii dem",
128 "height count does not match header",
129 ));
130 }
131 let mut north_up = Vec::with_capacity(heights.len());
133 for row in heights.chunks_exact(ncols).rev() {
134 north_up.extend_from_slice(row);
135 }
136 Self::new(xll, yll, cellsize, cellsize, ncols, nrows, north_up)
137 }
138}
139
140fn parse_asc_num(token: Option<&str>) -> Option<f64> {
141 token.and_then(|s| s.parse().ok())
142}
143
144#[derive(Debug, Clone)]
146pub struct TerrainFlowField {
147 grid: HeightGrid,
148 levels_agl: Vec<f64>,
150 u: Vec<f64>,
153 v: Vec<f64>,
154}
155
156impl TerrainFlowField {
157 pub fn adjust(
165 grid: HeightGrid,
166 base: &dyn WindField,
167 monitors: &[(f64, f64, f64, f64)],
168 levels_agl: &[f64],
169 obs_length_m: f64,
170 surface_decay_m: f64,
171 terrain_scale_m: f64,
172 ) -> Result<Self> {
173 if levels_agl.is_empty() || levels_agl.iter().any(|h| !h.is_finite() || *h < 0.0) {
174 return Err(Error::invalid(
175 "levels_agl",
176 "must be finite and nonnegative",
177 ));
178 }
179 if obs_length_m <= 0.0 || surface_decay_m <= 0.0 || terrain_scale_m <= 0.0 {
180 return Err(Error::invalid("terrain", "length scales must be positive"));
181 }
182 if levels_agl.is_empty() || levels_agl.iter().any(|h| !h.is_finite() || *h < 0.0) {
183 return Err(Error::invalid(
184 "levels_agl",
185 "must be finite and nonnegative",
186 ));
187 }
188 if obs_length_m <= 0.0 || surface_decay_m <= 0.0 {
189 return Err(Error::invalid("terrain", "length scales must be positive"));
190 }
191 for &(x, y, speed, dir) in monitors {
192 if !x.is_finite() || !y.is_finite() || !dir.is_finite() {
193 return Err(Error::invalid("monitor", "must be finite"));
194 }
195 if !speed.is_finite() || speed < 0.0 {
196 return Err(Error::invalid("monitor", "speed must be nonnegative"));
197 }
198 }
199 let (ncols, nrows) = (grid.ncols, grid.nrows);
200 let n = ncols * nrows;
201 let inv_dx2 = 1.0 / (grid.dx * grid.dx);
202 let inv_dy2 = 1.0 / (grid.dy * grid.dy);
203 let diag = -2.0 * (inv_dx2 + inv_dy2);
204 let omega = 1.6;
205 let mut u = Vec::with_capacity(levels_agl.len() * n);
206 let mut v = Vec::with_capacity(levels_agl.len() * n);
207 let mut phi = vec![0.0; n];
208 for &level in levels_agl {
209 let mut u0 = vec![0.0; n];
211 let mut v0 = vec![0.0; n];
212 for i in 0..nrows {
213 for j in 0..ncols {
214 let x = grid.origin_x + j as f64 * grid.dx;
215 let z = grid.origin_y + i as f64 * grid.dy;
216 let height = grid.heights[i * ncols + j] + level;
217 let bg = base.wind(0.0, [x, height, z]);
218 let (mut cx, mut cz, mut wsum) = (0.0, 0.0, 0.0);
219 for &(mx, mz, speed, dir) in monitors {
220 let mw = Wind::from_speed_direction(speed, dir);
221 let bg_m = base.wind(0.0, [mx, grid.height_at(mx, mz), mz]);
222 let dx = x - mx;
223 let dz = z - mz;
224 let weight = (-(dx * dx + dz * dz) / (obs_length_m * obs_length_m)).exp()
225 * (-level / surface_decay_m).exp();
226 cx += weight * (mw.x_mps - bg_m.x_mps);
227 cz += weight * (mw.z_mps - bg_m.z_mps);
228 wsum += weight;
229 }
230 if wsum > 0.0 {
231 let w_floor = 0.1;
234 cx /= wsum + w_floor;
235 cz /= wsum + w_floor;
236 }
237 u0[i * ncols + j] = bg.x_mps + cx;
238 v0[i * ncols + j] = bg.z_mps + cz;
239 }
240 }
241 let level_decay = (-level / terrain_scale_m).exp() / terrain_scale_m;
246 let slopes = |u0x: f64, u0z: f64, i: usize, j: usize| -> f64 {
247 let dhdx = (grid.heights[i * ncols + (j + 1).min(ncols - 1)]
248 - grid.heights[i * ncols + j.saturating_sub(1)])
249 / (2.0 * grid.dx);
250 let dhdz = (grid.heights[(i + 1).min(nrows - 1) * ncols + j]
251 - grid.heights[i.saturating_sub(1) * ncols + j])
252 / (2.0 * grid.dy);
253 u0x * dhdx + u0z * dhdz
254 };
255 phi.fill(0.0);
256 for _ in 0..4000 {
257 let mut max_step = 0.0_f64;
258 for i in 1..nrows - 1 {
259 for j in 1..ncols - 1 {
260 let ip = (i + 1).min(nrows - 1);
261 let im = i.saturating_sub(1);
262 let jp = (j + 1).min(ncols - 1);
263 let jm = j.saturating_sub(1);
264 let lap = (phi[i * ncols + jp] + phi[i * ncols + jm]) * inv_dx2
265 + (phi[ip * ncols + j] + phi[im * ncols + j]) * inv_dy2
266 + diag * phi[i * ncols + j];
267 let div = (u0[i * ncols + jp] - u0[i * ncols + jm]) / (2.0 * grid.dx)
268 + (v0[ip * ncols + j] - v0[im * ncols + j]) / (2.0 * grid.dy);
269 let rhs =
270 div - slopes(u0[i * ncols + j], v0[i * ncols + j], i, j) * level_decay;
271 let updated = phi[i * ncols + j] + omega * (rhs - lap) / diag;
272 max_step = max_step.max((updated - phi[i * ncols + j]).abs());
273 phi[i * ncols + j] = updated;
274 }
275 }
276 if max_step < 1e-6 {
277 break;
278 }
279 }
280 for i in 0..nrows {
281 for j in 0..ncols {
282 let edge = i == 0 || i == nrows - 1 || j == 0 || j == ncols - 1;
283 if edge {
284 u.push(u0[i * ncols + j]);
287 v.push(v0[i * ncols + j]);
288 continue;
289 }
290 let jp = (j + 1).min(ncols - 1);
291 let jm = j.saturating_sub(1);
292 let ip = (i + 1).min(nrows - 1);
293 let im = i.saturating_sub(1);
294 let gx = (phi[i * ncols + jp] - phi[i * ncols + jm]) / (2.0 * grid.dx);
295 let gz = (phi[ip * ncols + j] - phi[im * ncols + j]) / (2.0 * grid.dy);
296 u.push(u0[i * ncols + j] - gx);
297 v.push(v0[i * ncols + j] - gz);
298 }
299 }
300 }
301 Ok(Self {
302 grid,
303 levels_agl: levels_agl.to_vec(),
304 u,
305 v,
306 })
307 }
308
309 fn sample_level(&self, level: usize, x: f64, z: f64) -> (f64, f64) {
310 let (ncols, nrows) = (self.grid.ncols, self.grid.nrows);
311 let fx = ((x - self.grid.origin_x) / self.grid.dx).clamp(0.0, ncols as f64 - 1.0);
312 let fy = ((z - self.grid.origin_y) / self.grid.dy).clamp(0.0, nrows as f64 - 1.0);
313 let (j, i) = (fx.floor() as usize, fy.floor() as usize);
314 let (rx, ry) = (fx - j as f64, fy - i as f64);
315 let j1 = (j + 1).min(ncols - 1);
316 let i1 = (i + 1).min(nrows - 1);
317 let base = level * ncols * nrows;
318 let g = |arr: &[f64], i: usize, j: usize| arr[base + i * ncols + j];
319 let sample = |arr: &[f64]| {
320 (1.0 - ry) * ((1.0 - rx) * g(arr, i, j) + rx * g(arr, i, j1))
321 + ry * ((1.0 - rx) * g(arr, i1, j) + rx * g(arr, i1, j1))
322 };
323 (sample(&self.u), sample(&self.v))
324 }
325
326 pub fn horizontal_at(&self, x: f64, y: f64, z: f64) -> (f64, f64) {
328 let agl = y - self.grid.height_at(x, z);
329 let mut level = 0;
330 for (k, &h) in self.levels_agl.iter().enumerate() {
331 if agl <= h {
332 level = k;
333 break;
334 }
335 level = k;
336 }
337 self.sample_level(level, x, z)
338 }
339}
340
341impl WindField for TerrainFlowField {
342 fn wind(&self, _time_s: f64, position_m: [f64; 3]) -> Wind {
343 let (u, v) = self.horizontal_at(position_m[0], position_m[1], position_m[2]);
344 Wind {
345 x_mps: u,
346 y_mps: 0.0,
347 z_mps: v,
348 }
349 }
350}