Received power at each grid cell
32-bit in partThe received power at every cell around the transmitter and the share of cells at or above the receiver's sensitivity.
Pr = Pt + Gt + Gr − (FSPL + J(v_max) + other); covered = cells with Pr ≥ sensitivity ÷ known cells
The power is computed in 64-bit and written into a Float32Array. The covered share compares the 64-bit value with the sensitivity, while code that reads the grid afterwards compares the 32-bit value, so a cell within rounding of the threshold can be counted one way and drawn the other. Cell positions are x₀ + i × step, one multiplication each.
/**
* The received power from a transmitter at every point of a square grid around it, over the terrain. `ground(x, y)` → m or NaN (unknown: the cell is left out).
* Each cell: free-space loss + the knife-edge loss of the worst obstruction on the straight path (the main-edge method; several edges add more loss in
* reality, so the picture is a little optimistic where the path crosses many ridges). Antennas stand `tx.h` and `rxH` above the ground, the earth bulge uses k.
* coverage(ground, tx{x,y,h}, { f (GHz), pt, gt, gr (dBi), sensitivity (dBm), rxH, radius (m), step (m), k, clutter, samples, other }) → { nx, ny, x0, y0, step, pr: Float32Array (NaN unknown), covered (fraction of known cells), cells }
*/
export function coverage(ground, tx, { f = 2, pt = 30, gt = 10, gr = 0, sensitivity = -90, rxH = 2, radius = 5000, step = 100, k = 4 / 3, clutter = 0, samples = 40, other = 0 } = {}) {
const lam = C / (f * 1000), n = Math.max(1, Math.floor(radius / step)), nx = 2 * n + 1, pr = new Float32Array(nx * nx).fill(NaN), x0 = tx.x - n * step, y0 = tx.y - n * step;
const gz = ground(tx.x, tx.y); if (!Number.isFinite(gz)) return { nx, ny: nx, x0, y0, step, pr, covered: 0, cells: 0 };
const zA = gz + tx.h; let known = 0, ok = 0;
for (let j = 0; j < nx; j++) for (let i = 0; i < nx; i++) {
const x = x0 + i * step, y = y0 + j * step, D = Math.hypot(x - tx.x, y - tx.y);
if (D > radius + 1e-9) continue;
const z = ground(x, y); if (!Number.isFinite(z)) continue;
known++;
const dKm = Math.max(D, step / 4) / 1000, zB = z + rxH, m = Math.max(2, Math.min(samples, Math.ceil(D / step * 2)));
let vmax = -Infinity;
for (let s = 1; s < m; s++) {
const u = s / m, px = tx.x + (x - tx.x) * u, py = tx.y + (y - tx.y) * u, gp = ground(px, py); if (!Number.isFinite(gp)) continue;
const d1 = D * u, d2 = D - d1, line = zA + (zB - zA) * u;
const bulge = ((d1 / 1000) * (d2 / 1000)) / (12.74 * k), hh = gp + clutter + bulge - line, v = diffractionV(hh, d1, d2, lam);
if (v > vmax) vmax = v;
}
const loss = fspl(dKm, f * 1000) + (vmax > -Infinity ? knifeEdgeLoss(vmax) : 0) + other, p = pt + gt + gr - loss;
pr[i + nx * j] = p; if (p >= sensitivity) ok++;
}
return { nx, ny: nx, x0, y0, step, pr, covered: known ? ok / known : 0, cells: known };
}