signalk-livedepth-plugin 0.3.13 → 0.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-livedepth-plugin",
3
- "version": "0.3.13",
3
+ "version": "0.3.16",
4
4
  "description": "Build and visualise a depth map from your own soundings, live on the chart as your vessel moves.",
5
5
  "keywords": [
6
6
  "signalk-node-server-plugin",
@@ -493,7 +493,7 @@ var fieldVersion = 0;
493
493
  var surfaceCache = null;
494
494
  var outCanvas = null;
495
495
  var outCtx = null;
496
- var SURFACE_PAD = 0.25;
496
+ var SURFACE_PAD = 0.1;
497
497
  var overlayCache = null;
498
498
  var overlayCanvas = null;
499
499
  var overlayCtx = null;
@@ -548,7 +548,8 @@ function compile(ctx, type, src) {
548
548
  return shader;
549
549
  }
550
550
  function initWebgl() {
551
- if (gl) return true;
551
+ if (gl && !gl.isContextLost()) return true;
552
+ if (gl) releaseGpu();
552
553
  glCanvas = new OffscreenCanvas(1, 1);
553
554
  const msaa = settings?.msaa ?? true;
554
555
  const ctx = glCanvas.getContext("webgl2", {
@@ -557,6 +558,11 @@ function initWebgl() {
557
558
  antialias: msaa
558
559
  });
559
560
  if (!ctx) return false;
561
+ ctx.canvas.addEventListener("webglcontextlost", (event) => {
562
+ event.preventDefault();
563
+ releaseGpu();
564
+ fieldDirty = true;
565
+ });
560
566
  gpuMsaa = msaa;
561
567
  const vs = compile(ctx, ctx.VERTEX_SHADER, DEPTH_VERT_SRC);
562
568
  const fs = compile(ctx, ctx.FRAGMENT_SHADER, DEPTH_FRAG_SRC);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/web/shared/bathymetry.ts", "../../src/web/shared/shaders.ts", "../../src/web/shared/contours.ts", "../../src/web/shared/live-depth.ts", "../../src/web/shared/settings.ts", "../../src/web/render-worker.ts"],
4
- "sourcesContent": ["/** Bathymetric surface from a GPS+depth track: a shallow-biased depth raster\n * and a min-pooled level-of-detail pyramid for a WebGL renderer.\n *\n * The data is one vessel's track, not a survey, so a cell is only filled where\n * a sounding is within `coverageCells`; gaps stay NaN (no coverage) rather than\n * being invented. The fill is a distance-weighted blend with a linear falloff,\n * biased toward the shallowest sample in range by `shallowBias` (1 = never\n * deeper than that sample) so a smoothed surface can never overstate depth;\n * a contour (a sharp depth change) is then never smeared across by the fill.\n *\n * Pure functions (no Angular, no WebGL) so they can be unit tested directly.\n */\n\nimport { DepthSample } from \"./live-depth\";\n\n/** A regular depth raster in EPSG:3857 metres. */\nexport interface DepthField {\n width: number;\n height: number;\n /** [minX, minY, maxX, maxY] in metres. */\n extent: [number, number, number, number];\n /** Depth per cell in metres, row-major; NaN means no coverage. */\n depth: Float32Array;\n}\n\nexport interface BathymetryOptions {\n /** Target cell size in metres (the grid is built at about this size). */\n cellMetres: number;\n /** How far a sounding reaches, in cells. Beyond it a cell is uncovered. */\n coverageCells?: number;\n /** How far a sounding reaches, in metres, overriding `coverageCells`. Use\n * this to bridge the gaps between track samples (set it to about the sample\n * spacing) so the traversed ribbon is continuous rather than disconnected\n * dots. */\n coverageMetres?: number;\n /** Floor for the coverage radius, in metres. A sounding stands for at least\n * this area, so an anchored vessel's jittered positions (clusters a fraction\n * of a metre apart) render as one patch rather than dozens of specks. */\n coverageMinMetres?: number;\n /** Falloff exponent: 1 = linear (default), higher concentrates the value\n * nearer the sample. */\n power?: number;\n /** How far (metres) the fill may rise above the shallowest sample in range.\n * A bigger change is a contour it must not cross, so a sharp depth change is\n * capped rather than smeared over. */\n contourMetres?: number;\n /** 0 = pure distance blend, 1 = never deeper than the shallowest sample in\n * range. */\n shallowBias?: number;\n /** Hard cap on either grid dimension, to bound memory and time. */\n maxCells?: number;\n}\n\nexport const DEFAULT_COVERAGE_CELLS = 2;\n/** Floor for the coverage radius, in metres (see `coverageMinMetres`). */\nexport const DEFAULT_COVERAGE_MIN_M = 2;\nexport const DEFAULT_IDW_POWER = 1;\nexport const DEFAULT_SHALLOW_BIAS = 0.5;\nexport const DEFAULT_CONTOUR_M = 2;\nexport const DEFAULT_MAX_CELLS = 1024;\n\nconst EARTH_RADIUS = 6378137;\nconst MERCATOR_MAX_LAT = 85.05112878;\nconst RAD = Math.PI / 180;\n\nconst mercX = (lon: number) => lon * RAD * EARTH_RADIUS;\nconst mercY = (lat: number) =>\n EARTH_RADIUS *\n Math.log(\n Math.tan(\n Math.PI / 4 +\n (Math.max(-MERCATOR_MAX_LAT, Math.min(MERCATOR_MAX_LAT, lat)) * RAD) /\n 2,\n ),\n );\n\n/** Median distance (metres) between consecutive samples, in the order given\n * (assumed to be along-track). 0 with fewer than two samples. A robust measure\n * of the track's sample spacing, used to size a coverage radius that bridges\n * the gaps so the traversed ribbon is continuous. */\nexport function medianConsecutiveGap(\n samples: readonly { lon: number; lat: number }[],\n): number {\n if (samples.length < 2) {\n return 0;\n }\n const gaps: number[] = [];\n for (let i = 1; i < samples.length; i++) {\n const ax = mercX(samples[i - 1].lon);\n const ay = mercY(samples[i - 1].lat);\n gaps.push(\n Math.hypot(mercX(samples[i].lon) - ax, mercY(samples[i].lat) - ay),\n );\n }\n gaps.sort((a, b) => a - b);\n return gaps[Math.floor(gaps.length / 2)];\n}\n\n/** Cell indices a splat reached, as `[minX, minY, maxX, maxY]` (upper bounds\n * exclusive). */\nexport type CellRect = [number, number, number, number];\n\n/** A depth raster that can be extended a few samples at a time. The per-cell\n * IDW weights are kept, so a live sounding patches just the cells it reaches\n * instead of rebuilding the whole grid. */\nexport interface DepthFieldBuilder {\n readonly field: DepthField;\n /** Splat `samples` into the field, refreshing the cells they reach. Returns\n * the cell rect that changed (for a partial texture upload), or null. */\n add(samples: readonly DepthSample[]): CellRect | null;\n}\n\n/** Create an empty field over `extent` (EPSG:3857 `[minX,minY,maxX,maxY]`), or\n * null when the extent is unusable. Extend it with {@link DepthFieldBuilder.add}. */\nexport function createDepthFieldBuilder(\n extent: readonly number[],\n options: BathymetryOptions,\n): DepthFieldBuilder | null {\n const cellMetres = options.cellMetres;\n if (\n !(cellMetres > 0) ||\n extent.length !== 4 ||\n !extent.every((v) => Number.isFinite(v)) ||\n extent[2] <= extent[0] ||\n extent[3] <= extent[1]\n ) {\n return null;\n }\n const power = options.power ?? DEFAULT_IDW_POWER;\n const shallowBias = Math.max(\n 0,\n Math.min(1, options.shallowBias ?? DEFAULT_SHALLOW_BIAS),\n );\n const contourMetres = options.contourMetres ?? DEFAULT_CONTOUR_M;\n const maxCells = options.maxCells ?? DEFAULT_MAX_CELLS;\n\n const width = Math.min(\n maxCells,\n Math.ceil((extent[2] - extent[0]) / cellMetres),\n );\n const height = Math.min(\n maxCells,\n Math.ceil((extent[3] - extent[1]) / cellMetres),\n );\n if (width <= 0 || height <= 0) {\n return null;\n }\n // Re-derive the extent from the (possibly capped) grid so cells stay square.\n const minX = extent[0];\n const minY = extent[1];\n const gridExtent: [number, number, number, number] = [\n minX,\n minY,\n minX + width * cellMetres,\n minY + height * cellMetres,\n ];\n\n const depth = new Float32Array(width * height).fill(NaN);\n const wsum = new Float32Array(width * height);\n const wdsum = new Float32Array(width * height);\n const minSeen = new Float32Array(width * height).fill(Infinity);\n\n const coverageRadius = Math.max(\n cellMetres,\n options.coverageMinMetres ?? DEFAULT_COVERAGE_MIN_M,\n options.coverageMetres ??\n (options.coverageCells ?? DEFAULT_COVERAGE_CELLS) * cellMetres,\n );\n const coverage2 = coverageRadius * coverageRadius;\n // Bound the per-sample splat; a huge radius would make the build quadratic.\n const radiusCells = Math.min(64, Math.ceil(coverageRadius / cellMetres));\n\n const field: DepthField = { width, height, extent: gridExtent, depth };\n\n /** Recompute `depth` over the half-open cell rect from the accumulated\n * weights. */\n function refresh([rx0, ry0, rx1, ry1]: CellRect): void {\n for (let gy = ry0; gy < ry1; gy++) {\n const row = gy * width;\n for (let gx = rx0; gx < rx1; gx++) {\n const i = row + gx;\n const w = wsum[i];\n if (w <= 0) {\n depth[i] = NaN;\n continue;\n }\n const idw = wdsum[i] / w;\n const shallowest = minSeen[i];\n // Bias toward the shallowest sample in range, then cap the result so\n // the fill can never be more than `contourMetres` deeper than the\n // shallowest measured sample: a sharp depth change is a contour the\n // fill does not cross, while a gentle slope blends linearly.\n const blended =\n (1 - shallowBias) * idw + shallowBias * Math.min(idw, shallowest);\n depth[i] = Math.min(blended, shallowest + contourMetres);\n }\n }\n }\n\n const isUsable = (s: DepthSample): boolean =>\n Number.isFinite(s.lon) &&\n Number.isFinite(s.lat) &&\n Number.isFinite(s.depth) &&\n s.depth >= 0;\n\n function add(samples: readonly DepthSample[]): CellRect | null {\n let rect: CellRect | null = null;\n const grow = (gx: number, gy: number) => {\n if (!rect) {\n rect = [gx, gy, gx + 1, gy + 1];\n return;\n }\n if (gx < rect[0]) rect[0] = gx;\n if (gy < rect[1]) rect[1] = gy;\n if (gx + 1 > rect[2]) rect[2] = gx + 1;\n if (gy + 1 > rect[3]) rect[3] = gy + 1;\n };\n for (const s of samples) {\n if (!isUsable(s)) {\n continue;\n }\n const sx = mercX(s.lon);\n const sy = mercY(s.lat);\n const cx = Math.floor((sx - minX) / cellMetres);\n const cy = Math.floor((sy - minY) / cellMetres);\n for (let dy = -radiusCells; dy <= radiusCells; dy++) {\n const gy = cy + dy;\n if (gy < 0 || gy >= height) {\n continue;\n }\n const cellCenterY = minY + (gy + 0.5) * cellMetres;\n const ddy = cellCenterY - sy;\n for (let dx = -radiusCells; dx <= radiusCells; dx++) {\n const gx = cx + dx;\n if (gx < 0 || gx >= width) {\n continue;\n }\n const cellCenterX = minX + (gx + 0.5) * cellMetres;\n const ddx = cellCenterX - sx;\n const dist2 = ddx * ddx + ddy * ddy;\n if (dist2 > coverage2) {\n continue;\n }\n const idx = gy * width + gx;\n // Linear falloff: 1 at the sounding, 0 at the coverage edge. This\n // spreads a sample into empty water over `coverageRadius` with a\n // gradient rather than clustering the value around the nearest\n // sample, so adjacent tracks blend linearly across the gap. `power`\n // (default 1 = linear) shapes the ramp.\n const w = Math.pow(1 - Math.sqrt(dist2) / coverageRadius, power);\n wsum[idx] += w;\n wdsum[idx] += w * s.depth;\n if (s.depth < minSeen[idx]) {\n minSeen[idx] = s.depth;\n }\n grow(gx, gy);\n }\n }\n }\n if (rect) refresh(rect);\n return rect;\n }\n\n return { field, add };\n}\n\n/** Build a depth raster over `extent` from the samples in one pass, or null\n * when there is nothing to draw or the grid is unusable. */\nexport function buildDepthField(\n samples: readonly DepthSample[],\n extent: readonly number[],\n options: BathymetryOptions,\n): DepthField | null {\n const builder = createDepthFieldBuilder(extent, options);\n if (!builder) {\n return null;\n }\n builder.add(samples);\n return builder.field;\n}\n", "/** GLSL for the LiveDepth bathymetric surface.\n *\n * A full-screen quad covering the current viewport samples the depth field\n * (`uDepth`, an R32F texture, -1 = no coverage) through a view transform\n * (`uView`, `uField`, both in EPSG:3857 metres), shades discrete depth bands up\n * to `uShadeLimit` using a colour ramp (`uLut`), and draws an isobar wherever\n * the depth crosses a multiple of `uInterval`. Deeper than the shade limit (or\n * uncovered) is transparent, so the chart shows through; isobars are still\n * drawn over unshaded data.\n *\n * The field is built once per data fetch and sampled here, so the surface \u2014 and\n * therefore the absolute-depth isobars \u2014 does not change as the view is panned\n * or zoomed. The depth is interpolated by hand (bilinear) rather than relying\n * on a LINEAR filter: a float texture is only filterable with\n * OES_texture_float_linear, which many GPUs (e.g. Firefox on some hardware) do\n * not expose, and an unfilterable LINEAR texture is incomplete and samples as\n * nothing.\n */\n\nexport const DEPTH_VERT_SRC = `#version 300 es\nin vec2 aPos;\nout vec2 vUv;\nvoid main() { vUv = aPos * 0.5 + 0.5; gl_Position = vec4(aPos, 0.0, 1.0); }`;\n\nexport const DEPTH_FRAG_SRC = `#version 300 es\nprecision highp float;\nin vec2 vUv;\nout vec4 fragColor;\nuniform sampler2D uDepth;\nuniform sampler2D uLut;\nuniform vec2 uTexel; // 1 / field size in texels\nuniform vec4 uView; // viewport: origin.xy, size.xy (metres)\nuniform vec4 uField; // field: origin.xy, size.xy (metres)\nuniform float uShadeLimit;\nuniform int uLevels;\nuniform float uInterval;\nuniform float uLineWidth;\nuniform float uIsobars; // 1 = per-fragment isobars, 0 = vector contours drawn by the runtime\nbool uncov(float v) { return v < 0.0; } // -1: no coverage\nfloat sampleDepth(vec2 uv) {\n vec2 fc = uv / uTexel - 0.5;\n vec2 f = fract(fc);\n vec2 b = (floor(fc) + 0.5) * uTexel;\n float d00 = texture(uDepth, b).r;\n float d10 = texture(uDepth, b + vec2(uTexel.x, 0.0)).r;\n float d01 = texture(uDepth, b + vec2(0.0, uTexel.y)).r;\n float d11 = texture(uDepth, b + uTexel).r;\n if (uncov(d00) || uncov(d10) || uncov(d01) || uncov(d11)) {\n return texture(uDepth, uv).r; // near an edge/gap: no interpolation\n }\n return mix(mix(d00, d10, f.x), mix(d01, d11, f.x), f.y);\n}\nvoid main() {\n // viewport pixel -> world metres -> field uv\n vec2 world = uView.xy + vUv * uView.zw;\n vec2 fuv = (world - uField.xy) / uField.zw;\n if (fuv.x < 0.0 || fuv.x > 1.0 || fuv.y < 0.0 || fuv.y > 1.0) { discard; }\n float d = sampleDepth(fuv);\n if (uncov(d)) { discard; }\n // Shading bands, only up to the shade limit; deeper water is left to the chart.\n bool shaded = d < uShadeLimit;\n vec3 fill = vec3(0.0);\n if (shaded) {\n float bandF = d / uShadeLimit * float(uLevels);\n float band = floor(bandF);\n float tCenter = (band + 0.5) / float(uLevels);\n fill = texture(uLut, vec2(clamp(tCenter, 0.0, 1.0), 0.5)).rgb;\n }\n // Isobars at every absolute multiple of the interval, over ALL data. The\n // runtime can disable these and draw vector contours instead.\n float line = 0.0;\n if (uIsobars > 0.5) {\n float ci = d / uInterval;\n float dv = min(fract(ci), 1.0 - fract(ci));\n float px = dv / max(fwidth(ci), 1e-6);\n line = 1.0 - smoothstep(uLineWidth, uLineWidth + 1.5, px);\n }\n vec3 col = mix(fill, vec3(0.0), line);\n float alpha = max(line, shaded ? 1.0 : 0.0);\n fragColor = vec4(col, alpha);\n}`;\n", "/** Vector isobars from a depth field, via marching squares.\n *\n * The field is a raster, so the per-fragment isobars it can shade are limited by\n * its resolution. Tracing the same crossings as geometry instead gives lines\n * that stay crisp at any zoom (the vector-tile look). Segments are in the\n * field's EPSG:3857 metres; the renderer maps them to pixels. Cells with an\n * uncovered corner are skipped, so a contour stops at the data edge.\n *\n * Each cell emits only the contour levels it actually straddles, so the cost\n * scales with the number of crossings rather than cells \u00D7 levels.\n *\n * Levels are walked at the finer of the two intervals; a level that also lands\n * on the major grid is returned as a major (index) contour, the rest as minor.\n */\n\nimport type { DepthField } from \"./bathymetry.js\";\n\n/** One contour segment in world metres: `[x0, y0, x1, y1]`. */\nexport type ContourSegment = [number, number, number, number];\n\n/** Major (index) and minor contours of a field. */\nexport interface ContourSet {\n major: ContourSegment[];\n minor: ContourSegment[];\n}\n\n/** Whether `level` is a whole multiple of `step` (within float tolerance). */\nfunction onGrid(level: number, step: number): boolean {\n const n = level / step;\n return Math.abs(n - Math.round(n)) < 1e-6;\n}\n\n/** Marching-squares contours of `field`: every multiple of `minorStep` is a\n * minor contour, every multiple of `majorStep` a major one, up to `maxLevel`\n * (inclusive). A level on both grids is major. */\nexport function extractContours(\n field: DepthField,\n minorStep: number,\n majorStep: number,\n maxLevel: number,\n): ContourSet {\n const { width, height, depth, extent } = field;\n const result: ContourSet = { major: [], minor: [] };\n const minor = minorStep > 0 ? minorStep : 1;\n const major = majorStep > 0 ? majorStep : minor;\n if (width < 2 || height < 2) return result;\n const cell = (extent[2] - extent[0]) / width;\n const at = (gx: number, gy: number) => depth[gy * width + gx];\n // Corner offsets around a cell, in order (0,0) (1,0) (1,1) (0,1).\n const ox = [0, 1, 1, 0];\n const oy = [0, 0, 1, 1];\n // Walk at the finer interval so both grids are covered.\n const step = Math.min(minor, major);\n\n for (let gy = 0; gy < height - 1; gy++) {\n for (let gx = 0; gx < width - 1; gx++) {\n const d = [\n at(gx, gy),\n at(gx + 1, gy),\n at(gx + 1, gy + 1),\n at(gx, gy + 1),\n ];\n if (\n !Number.isFinite(d[0]) ||\n !Number.isFinite(d[1]) ||\n !Number.isFinite(d[2]) ||\n !Number.isFinite(d[3])\n ) {\n continue;\n }\n const lo = Math.min(d[0], d[1], d[2], d[3]);\n const hi = Math.max(d[0], d[1], d[2], d[3]);\n if (hi <= lo) continue;\n // The first contour level strictly inside (lo, hi), then every step up.\n const first = Math.floor(lo / step) * step + step;\n for (let level = first; level < hi && level <= maxLevel; level += step) {\n if (level <= lo) continue;\n const out = onGrid(level, major) ? result.major : result.minor;\n // Edge i runs corner i -> i+1 (0:(0-1) 1:(1-2) 2:(2-3) 3:(3-0)).\n const cross: Array<[number, number] | null> = [null, null, null, null];\n const crossed: number[] = [];\n for (let i = 0; i < 4; i++) {\n const j = (i + 1) % 4;\n if (d[i] >= level === d[j] >= level) {\n continue;\n }\n const t = (level - d[i]) / (d[j] - d[i]);\n cross[i] = [\n extent[0] + (gx + ox[i] + (ox[j] - ox[i]) * t) * cell,\n extent[1] + (gy + oy[i] + (oy[j] - oy[i]) * t) * cell,\n ];\n crossed.push(i);\n }\n if (crossed.length === 2) {\n const [a, b] = crossed;\n out.push([...cross[a]!, ...cross[b]!]);\n } else if (crossed.length === 4) {\n // Ambiguous saddle: split by the cell-centre average.\n const centre = (d[0] + d[1] + d[2] + d[3]) / 4;\n if (centre >= level) {\n out.push([...cross[0]!, ...cross[1]!]);\n out.push([...cross[2]!, ...cross[3]!]);\n } else {\n out.push([...cross[0]!, ...cross[3]!]);\n out.push([...cross[1]!, ...cross[2]!]);\n }\n }\n }\n }\n }\n return result;\n}\n", "/** Live depth layer: pure helpers turning depth-at-position samples (v2 Track\n * API `properties`) into a safe, drawable aggregate.\n *\n * The overriding rule is safety: a rendered mark must never display deeper than\n * the shallowest sample it covers. Every aggregate therefore takes the\n * MINIMUM depth \u2014 never a mean, which would let a shoal vanish into deeper\n * water \u2014 and the layer draws shallower marks over deeper ones. Samples at or\n * below a shoal threshold are returned individually so a critical minimum is\n * never smeared to a cell centre.\n *\n * Pure functions (no Angular, no map) so they can be unit tested directly.\n */\n\n/** A `[lon, lat]` position. */\nexport type Position = [number, number];\n\n/** A URL query string from the given params, skipping absent or empty ones. */\nfunction queryString(\n params: Record<string, string | number | undefined>,\n): string {\n return Object.entries(params)\n .filter(([, v]) => v !== undefined && v !== \"\")\n .map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`)\n .join(\"&\");\n}\n\n/** A recorded depth at a position, depth in metres (canonical Signal K). */\nexport interface DepthSample {\n lon: number;\n lat: number;\n depth: number;\n}\n\n/** One grid cell of the aggregate. */\nexport interface DepthCell {\n /** Grid indices (Web Mercator cell coordinates), for neighbour lookup. */\n ix: number;\n iy: number;\n /** Cell centre (mean of the samples in it), lon/lat. */\n lon: number;\n lat: number;\n /** Shallowest depth among the cell's samples, in metres. */\n depth: number;\n /** How many samples fell in the cell. */\n count: number;\n}\n\n/** One drawn circle: a group of adjacent cells at a similar depth, merged. */\nexport interface DepthCluster {\n /** Circle centre (weighted centroid), lon/lat. */\n lon: number;\n lat: number;\n /** Shallowest depth in the group, in metres. */\n depth: number;\n /** Samples the group covers. */\n count: number;\n /** Spatial radius of the group, in metres (Web Mercator). */\n radiusMetres: number;\n}\n\n/** Depth (metres) the shading runs to by default: at this depth and beyond the\n * layer draws nothing, so the chart shows through. */\nexport const DEFAULT_SHADE_LIMIT_M = 10;\n\n/** Default shoal threshold (metres): samples at or below this are marked\n * individually. Matches the S-57 renderer's default safety depth. */\nexport const DEFAULT_SAFETY_DEPTH_M = 3;\n\nconst EARTH_RADIUS = 6378137;\nconst MERCATOR_MAX_LAT = 85.05112878;\nconst RAD = Math.PI / 180;\n\n/** Web Mercator easting (metres) of a longitude. */\nexport function mercX(lon: number): number {\n return lon * RAD * EARTH_RADIUS;\n}\n\n/** Web Mercator northing (metres) of a latitude, clamped to the projection. */\nexport function mercY(lat: number): number {\n const c = Math.max(-MERCATOR_MAX_LAT, Math.min(MERCATOR_MAX_LAT, lat));\n return EARTH_RADIUS * Math.log(Math.tan(Math.PI / 4 + (c * RAD) / 2));\n}\n\n/** Longitude of a Web Mercator easting. */\nexport function lonOf(x: number): number {\n return x / (RAD * EARTH_RADIUS);\n}\n\n/** Latitude of a Web Mercator northing. */\nexport function latOf(y: number): number {\n return (2 * Math.atan(Math.exp(y / EARTH_RADIUS)) - Math.PI / 2) / RAD;\n}\n\n/** Depth tint, after the Navionics SonarChart water palette: a saturated\n * azure at the surface, fading through pale blues to white at the shade\n * limit. Stops are `[t, r, g, b]` with `t` the fraction from the surface (0)\n * to the shade limit (1). */\nconst SHADE_RAMP: ReadonlyArray<readonly [number, number, number, number]> = [\n [0, 37, 175, 248],\n [0.25, 143, 215, 248],\n [0.5, 167, 223, 248],\n [0.75, 183, 223, 248],\n [1, 248, 248, 248],\n];\n\n/** Isobaths drawn between the surface and the shade limit. */\nexport const SHADE_CONTOUR_LEVELS = 6;\n\n/** RGB at fraction `t` (0 = surface, 1 = the shade limit), sampling\n * {@link SHADE_RAMP}. */\nexport function shadeColor(t: number): [number, number, number] {\n const c = Math.min(1, Math.max(0, Number.isFinite(t) ? t : 0));\n let lo = SHADE_RAMP[0];\n let hi = SHADE_RAMP[SHADE_RAMP.length - 1];\n for (let i = 0; i < SHADE_RAMP.length - 1; i++) {\n if (c >= SHADE_RAMP[i][0] && c <= SHADE_RAMP[i + 1][0]) {\n lo = SHADE_RAMP[i];\n hi = SHADE_RAMP[i + 1];\n break;\n }\n }\n const span = hi[0] - lo[0];\n const f = span > 0 ? (c - lo[0]) / span : 0;\n const lerp = (a: number, b: number) => Math.round(a + (b - a) * f);\n return [lerp(lo[1], hi[1]), lerp(lo[2], hi[2]), lerp(lo[3], hi[3])];\n}\n\n/** RGB for a depth: azure at the surface, white at `shadeLimit` and beyond. */\nexport function depthColor(\n depth: number,\n shadeLimit: number,\n): [number, number, number] {\n const limit = shadeLimit > 0 ? shadeLimit : DEFAULT_SHADE_LIMIT_M;\n return shadeColor(depth / limit);\n}\n\n/** {@link depthColor} as a CSS `rgb()`/`rgba()` string. */\nexport function depthCss(depth: number, shadeLimit: number, alpha = 1): string {\n const [r, g, b] = depthColor(depth, shadeLimit);\n return `rgba(${r},${g},${b},${alpha})`;\n}\n\n/** Aggregate samples into a Web Mercator grid, keeping the SHALLOWEST depth in\n * each cell. `maxDepthMetres` drops samples deeper than the display range\n * (they can never form a cell's minimum anyway); a sample that idles in the\n * grid alone still carries its real depth. Samples with a non-finite or\n * negative depth are ignored \u2014 a negative reading is not water. */\nexport function aggregateMinDepth(\n samples: readonly DepthSample[],\n cellSizeMetres: number,\n maxDepthMetres = Infinity,\n): DepthCell[] {\n if (!(cellSizeMetres > 0)) {\n return [];\n }\n const cells = new Map<\n string,\n {\n ix: number;\n iy: number;\n sx: number;\n sy: number;\n n: number;\n depth: number;\n }\n >();\n for (const s of samples) {\n if (\n !Number.isFinite(s.lon) ||\n !Number.isFinite(s.lat) ||\n !Number.isFinite(s.depth) ||\n s.depth < 0 ||\n s.depth > maxDepthMetres\n ) {\n continue;\n }\n const x = mercX(s.lon);\n const y = mercY(s.lat);\n const ix = Math.floor(x / cellSizeMetres);\n const iy = Math.floor(y / cellSizeMetres);\n const key = `${ix},${iy}`;\n const c = cells.get(key);\n if (c) {\n c.sx += x;\n c.sy += y;\n c.n++;\n if (s.depth < c.depth) {\n c.depth = s.depth;\n }\n } else {\n cells.set(key, { ix, iy, sx: x, sy: y, n: 1, depth: s.depth });\n }\n }\n const out: DepthCell[] = [];\n cells.forEach((c) => {\n out.push({\n ix: c.ix,\n iy: c.iy,\n lon: lonOf(c.sx / c.n),\n lat: latOf(c.sy / c.n),\n depth: c.depth,\n count: c.n,\n });\n });\n return out;\n}\n\n/** Cells ordered deepest first, so a shallow cell drawn later paints over a\n * deeper one that overlaps it \u2014 a shoal is never occluded. */\nexport function orderDeepToShallow<T extends { depth: number }>(\n cells: readonly T[],\n): T[] {\n return [...cells].sort((a, b) => b.depth - a.depth);\n}\n\n/** Samples at or below `safetyDepthMetres`, drawn individually on top of the\n * aggregate so a critical minimum keeps its exact position and depth. */\nexport function selectShoals(\n samples: readonly DepthSample[],\n safetyDepthMetres: number,\n): DepthSample[] {\n return samples.filter(\n (s) =>\n Number.isFinite(s.depth) && s.depth >= 0 && s.depth <= safetyDepthMetres,\n );\n}\n\n/** Share of the anchor's depth two samples may differ by and still merge.\n * Depth readings are noisy, so exact equality merges almost nothing. */\nexport const DEFAULT_MERGE_TOLERANCE = 0.15;\n\n/** How far a merged group may grow from its shallowest cell, in cells. Bounds\n * a circle's size so a uniform-depth passage does not become one giant blob. */\nexport const MERGE_MAX_RADIUS_CELLS = 2.5;\n\n/** Merge adjacent grid cells whose depths are within `tolerance` of the\n * group's shallowest cell, into one circle per group. The shallowest cell is\n * the anchor and comparisons are always against the anchor (not the previous\n * neighbour), so depths cannot chain up to merge a wide range; and a group is\n * not allowed to grow beyond `maxRadiusCells` from its anchor, so a long\n * uniform run becomes a row of modest circles rather than one huge one. */\nexport function mergeDepthCells(\n cells: readonly DepthCell[],\n cellSizeMetres: number,\n toleranceOrMaxRadius: number = DEFAULT_MERGE_TOLERANCE,\n maxRadiusCells: number = MERGE_MAX_RADIUS_CELLS,\n): DepthCluster[] {\n if (cells.length === 0 || !(cellSizeMetres > 0)) {\n return [];\n }\n const tolerance = Math.max(0, toleranceOrMaxRadius);\n const maxRadius = Math.max(cellSizeMetres, maxRadiusCells * cellSizeMetres);\n const byKey = new Map<string, DepthCell>();\n cells.forEach((c) => byKey.set(`${c.ix},${c.iy}`, c));\n const visited = new Set<string>();\n const ordered = [...cells].sort((a, b) => a.depth - b.depth);\n const neighbours = [\n [1, 0],\n [-1, 0],\n [0, 1],\n [0, -1],\n [1, 1],\n [1, -1],\n [-1, 1],\n [-1, -1],\n ];\n const clusters: DepthCluster[] = [];\n\n for (const start of ordered) {\n if (visited.has(`${start.ix},${start.iy}`)) {\n continue;\n }\n const anchor = start.depth;\n const ax = mercX(start.lon);\n const ay = mercY(start.lat);\n const members: DepthCell[] = [];\n const queue: DepthCell[] = [start];\n visited.add(`${start.ix},${start.iy}`);\n\n while (queue.length) {\n const cell = queue.pop();\n if (!cell) {\n continue;\n }\n members.push(cell);\n for (const [dx, dy] of neighbours) {\n const key = `${cell.ix + dx},${cell.iy + dy}`;\n const nb = byKey.get(key);\n if (!nb || visited.has(key)) {\n continue;\n }\n // anchor-relative, not neighbour-to-neighbour, so depth cannot drift\n if (nb.depth - anchor > Math.max(tolerance * anchor, 0.1)) {\n continue;\n }\n if (Math.hypot(mercX(nb.lon) - ax, mercY(nb.lat) - ay) > maxRadius) {\n continue;\n }\n visited.add(key);\n queue.push(nb);\n }\n }\n\n let cx = 0;\n let cy = 0;\n let total = 0;\n let minDepth = Infinity;\n members.forEach((m) => {\n cx += mercX(m.lon) * m.count;\n cy += mercY(m.lat) * m.count;\n total += m.count;\n if (m.depth < minDepth) {\n minDepth = m.depth;\n }\n });\n cx /= total;\n cy /= total;\n let radius = 0;\n members.forEach((m) => {\n radius = Math.max(\n radius,\n Math.hypot(mercX(m.lon) - cx, mercY(m.lat) - cy),\n );\n });\n clusters.push({\n lon: lonOf(cx),\n lat: latOf(cy),\n depth: minDepth,\n count: total,\n radiusMetres: Math.min(maxRadius, radius + cellSizeMetres / 2),\n });\n }\n return clusters;\n}\n\n/** Aggregate, merge similar-depth neighbours and order deep \u2192 shallow, ready\n * to draw. */\nexport function depthClusters(\n samples: readonly DepthSample[],\n cellSizeMetres: number,\n tolerance: number = DEFAULT_MERGE_TOLERANCE,\n maxDepthMetres = Infinity,\n maxRadiusCells: number = MERGE_MAX_RADIUS_CELLS,\n): DepthCluster[] {\n return orderDeepToShallow(\n mergeDepthCells(\n aggregateMinDepth(samples, cellSizeMetres, maxDepthMetres),\n cellSizeMetres,\n tolerance,\n maxRadiusCells,\n ),\n );\n}\n\n// ******** History API ********\n//\n// The History API supplies the depth series (position and depth together) over\n// a recent time window; the Track API is not used, as on servers whose track\n// provider does not co-record depth it costs seconds per query just to find a\n// time span. Each depth sample carries its own position.\n\n/** Aggregation requested for depth in the History API: the SHALLOWEST sample.\n * At native resolution (no `resolution`) each bucket is one recorded sample, so\n * this is an identity; it only matters if a provider buckets unasked, where it\n * keeps the shallowest value rather than an average. */\nexport const DEPTH_HISTORY_METHOD = \"min\";\n\n/** Query for the depth series with the position of each sample. An explicit\n * `resolution` (seconds, fractional allowed) is honoured by the provider; when\n * it is omitted most providers auto-downsample to a few hundred points, so the\n * caller should always pass the finest interval it wants. `context` defaults to\n * the server's own vessel. */\nexport function historyValuesQuery(opts: {\n depthPath: string;\n from: number;\n to: number;\n resolution?: number;\n context?: string;\n}): string {\n return queryString({\n context: opts.context,\n paths: `navigation.position,${opts.depthPath}:${DEPTH_HISTORY_METHOD},navigation.headingTrue:average`,\n from: new Date(opts.from).toISOString(),\n to: new Date(opts.to).toISOString(),\n resolution: opts.resolution,\n });\n}\n\n/** One depth sample from the History API, with its recorded position. */\nexport interface DepthHistoryRow {\n time: number;\n depth: number;\n position?: Position;\n /** True heading (radians) at the sample, to place it at the transducer. */\n heading?: number;\n}\n\n/** A `[lon, lat]` from either array or `{latitude, longitude}` form. */\nfunction toPosition(value: unknown): Position | undefined {\n if (Array.isArray(value) && value.length >= 2) {\n const [lon, lat] = value as number[];\n if (Number.isFinite(lon) && Number.isFinite(lat)) {\n return [lon, lat];\n }\n return undefined;\n }\n if (value && typeof value === \"object\") {\n const v = value as { latitude?: unknown; longitude?: unknown };\n if (typeof v.latitude === \"number\" && typeof v.longitude === \"number\") {\n return [v.longitude, v.latitude];\n }\n }\n return undefined;\n}\n\n/** Depth rows from a History API `values` response, matched to `depthPath`.\n * The `position` column is attached when the response carried one; rows with a\n * null, non-number or negative depth are skipped. */\nexport function parseDepthSeries(\n result: unknown,\n depthPath: string,\n): DepthHistoryRow[] {\n const values = (result as { values?: Array<{ path?: string }> })?.values;\n const data = (result as { data?: unknown[] })?.data;\n if (!Array.isArray(values) || !Array.isArray(data)) {\n return [];\n }\n const depthCol = values.findIndex((v) => v?.path === depthPath);\n if (depthCol < 0) {\n return [];\n }\n const posCol = values.findIndex((v) => v?.path === \"navigation.position\");\n const headingCol = values.findIndex(\n (v) => v?.path === \"navigation.headingTrue\",\n );\n const rows: DepthHistoryRow[] = [];\n for (const row of data) {\n if (!Array.isArray(row)) {\n continue;\n }\n const time = Date.parse(String(row[0]));\n const depth = row[depthCol + 1];\n if (\n !Number.isFinite(time) ||\n typeof depth !== \"number\" ||\n !Number.isFinite(depth) ||\n depth < 0\n ) {\n continue;\n }\n const rec: DepthHistoryRow = { time, depth };\n if (posCol >= 0) {\n const position = toPosition(row[posCol + 1]);\n if (position) {\n rec.position = position;\n }\n }\n if (headingCol >= 0) {\n const heading = row[headingCol + 1];\n if (typeof heading === \"number\" && Number.isFinite(heading)) {\n rec.heading = heading;\n }\n }\n rows.push(rec);\n }\n return rows;\n}\n", "/** The extension's user settings, shared by the config panel (which edits\n * them), the runtime (which applies them) and the render Worker (which draws\n * with them). Persisted in host state under the extension scope. */\n\nexport type DepthSource = \"history\" | \"tiles\";\n\n/** Overlay render quality. `auto` measures the worker's own frame cost and\n * scales to a budget; the rest pin a scale, so a weak GPU (a laptop iGPU) can\n * be dialled down and a fast one dialled up. */\nexport type DepthQuality = \"auto\" | \"high\" | \"balanced\" | \"low\";\n\nexport interface LiveDepthSettings {\n enabled: boolean;\n /** Where the soundings come from: the server's History API (the vessel's own\n * recent track) or the plugin's gridded bathymetry tiles. */\n source: DepthSource;\n /** Overlay render quality (see {@link DepthQuality}). */\n quality: DepthQuality;\n /** Render the shaded surface at the host's full requested size, ignoring the\n * render-quality scale. */\n fullResolution: boolean;\n /** Draw isobars as vector polylines (crisp at any zoom) instead of the\n * per-fragment raster approximation. */\n vectorContours: boolean;\n /** Antialias the WebGL surface, so the coverage/discard edges are smooth. */\n msaa: boolean;\n /** Cap on either depth-field dimension. Higher resolves finer contours at the\n * cost of memory and build time. */\n fieldCells: number;\n /** Major (index) vector isobar line width, in screen pixels. */\n contourWidthPx: number;\n /** Minor vector isobar line width, in screen pixels. */\n minorContourWidthPx: number;\n /** Major (index) isobar colour (CSS). */\n majorContourColor: string;\n /** Minor isobar colour (CSS). */\n minorContourColor: string;\n /** History look-back window, in hours. */\n historyWindowHours: number;\n /** History sample interval, in seconds. */\n historyResolutionS: number;\n /** Depth (metres) the shading runs to; deeper is left to the chart. */\n shadeLimitMetres: number;\n /** Depth (metres) between major isobaths (the index contours). */\n contourIntervalMetres: number;\n /** Depth (metres) between minor isobaths. */\n minorContourMetres: number;\n /** Radius (metres) over which a sounding fills water with no samples of its\n * own, so a single pass paints a readable swath instead of a hairline track.\n * Measured cells keep their own depth; the fill fades linearly to nothing at\n * this radius. 0 disables the fill. */\n fillMetres: number;\n /** Grid cell size in screen pixels; also the base label size. */\n cellPx: number;\n /** Fractional depth difference (0.15 = 15%) within which adjacent cells\n * merge into one group. */\n mergeTolerance: number;\n /** Samples deeper than this (metres) are not drawn. */\n maxDepthMetres: number;\n /** Samples at or below this depth (metres) are marked individually. */\n safetyDepthMetres: number;\n}\n\nexport const DEFAULT_SETTINGS: LiveDepthSettings = {\n enabled: true,\n source: \"history\",\n quality: \"auto\",\n fullResolution: true,\n vectorContours: true,\n msaa: true,\n fieldCells: 2048,\n contourWidthPx: 1,\n minorContourWidthPx: 0.75,\n majorContourColor: \"#000000\",\n minorContourColor: \"#9aa4a8\",\n historyWindowHours: 1,\n historyResolutionS: 0.1,\n shadeLimitMetres: 10,\n contourIntervalMetres: 1.524,\n minorContourMetres: 0.3048,\n fillMetres: 6,\n cellPx: 24,\n mergeTolerance: 0.15,\n maxDepthMetres: 100,\n safetyDepthMetres: 3,\n};\n\n/** The render scale (fraction of the host's requested frame size) each fixed\n * quality pins; `auto` is driven by the worker's own measurement instead. */\nexport const QUALITY_SCALE: Record<Exclude<DepthQuality, \"auto\">, number> = {\n high: 1,\n balanced: 0.6,\n low: 0.4,\n};\n\n/** The allowed values of the settings that are a closed set of strings. */\nconst ENUM_VALUES: Partial<Record<keyof LiveDepthSettings, readonly string[]>> =\n {\n source: [\"history\", \"tiles\"],\n quality: [\"auto\", \"high\", \"balanced\", \"low\"],\n };\n\n/** The settings keys, with the bounds the config panel clamps to. */\nexport const SETTINGS_BOUNDS: Partial<\n Record<keyof LiveDepthSettings, { min: number; max: number; step: number }>\n> = {\n historyWindowHours: { min: 0.1, max: 168, step: 0.1 },\n historyResolutionS: { min: 0.01, max: 60, step: 0.01 },\n shadeLimitMetres: { min: 1, max: 200, step: 1 },\n contourIntervalMetres: { min: 0.3048, max: 50, step: 0.3048 },\n minorContourMetres: { min: 0.3048, max: 50, step: 0.3048 },\n fillMetres: { min: 0, max: 20, step: 1 },\n cellPx: { min: 4, max: 128, step: 1 },\n fieldCells: { min: 256, max: 4096, step: 256 },\n contourWidthPx: { min: 0.5, max: 6, step: 0.5 },\n minorContourWidthPx: { min: 0.5, max: 6, step: 0.25 },\n mergeTolerance: { min: 0, max: 1, step: 0.01 },\n maxDepthMetres: { min: 1, max: 500, step: 1 },\n safetyDepthMetres: { min: 0, max: 50, step: 0.5 },\n};\n\n/** Merge stored state over the defaults, ignoring unknown/ill-typed keys. */\nexport function mergeSettings(\n stored: Record<string, unknown> | undefined,\n): LiveDepthSettings {\n const out = { ...DEFAULT_SETTINGS };\n if (!stored) {\n return out;\n }\n for (const key of Object.keys(\n DEFAULT_SETTINGS,\n ) as (keyof LiveDepthSettings)[]) {\n const value = stored[key];\n if (typeof value !== typeof DEFAULT_SETTINGS[key]) {\n continue;\n }\n const allowed = ENUM_VALUES[key];\n if (allowed && !allowed.includes(value as string)) {\n continue;\n }\n (out[key] as unknown) = value;\n }\n return out;\n}\n", "/// <reference lib=\"webworker\" />\n//\n// The render Worker: turns the soundings into one overlay frame.\n//\n// It is kept off the runtime iframe's main thread \u2014 on several engines an\n// extension iframe shares the host page's main thread, so a heavy render here\n// would stall the whole chart. The runtime sends it the soundings and settings;\n// on each `render` request it builds (once per data change) the depth field,\n// runs a WebGL2 pass that shades the surface and draws isobaths, composites\n// that onto a 2D canvas that adds the shoal/cluster dots and their depth\n// labels, and hands the result back as an ImageBitmap.\n//\n// Geometry: the host asks for a north-up, axis-aligned EPSG:3857 box whose\n// pixels are evenly spaced in Mercator metres. The field is built in the same\n// space, so a frame is a re-raster of the cached field \u2014 panning or zooming\n// never changes the surface (and the absolute-depth isobars stay put).\n\nimport {\n createDepthFieldBuilder,\n type CellRect,\n type DepthField,\n type DepthFieldBuilder,\n} from \"./shared/bathymetry.js\";\nimport { DEPTH_FRAG_SRC, DEPTH_VERT_SRC } from \"./shared/shaders.js\";\nimport {\n extractContours,\n type ContourSegment,\n type ContourSet,\n} from \"./shared/contours.js\";\nimport {\n DEFAULT_COVERAGE_MIN_M,\n medianConsecutiveGap,\n} from \"./shared/bathymetry.js\";\nimport {\n SHADE_CONTOUR_LEVELS,\n depthClusters,\n depthCss,\n latOf,\n lonOf,\n mercX,\n mercY,\n selectShoals,\n shadeColor,\n type DepthCluster,\n type DepthSample,\n} from \"./shared/live-depth.js\";\nimport { QUALITY_SCALE, type LiveDepthSettings } from \"./shared/settings.js\";\nimport type {\n RenderRequest,\n RenderWorkerIn,\n RenderWorkerOut,\n} from \"./shared/protocol.js\";\n\n/** Hard cap on a field dimension; if the cell would exceed it the cells are\n * grown to fit, so the grid always covers the whole data extent. */\nconst MAX_FIELD_CELLS = 1024;\n/** Label size (CSS px): scaled with zoom between a readable floor and a cap. */\nconst LABEL_FONT_MIN = 12;\nconst LABEL_FONT_MAX = 20;\n/** Smallest on-screen radius (CSS px) at which a group is labelled. */\nconst LABEL_MIN_RADIUS_PX = 8;\n\nconst ORIGIN_SHIFT = Math.PI * 6378137;\nconst WORLD_WIDTH = 2 * ORIGIN_SHIFT;\n\nlet samples: DepthSample[] = [];\n/** The server-derived soundings, replaced as a whole on a base update. */\nlet baseSamples: DepthSample[] = [];\n/** The own-vessel soundings folded in since, appended and never rebuilt. */\nlet liveSamples: DepthSample[] = [];\nlet settings: LiveDepthSettings | null = null;\nlet builder: DepthFieldBuilder | null = null;\nlet fieldDirty = true;\n/** Bumped whenever the depth texture changes, so the cached surface is rebuilt. */\nlet fieldVersion = 0;\n/** Last rendered surface (WebGL) and 2D overlay, keyed on everything that\n * affects them. Reusing them skips the per-pixel shader and the overlay's\n * canvas work on requests where nothing changed (the host asks for a frame\n * far more often than the field or the view changes). */\nlet surfaceCache: {\n key: string;\n image: ImageBitmap;\n box: [number, number, number, number];\n width: number;\n height: number;\n} | null = null;\n/** The output frame canvas, reused across requests (a fresh 2560\u00D71267 canvas\n * per frame is a ~13 MB allocation and a full clear every time). */\nlet outCanvas: OffscreenCanvas | null = null;\nlet outCtx: OffscreenCanvasRenderingContext2D | null = null;\n/** Each side of the surface is rendered this much wider than the view, so a\n * following or panning map reuses the rendered surface instead of re-running\n * the full-screen shader for every small move. 0.25 makes the bitmap 1.5\u00D7 the\n * view in each dimension. */\nconst SURFACE_PAD = 0.25;\nlet overlayCache: { key: string; image: ImageBitmap } | null = null;\nlet overlayCanvas: OffscreenCanvas | null = null;\nlet overlayCtx: OffscreenCanvasRenderingContext2D | null = null;\n/** The vessel's live position/heading + dimensions, for the debug footprint. */\nlet vessel: {\n position: [number, number] | null;\n heading?: number;\n loa?: number;\n beam?: number;\n} = { position: null };\n/** The user's preferred depth unit (`\"m\"` or `\"foot\"`), for the labels. */\nlet depthUnit = \"m\";\n\n/** Bumped whenever `samples` changes, so the overlay caches below recompute on\n * a data change rather than on every render. */\nlet samplesVersion = 0;\n/** The shoal samples and depth clusters, keyed on their inputs: a frame that\n * only moves the view (or is a live tick with no new soundings) reuses them\n * instead of re-aggregating every sample. */\nlet shoalCache: { key: string; value: DepthSample[] } | null = null;\nlet clusterCache: { key: string; value: DepthCluster[] } | null = null;\n/** Vector contours for the current field, in world metres. */\nlet contours: ContourSet = { major: [], minor: [] };\n/** The MSAA setting the live GL context was created with. */\nlet gpuMsaa = true;\n\n/** Budget for one frame's worker work, in ms; `auto` scales the shaded surface\n * to hold it. */\nconst AUTO_FRAME_BUDGET_MS = 10;\n/** Frames between auto adjustments. Adjusting every frame makes the surface\n * hunt (and the map shimmer); a couple of seconds apart lets it settle. */\nconst AUTO_ADJUST_EVERY = 30;\n/** Frames between perf readouts to the runtime. */\nconst PERF_REPORT_EVERY = 20;\n/** The scale `auto` has settled on; stepped occasionally. */\nlet autoScale = 0.7;\n/** Rolling average of the frame cost, for a stable auto decision. */\nlet frameAvgMs = 0;\n/** Frames since the last auto adjustment. */\nlet framesSinceAdjust = 0;\n/** Frames since the last perf readout. */\nlet perfFrames = 0;\n\n/** The shaded-surface scale for this frame: pinned by the quality setting, or\n * the adaptive value in `auto`. Only the WebGL surface is drawn at this scale;\n * the dots, clusters and labels are always drawn at full resolution, so the\n * map stays readable and keeps every depth measurement. */\nfunction renderScale(): number {\n // Full-resolution renders the surface at the host's requested size, ignoring\n // the quality scale (crowd-depth parity).\n if (settings?.fullResolution) return 1;\n const quality = settings?.quality ?? \"auto\";\n return quality === \"auto\" ? autoScale : QUALITY_SCALE[quality];\n}\n\n/** A depth in the user's unit: whole feet, or a tenth of a metre. */\nfunction formatDepth(metres: number): string {\n return depthUnit === \"foot\"\n ? (metres / 0.3048).toFixed(0)\n : metres.toFixed(1);\n}\n\n// ******** WebGL ********\n\nlet glCanvas: OffscreenCanvas | null = null;\nlet gl: WebGL2RenderingContext | null = null;\nlet program: WebGLProgram | null = null;\nlet quad: WebGLBuffer | null = null;\nlet depthTexture: WebGLTexture | null = null;\nlet lutTexture: WebGLTexture | null = null;\nlet aPos = 0;\nlet uDepth: WebGLUniformLocation | null = null;\nlet uLut: WebGLUniformLocation | null = null;\nlet uTexel: WebGLUniformLocation | null = null;\nlet uView: WebGLUniformLocation | null = null;\nlet uField: WebGLUniformLocation | null = null;\nlet uShadeLimit: WebGLUniformLocation | null = null;\nlet uLevels: WebGLUniformLocation | null = null;\nlet uInterval: WebGLUniformLocation | null = null;\nlet uLineWidth: WebGLUniformLocation | null = null;\nlet uIsobars: WebGLUniformLocation | null = null;\n\nfunction compile(\n ctx: WebGL2RenderingContext,\n type: number,\n src: string,\n): WebGLShader | null {\n const shader = ctx.createShader(type);\n if (!shader) return null;\n ctx.shaderSource(shader, src);\n ctx.compileShader(shader);\n if (!ctx.getShaderParameter(shader, ctx.COMPILE_STATUS)) {\n ctx.deleteShader(shader);\n return null;\n }\n return shader;\n}\n\nfunction initWebgl(): boolean {\n if (gl) return true;\n glCanvas = new OffscreenCanvas(1, 1);\n const msaa = settings?.msaa ?? true;\n const ctx = glCanvas.getContext(\"webgl2\", {\n alpha: true,\n premultipliedAlpha: true,\n antialias: msaa,\n }) as WebGL2RenderingContext | null;\n if (!ctx) return false;\n gpuMsaa = msaa;\n const vs = compile(ctx, ctx.VERTEX_SHADER, DEPTH_VERT_SRC);\n const fs = compile(ctx, ctx.FRAGMENT_SHADER, DEPTH_FRAG_SRC);\n const prog = ctx.createProgram();\n if (!vs || !fs || !prog) return false;\n ctx.attachShader(prog, vs);\n ctx.attachShader(prog, fs);\n ctx.linkProgram(prog);\n if (!ctx.getProgramParameter(prog, ctx.LINK_STATUS)) return false;\n\n gl = ctx;\n program = prog;\n aPos = ctx.getAttribLocation(prog, \"aPos\");\n uDepth = ctx.getUniformLocation(prog, \"uDepth\");\n uLut = ctx.getUniformLocation(prog, \"uLut\");\n uTexel = ctx.getUniformLocation(prog, \"uTexel\");\n uView = ctx.getUniformLocation(prog, \"uView\");\n uField = ctx.getUniformLocation(prog, \"uField\");\n uShadeLimit = ctx.getUniformLocation(prog, \"uShadeLimit\");\n uLevels = ctx.getUniformLocation(prog, \"uLevels\");\n uInterval = ctx.getUniformLocation(prog, \"uInterval\");\n uLineWidth = ctx.getUniformLocation(prog, \"uLineWidth\");\n uIsobars = ctx.getUniformLocation(prog, \"uIsobars\");\n\n quad = ctx.createBuffer();\n ctx.bindBuffer(ctx.ARRAY_BUFFER, quad);\n ctx.bufferData(\n ctx.ARRAY_BUFFER,\n new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),\n ctx.STATIC_DRAW,\n );\n\n depthTexture = ctx.createTexture();\n ctx.bindTexture(ctx.TEXTURE_2D, depthTexture);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MIN_FILTER, ctx.NEAREST);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MAG_FILTER, ctx.NEAREST);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_S, ctx.CLAMP_TO_EDGE);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_T, ctx.CLAMP_TO_EDGE);\n\n lutTexture = ctx.createTexture();\n const lut = new Uint8Array(256 * 4);\n for (let i = 0; i < 256; i++) {\n const [r, g, b] = shadeColor(i / 255);\n lut[i * 4] = r;\n lut[i * 4 + 1] = g;\n lut[i * 4 + 2] = b;\n lut[i * 4 + 3] = 255;\n }\n ctx.bindTexture(ctx.TEXTURE_2D, lutTexture);\n ctx.texImage2D(\n ctx.TEXTURE_2D,\n 0,\n ctx.RGBA,\n 256,\n 1,\n 0,\n ctx.RGBA,\n ctx.UNSIGNED_BYTE,\n lut,\n );\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MIN_FILTER, ctx.LINEAR);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MAG_FILTER, ctx.LINEAR);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_S, ctx.CLAMP_TO_EDGE);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_T, ctx.CLAMP_TO_EDGE);\n\n return true;\n}\n\n/** Release the GL context while the overlay is hidden or suspended. */\nfunction releaseGpu(): void {\n if (gl) {\n gl.getExtension(\"WEBGL_lose_context\")?.loseContext();\n }\n gl = null;\n program = null;\n quad = null;\n depthTexture = null;\n lutTexture = null;\n glCanvas = null;\n surfaceCache?.image.close();\n surfaceCache = null;\n outCanvas = null;\n outCtx = null;\n overlayCache?.image.close();\n overlayCache = null;\n overlayCanvas = null;\n overlayCtx = null;\n}\n\nfunction uploadField(f: DepthField): void {\n if (!gl || !depthTexture) return;\n // GLSL ES does not require NaN support, so the no-coverage marker the field\n // uses on the CPU (NaN) is uploaded as -1 \u2014 a value no real depth can take \u2014\n // and the shader tests `v < 0` for it.\n const gpu = new Float32Array(f.depth.length);\n for (let i = 0; i < f.depth.length; i++) {\n const d = f.depth[i];\n gpu[i] = Number.isNaN(d) ? -1 : d;\n }\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, depthTexture);\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n gl.R32F,\n f.width,\n f.height,\n 0,\n gl.RED,\n gl.FLOAT,\n gpu,\n );\n fieldVersion++;\n}\n\n/** Upload only the cells in `rect` (half-open), so folding a live sounding in\n * costs a few rows of texture rather than the whole field. */\nfunction uploadRegion(f: DepthField, [x0, y0, x1, y1]: CellRect): void {\n if (!gl || !depthTexture) return;\n const w = x1 - x0;\n const h = y1 - y0;\n if (w <= 0 || h <= 0) return;\n const gpu = new Float32Array(w * h);\n for (let y = 0; y < h; y++) {\n const src = (y0 + y) * f.width + x0;\n const dst = y * w;\n for (let x = 0; x < w; x++) {\n const d = f.depth[src + x];\n gpu[dst + x] = Number.isNaN(d) ? -1 : d;\n }\n }\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, depthTexture);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, x0, y0, w, h, gl.RED, gl.FLOAT, gpu);\n fieldVersion++;\n}\n\n/** Rebuild the field around the current base (a view change or a settings\n * change), then fold the own-vessel soundings back in. This is the only path\n * that pays for a whole-grid rebuild; a live flush just patches cells. */\nfunction rebuildField(): void {\n builder = null;\n samples = [...baseSamples, ...liveSamples];\n // Size the grid off the server data, which covers the view; a live sounding\n // outside it is clipped until the next base refresh rather than growing the\n // grid (and forcing a rebuild) on every flush.\n const source = baseSamples.length ? baseSamples : samples;\n if (source.length === 0 || !settings) return;\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const s of source) {\n const x = mercX(s.lon);\n const y = mercY(s.lat);\n minX = Math.min(minX, x);\n maxX = Math.max(maxX, x);\n minY = Math.min(minY, y);\n maxY = Math.max(maxY, y);\n }\n const padX = (maxX - minX) * 0.1 + 1;\n const padY = (maxY - minY) * 0.1 + 1;\n const extent: [number, number, number, number] = [\n minX - padX,\n minY - padY,\n maxX + padX,\n maxY + padY,\n ];\n const extMax = Math.max(extent[2] - extent[0], extent[3] - extent[1]);\n // Tile samples are already gridded, not along-track ordered, so a\n // consecutive-gap estimate is meaningless; the target cell size is the field\n // resolution instead.\n const tiles = settings.source === \"tiles\";\n const gap = tiles ? 0 : medianConsecutiveGap(source);\n const minCover = tiles ? 0 : DEFAULT_COVERAGE_MIN_M;\n const maxCells = Math.max(\n 64,\n Math.round(settings.fieldCells) || MAX_FIELD_CELLS,\n );\n const cell = Math.max(gap, extMax / maxCells, minCover / 8, 0.01);\n const next = createDepthFieldBuilder(extent, {\n cellMetres: cell,\n maxCells,\n // `gap` bridges the along-track gaps, the fill radius widens a sounding\n // into empty water, and a sounding always covers its own cell.\n coverageMetres: Math.max(cell * 1.5, gap * 1.5, settings.fillMetres),\n coverageMinMetres: minCover,\n });\n if (!next) return;\n next.add(baseSamples);\n if (liveSamples.length) next.add(liveSamples);\n builder = next;\n uploadField(next.field);\n // Trace the vector isobars once per (re)build. Live soundings update the\n // surface immediately; the contours catch up on the next base fetch.\n contours = settings.vectorContours\n ? extractContours(\n next.field,\n Math.max(0.01, settings.minorContourMetres),\n Math.max(0.01, settings.contourIntervalMetres),\n Math.max(0, settings.maxDepthMetres),\n )\n : { major: [], minor: [] };\n}\n\n/** Fold new own-vessel soundings in, refreshing only the cells they reach. */\nfunction addLive(newSamples: DepthSample[]): void {\n liveSamples.push(...newSamples);\n samples.push(...newSamples);\n samplesVersion++;\n if (!builder) {\n // No field yet (the base has not arrived): rebuild once it does.\n fieldDirty = true;\n return;\n }\n const rect = builder.add(newSamples);\n if (rect) uploadRegion(builder.field, rect);\n}\n\nfunction renderGpu(\n box: readonly [number, number, number, number],\n canvasW: number,\n canvasH: number,\n ratio: number,\n): void {\n const f = builder?.field;\n if (!gl || !f || !glCanvas) return;\n if (glCanvas.width !== canvasW || glCanvas.height !== canvasH) {\n glCanvas.width = canvasW;\n glCanvas.height = canvasH;\n }\n gl.viewport(0, 0, canvasW, canvasH);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.useProgram(program);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, depthTexture);\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, lutTexture);\n\n const ext = f.extent;\n const fieldCentreX = (ext[0] + ext[2]) / 2;\n const viewCentreX = (box[0] + box[2]) / 2;\n const shift =\n Math.round((fieldCentreX - viewCentreX) / WORLD_WIDTH) * WORLD_WIDTH;\n // Pass field-relative metres to the shader: absolute EPSG:3857 values\n // (~8.6e6) lose about a metre of precision in the shader's float32 maths.\n gl.uniform4f(\n uView,\n box[0] + shift - ext[0],\n box[1] - ext[1],\n box[2] - box[0],\n box[3] - box[1],\n );\n gl.uniform4f(uField, 0, 0, ext[2] - ext[0], ext[3] - ext[1]);\n gl.uniform2f(uTexel, 1 / f.width, 1 / f.height);\n gl.uniform1i(uDepth, 0);\n gl.uniform1i(uLut, 1);\n gl.uniform1f(uShadeLimit, Math.max(0.01, settings?.shadeLimitMetres ?? 10));\n gl.uniform1i(uLevels, SHADE_CONTOUR_LEVELS);\n gl.uniform1f(uInterval, Math.max(0.01, settings?.contourIntervalMetres ?? 1));\n gl.uniform1f(uLineWidth, Math.max(1, ratio));\n gl.uniform1f(uIsobars, settings?.vectorContours ? 0 : 1);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, quad);\n gl.enableVertexAttribArray(aPos);\n gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n}\n\n// ******** 2D overlay (dots + labels) ********\n\n/** Draw the vector isobars (traced from the field) on the 2D overlay. */\nfunction drawContours(\n ctx: OffscreenCanvasRenderingContext2D,\n box: readonly [number, number, number, number],\n canvasW: number,\n canvasH: number,\n ratio: number,\n): void {\n if (!settings?.vectorContours) return;\n const f = builder?.field;\n if (!f || (contours.major.length === 0 && contours.minor.length === 0)) {\n return;\n }\n const boxW = box[2] - box[0];\n const boxH = box[3] - box[1];\n if (!(boxW > 0) || !(boxH > 0)) return;\n // Match the surface's world copy (see renderGpu).\n const ext = f.extent;\n const shift =\n Math.round(((ext[0] + ext[2]) / 2 - (box[0] + box[2]) / 2) / WORLD_WIDTH) *\n WORLD_WIDTH;\n const px = (x: number) => ((x + shift - box[0]) / boxW) * canvasW;\n const py = (y: number) => ((box[3] - y) / boxH) * canvasH;\n const stroke = (\n segments: ContourSegment[],\n color: string,\n widthPx: number,\n ) => {\n if (segments.length === 0) return;\n ctx.beginPath();\n for (const [x0, y0, x1, y1] of segments) {\n ctx.moveTo(px(x0), py(y0));\n ctx.lineTo(px(x1), py(y1));\n }\n ctx.lineWidth = Math.max(0.5, widthPx * ratio);\n ctx.lineCap = \"round\";\n ctx.lineJoin = \"round\";\n ctx.strokeStyle = color;\n ctx.stroke();\n };\n // Minor first, so the darker index contours sit on top.\n stroke(\n contours.minor,\n settings.minorContourColor,\n settings.minorContourWidthPx,\n );\n stroke(contours.major, settings.majorContourColor, settings.contourWidthPx);\n}\n\nfunction drawOverlay(\n ctx: OffscreenCanvasRenderingContext2D,\n box: readonly [number, number, number, number],\n canvasW: number,\n canvasH: number,\n resolution: number,\n ratio: number,\n zoom: number,\n): void {\n if (!settings) return;\n const boxW = box[2] - box[0];\n const boxH = box[3] - box[1];\n const centreX = (box[0] + box[2]) / 2;\n const toPixel = (lon: number, lat: number): [number, number] => {\n const mx = mercX(lon);\n const my = mercY(lat);\n const x = mx + Math.round((centreX - mx) / WORLD_WIDTH) * WORLD_WIDTH;\n return [((x - box[0]) / boxW) * canvasW, ((box[3] - my) / boxH) * canvasH];\n };\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n\n const dot = (p: [number, number], depth: number) => {\n ctx.beginPath();\n ctx.arc(\n p[0],\n p[1],\n Math.max(1.5, settings!.cellPx * 0.12) * ratio,\n 0,\n Math.PI * 2,\n );\n ctx.fillStyle = depthCss(depth, settings!.shadeLimitMetres, 0.95);\n ctx.fill();\n };\n const label = (text: string, p: [number, number], fontCss: number) => {\n ctx.font = `${Math.round(fontCss * ratio)}px Roboto, Arial, sans-serif`;\n ctx.lineWidth = Math.max(2, 2 * ratio);\n ctx.strokeStyle = \"rgba(255,255,255,0.85)\";\n ctx.strokeText(text, p[0], p[1]);\n ctx.fillStyle = \"rgba(0,0,0,0.95)\";\n ctx.fillText(text, p[0], p[1]);\n };\n\n drawContours(ctx, box, canvasW, canvasH, ratio);\n\n // Recomputing these over every sample each frame is the worker's dominant\n // cost; they depend only on the data and the settings (the cluster cell size\n // also on the view resolution), so cache them and rebuild only on a key\n // change \u2014 a frame that just moves the view reuses them.\n const shoalKey = `${samplesVersion}:${settings.safetyDepthMetres}`;\n if (!shoalCache || shoalCache.key !== shoalKey) {\n shoalCache = {\n key: shoalKey,\n value: selectShoals(samples, settings.safetyDepthMetres),\n };\n }\n shoalCache.value.forEach((s) => dot(toPixel(s.lon, s.lat), s.depth));\n\n const cellSize = Math.max(1, settings.cellPx * resolution);\n const clusterKey = `${samplesVersion}:${cellSize}:${settings.mergeTolerance}:${settings.maxDepthMetres}`;\n if (!clusterCache || clusterCache.key !== clusterKey) {\n clusterCache = {\n key: clusterKey,\n value: depthClusters(\n samples,\n cellSize,\n settings.mergeTolerance,\n settings.maxDepthMetres,\n ),\n };\n }\n const clusters = clusterCache.value;\n for (const c of clusters) {\n const p = toPixel(c.lon, c.lat);\n const radiusPx = (c.radiusMetres / resolution) * ratio;\n if (radiusPx >= LABEL_MIN_RADIUS_PX) {\n const fontCss = Math.max(\n LABEL_FONT_MIN,\n Math.min(LABEL_FONT_MAX, (zoom - 8) * 1.5 + LABEL_FONT_MIN),\n );\n label(formatDepth(c.depth), p, fontCss);\n } else {\n dot(p, c.depth);\n }\n }\n\n // Debug: the vessel's footprint (LOA \u00D7 beam) centred on the reported position\n // and rotated to the heading, so where a sounding lands relative to the hull\n // can be seen. Off unless the plugin's debug config turns it on.\n const v = vessel;\n if (v.position && v.heading !== undefined && v.loa && v.beam) {\n const cx = mercX(v.position[0]);\n const cy = mercY(v.position[1]);\n const cos = Math.cos(v.heading);\n const sin = Math.sin(v.heading);\n const halfLength = v.loa / 2;\n const halfBeam = v.beam / 2;\n const corners = (\n [\n [halfLength, halfBeam],\n [halfLength, -halfBeam],\n [-halfLength, -halfBeam],\n [-halfLength, halfBeam],\n ] as const\n ).map(([fwd, stbd]) =>\n toPixel(\n lonOf(cx + fwd * sin + stbd * cos),\n latOf(cy + fwd * cos - stbd * sin),\n ),\n );\n ctx.beginPath();\n corners.forEach(([px, py], i) =>\n i === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py),\n );\n ctx.closePath();\n ctx.lineWidth = Math.max(1.5, 1.5 * ratio);\n ctx.strokeStyle = \"rgba(255, 0, 255, 0.95)\";\n ctx.stroke();\n ctx.fillStyle = \"rgba(255, 0, 255, 0.12)\";\n ctx.fill();\n }\n}\n\n// ******** message handling ********\n\nfunction render(req: RenderRequest): void {\n try {\n if (!initWebgl()) {\n post({ type: \"error\", message: \"WebGL2 unavailable\" });\n return;\n }\n if (fieldDirty || !builder) {\n rebuildField();\n fieldDirty = false;\n }\n const { extent, width, height, pixelRatio, resolution, zoom } = req;\n const box: [number, number, number, number] = [\n extent[0],\n extent[1],\n extent[2],\n extent[3],\n ];\n // Only the shaded WebGL surface is drawn at the quality scale (the shading\n // is smooth, so it tolerates it). It is upscaled onto the frame, and the\n // dots, clusters and labels are then drawn at full resolution on top \u2014 so\n // every sounding stays sharp and the cluster cell size (which depends on\n // the frame resolution) never coarsens. The frame stays exactly\n // `width \u00D7 height`, as the API requires.\n const scale = renderScale();\n const surfaceW = Math.max(1, Math.round(width * scale));\n const surfaceH = Math.max(1, Math.round(height * scale));\n const started = performance.now();\n if (\n !outCanvas ||\n !outCtx ||\n outCanvas.width !== width ||\n outCanvas.height !== height\n ) {\n outCanvas = new OffscreenCanvas(width, height);\n outCtx = outCanvas.getContext(\"2d\");\n }\n const out = outCanvas;\n const ctx = outCtx;\n if (!ctx) {\n post({ type: \"error\", message: \"no 2D context\" });\n return;\n }\n ctx.clearRect(0, 0, width, height);\n let redrew = false;\n if (builder && glCanvas) {\n const padX = (box[2] - box[0]) * SURFACE_PAD;\n const padY = (box[3] - box[1]) * SURFACE_PAD;\n const padBox: [number, number, number, number] = [\n box[0] - padX,\n box[1] - padY,\n box[2] + padX,\n box[3] + padY,\n ];\n const padW = Math.max(1, Math.round(surfaceW * (1 + SURFACE_PAD * 2)));\n const padH = Math.max(1, Math.round(surfaceH * (1 + SURFACE_PAD * 2)));\n const surfaceKey = [\n fieldVersion,\n padW,\n padH,\n pixelRatio,\n settings?.shadeLimitMetres,\n settings?.contourIntervalMetres,\n settings?.vectorContours,\n settings?.msaa,\n ].join(\"|\");\n const cached = surfaceCache;\n const covers =\n !!cached &&\n cached.key === surfaceKey &&\n box[0] >= cached.box[0] &&\n box[1] >= cached.box[1] &&\n box[2] <= cached.box[2] &&\n box[3] <= cached.box[3];\n if (!covers) {\n renderGpu(padBox, padW, padH, pixelRatio * scale);\n cached?.image.close();\n surfaceCache = {\n key: surfaceKey,\n image: glCanvas.transferToImageBitmap(),\n box: padBox,\n width: padW,\n height: padH,\n };\n redrew = true;\n }\n const cache = surfaceCache!;\n // Blit the part of the padded surface the current view covers.\n const cw = cache.box[2] - cache.box[0];\n const ch = cache.box[3] - cache.box[1];\n const sx = ((box[0] - cache.box[0]) / cw) * cache.width;\n const sy = ((cache.box[3] - box[3]) / ch) * cache.height;\n const sw = ((box[2] - box[0]) / cw) * cache.width;\n const sh = ((box[3] - box[1]) / ch) * cache.height;\n ctx.drawImage(cache.image, sx, sy, sw, sh, 0, 0, width, height);\n }\n const overlayKey = [\n fieldVersion,\n samplesVersion,\n box.join(\",\"),\n width,\n height,\n resolution,\n pixelRatio,\n zoom,\n settings?.vectorContours,\n settings?.contourIntervalMetres,\n settings?.minorContourMetres,\n settings?.contourWidthPx,\n settings?.minorContourWidthPx,\n settings?.majorContourColor,\n settings?.minorContourColor,\n settings?.safetyDepthMetres,\n settings?.cellPx,\n settings?.mergeTolerance,\n settings?.maxDepthMetres,\n settings?.shadeLimitMetres,\n ].join(\"|\");\n if (!overlayCache || overlayCache.key !== overlayKey) {\n if (\n !overlayCanvas ||\n !overlayCtx ||\n overlayCanvas.width !== width ||\n overlayCanvas.height !== height\n ) {\n overlayCanvas = new OffscreenCanvas(width, height);\n overlayCtx = overlayCanvas.getContext(\"2d\");\n }\n if (overlayCtx) {\n overlayCtx.clearRect(0, 0, width, height);\n drawOverlay(\n overlayCtx,\n box,\n width,\n height,\n resolution,\n pixelRatio,\n zoom,\n );\n overlayCache?.image.close();\n overlayCache = {\n key: overlayKey,\n image: overlayCanvas.transferToImageBitmap(),\n };\n redrew = true;\n }\n }\n if (overlayCache) ctx.drawImage(overlayCache.image, 0, 0);\n const image = out.transferToImageBitmap();\n const frameMs = performance.now() - started;\n // Auto: smooth the cost and adjust only every few frames, with hysteresis,\n // so the surface settles instead of hunting under the user. `drawImage`\n // syncs the GL pass, so the window covers the real per-frame work.\n if (redrew && (settings?.quality ?? \"auto\") === \"auto\") {\n frameAvgMs =\n frameAvgMs === 0 ? frameMs : frameAvgMs * 0.8 + frameMs * 0.2;\n if (++framesSinceAdjust >= AUTO_ADJUST_EVERY) {\n framesSinceAdjust = 0;\n if (frameAvgMs > AUTO_FRAME_BUDGET_MS * 1.25) {\n autoScale = Math.max(0.4, autoScale - 0.1);\n } else if (frameAvgMs < AUTO_FRAME_BUDGET_MS * 0.6) {\n autoScale = Math.min(1, autoScale + 0.1);\n }\n }\n }\n if (++perfFrames >= PERF_REPORT_EVERY) {\n perfFrames = 0;\n post({ type: \"perf\", scale, frameMs });\n }\n post({ type: \"frame\", requestId: req.requestId, image }, [image]);\n } catch (err) {\n post({ type: \"error\", message: String(err) });\n }\n}\n\nfunction post(message: RenderWorkerOut, transfer?: Transferable[]): void {\n (self as unknown as Worker).postMessage(message, transfer ?? []);\n}\n\nself.onmessage = (e: MessageEvent<RenderWorkerIn>) => {\n const msg = e.data;\n switch (msg.type) {\n case \"base\":\n baseSamples = msg.samples;\n fieldDirty = true;\n samplesVersion++;\n break;\n case \"live\":\n addLive(msg.samples);\n break;\n case \"vessel\":\n vessel = msg;\n break;\n case \"settings\": {\n // MSAA is fixed when the GL context is created; drop it so the next\n // render recreates it with the new setting.\n if (msg.settings.msaa !== gpuMsaa) releaseGpu();\n settings = msg.settings;\n fieldDirty = true;\n break;\n }\n case \"units\":\n depthUnit = msg.depth;\n break;\n case \"render\":\n render(msg.req);\n break;\n case \"release\":\n releaseGpu();\n break;\n }\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/** Bathymetric surface from a GPS+depth track: a shallow-biased depth raster\n * and a min-pooled level-of-detail pyramid for a WebGL renderer.\n *\n * The data is one vessel's track, not a survey, so a cell is only filled where\n * a sounding is within `coverageCells`; gaps stay NaN (no coverage) rather than\n * being invented. The fill is a distance-weighted blend with a linear falloff,\n * biased toward the shallowest sample in range by `shallowBias` (1 = never\n * deeper than that sample) so a smoothed surface can never overstate depth;\n * a contour (a sharp depth change) is then never smeared across by the fill.\n *\n * Pure functions (no Angular, no WebGL) so they can be unit tested directly.\n */\n\nimport { DepthSample } from \"./live-depth\";\n\n/** A regular depth raster in EPSG:3857 metres. */\nexport interface DepthField {\n width: number;\n height: number;\n /** [minX, minY, maxX, maxY] in metres. */\n extent: [number, number, number, number];\n /** Depth per cell in metres, row-major; NaN means no coverage. */\n depth: Float32Array;\n}\n\nexport interface BathymetryOptions {\n /** Target cell size in metres (the grid is built at about this size). */\n cellMetres: number;\n /** How far a sounding reaches, in cells. Beyond it a cell is uncovered. */\n coverageCells?: number;\n /** How far a sounding reaches, in metres, overriding `coverageCells`. Use\n * this to bridge the gaps between track samples (set it to about the sample\n * spacing) so the traversed ribbon is continuous rather than disconnected\n * dots. */\n coverageMetres?: number;\n /** Floor for the coverage radius, in metres. A sounding stands for at least\n * this area, so an anchored vessel's jittered positions (clusters a fraction\n * of a metre apart) render as one patch rather than dozens of specks. */\n coverageMinMetres?: number;\n /** Falloff exponent: 1 = linear (default), higher concentrates the value\n * nearer the sample. */\n power?: number;\n /** How far (metres) the fill may rise above the shallowest sample in range.\n * A bigger change is a contour it must not cross, so a sharp depth change is\n * capped rather than smeared over. */\n contourMetres?: number;\n /** 0 = pure distance blend, 1 = never deeper than the shallowest sample in\n * range. */\n shallowBias?: number;\n /** Hard cap on either grid dimension, to bound memory and time. */\n maxCells?: number;\n}\n\nexport const DEFAULT_COVERAGE_CELLS = 2;\n/** Floor for the coverage radius, in metres (see `coverageMinMetres`). */\nexport const DEFAULT_COVERAGE_MIN_M = 2;\nexport const DEFAULT_IDW_POWER = 1;\nexport const DEFAULT_SHALLOW_BIAS = 0.5;\nexport const DEFAULT_CONTOUR_M = 2;\nexport const DEFAULT_MAX_CELLS = 1024;\n\nconst EARTH_RADIUS = 6378137;\nconst MERCATOR_MAX_LAT = 85.05112878;\nconst RAD = Math.PI / 180;\n\nconst mercX = (lon: number) => lon * RAD * EARTH_RADIUS;\nconst mercY = (lat: number) =>\n EARTH_RADIUS *\n Math.log(\n Math.tan(\n Math.PI / 4 +\n (Math.max(-MERCATOR_MAX_LAT, Math.min(MERCATOR_MAX_LAT, lat)) * RAD) /\n 2,\n ),\n );\n\n/** Median distance (metres) between consecutive samples, in the order given\n * (assumed to be along-track). 0 with fewer than two samples. A robust measure\n * of the track's sample spacing, used to size a coverage radius that bridges\n * the gaps so the traversed ribbon is continuous. */\nexport function medianConsecutiveGap(\n samples: readonly { lon: number; lat: number }[],\n): number {\n if (samples.length < 2) {\n return 0;\n }\n const gaps: number[] = [];\n for (let i = 1; i < samples.length; i++) {\n const ax = mercX(samples[i - 1].lon);\n const ay = mercY(samples[i - 1].lat);\n gaps.push(\n Math.hypot(mercX(samples[i].lon) - ax, mercY(samples[i].lat) - ay),\n );\n }\n gaps.sort((a, b) => a - b);\n return gaps[Math.floor(gaps.length / 2)];\n}\n\n/** Cell indices a splat reached, as `[minX, minY, maxX, maxY]` (upper bounds\n * exclusive). */\nexport type CellRect = [number, number, number, number];\n\n/** A depth raster that can be extended a few samples at a time. The per-cell\n * IDW weights are kept, so a live sounding patches just the cells it reaches\n * instead of rebuilding the whole grid. */\nexport interface DepthFieldBuilder {\n readonly field: DepthField;\n /** Splat `samples` into the field, refreshing the cells they reach. Returns\n * the cell rect that changed (for a partial texture upload), or null. */\n add(samples: readonly DepthSample[]): CellRect | null;\n}\n\n/** Create an empty field over `extent` (EPSG:3857 `[minX,minY,maxX,maxY]`), or\n * null when the extent is unusable. Extend it with {@link DepthFieldBuilder.add}. */\nexport function createDepthFieldBuilder(\n extent: readonly number[],\n options: BathymetryOptions,\n): DepthFieldBuilder | null {\n const cellMetres = options.cellMetres;\n if (\n !(cellMetres > 0) ||\n extent.length !== 4 ||\n !extent.every((v) => Number.isFinite(v)) ||\n extent[2] <= extent[0] ||\n extent[3] <= extent[1]\n ) {\n return null;\n }\n const power = options.power ?? DEFAULT_IDW_POWER;\n const shallowBias = Math.max(\n 0,\n Math.min(1, options.shallowBias ?? DEFAULT_SHALLOW_BIAS),\n );\n const contourMetres = options.contourMetres ?? DEFAULT_CONTOUR_M;\n const maxCells = options.maxCells ?? DEFAULT_MAX_CELLS;\n\n const width = Math.min(\n maxCells,\n Math.ceil((extent[2] - extent[0]) / cellMetres),\n );\n const height = Math.min(\n maxCells,\n Math.ceil((extent[3] - extent[1]) / cellMetres),\n );\n if (width <= 0 || height <= 0) {\n return null;\n }\n // Re-derive the extent from the (possibly capped) grid so cells stay square.\n const minX = extent[0];\n const minY = extent[1];\n const gridExtent: [number, number, number, number] = [\n minX,\n minY,\n minX + width * cellMetres,\n minY + height * cellMetres,\n ];\n\n const depth = new Float32Array(width * height).fill(NaN);\n const wsum = new Float32Array(width * height);\n const wdsum = new Float32Array(width * height);\n const minSeen = new Float32Array(width * height).fill(Infinity);\n\n const coverageRadius = Math.max(\n cellMetres,\n options.coverageMinMetres ?? DEFAULT_COVERAGE_MIN_M,\n options.coverageMetres ??\n (options.coverageCells ?? DEFAULT_COVERAGE_CELLS) * cellMetres,\n );\n const coverage2 = coverageRadius * coverageRadius;\n // Bound the per-sample splat; a huge radius would make the build quadratic.\n const radiusCells = Math.min(64, Math.ceil(coverageRadius / cellMetres));\n\n const field: DepthField = { width, height, extent: gridExtent, depth };\n\n /** Recompute `depth` over the half-open cell rect from the accumulated\n * weights. */\n function refresh([rx0, ry0, rx1, ry1]: CellRect): void {\n for (let gy = ry0; gy < ry1; gy++) {\n const row = gy * width;\n for (let gx = rx0; gx < rx1; gx++) {\n const i = row + gx;\n const w = wsum[i];\n if (w <= 0) {\n depth[i] = NaN;\n continue;\n }\n const idw = wdsum[i] / w;\n const shallowest = minSeen[i];\n // Bias toward the shallowest sample in range, then cap the result so\n // the fill can never be more than `contourMetres` deeper than the\n // shallowest measured sample: a sharp depth change is a contour the\n // fill does not cross, while a gentle slope blends linearly.\n const blended =\n (1 - shallowBias) * idw + shallowBias * Math.min(idw, shallowest);\n depth[i] = Math.min(blended, shallowest + contourMetres);\n }\n }\n }\n\n const isUsable = (s: DepthSample): boolean =>\n Number.isFinite(s.lon) &&\n Number.isFinite(s.lat) &&\n Number.isFinite(s.depth) &&\n s.depth >= 0;\n\n function add(samples: readonly DepthSample[]): CellRect | null {\n let rect: CellRect | null = null;\n const grow = (gx: number, gy: number) => {\n if (!rect) {\n rect = [gx, gy, gx + 1, gy + 1];\n return;\n }\n if (gx < rect[0]) rect[0] = gx;\n if (gy < rect[1]) rect[1] = gy;\n if (gx + 1 > rect[2]) rect[2] = gx + 1;\n if (gy + 1 > rect[3]) rect[3] = gy + 1;\n };\n for (const s of samples) {\n if (!isUsable(s)) {\n continue;\n }\n const sx = mercX(s.lon);\n const sy = mercY(s.lat);\n const cx = Math.floor((sx - minX) / cellMetres);\n const cy = Math.floor((sy - minY) / cellMetres);\n for (let dy = -radiusCells; dy <= radiusCells; dy++) {\n const gy = cy + dy;\n if (gy < 0 || gy >= height) {\n continue;\n }\n const cellCenterY = minY + (gy + 0.5) * cellMetres;\n const ddy = cellCenterY - sy;\n for (let dx = -radiusCells; dx <= radiusCells; dx++) {\n const gx = cx + dx;\n if (gx < 0 || gx >= width) {\n continue;\n }\n const cellCenterX = minX + (gx + 0.5) * cellMetres;\n const ddx = cellCenterX - sx;\n const dist2 = ddx * ddx + ddy * ddy;\n if (dist2 > coverage2) {\n continue;\n }\n const idx = gy * width + gx;\n // Linear falloff: 1 at the sounding, 0 at the coverage edge. This\n // spreads a sample into empty water over `coverageRadius` with a\n // gradient rather than clustering the value around the nearest\n // sample, so adjacent tracks blend linearly across the gap. `power`\n // (default 1 = linear) shapes the ramp.\n const w = Math.pow(1 - Math.sqrt(dist2) / coverageRadius, power);\n wsum[idx] += w;\n wdsum[idx] += w * s.depth;\n if (s.depth < minSeen[idx]) {\n minSeen[idx] = s.depth;\n }\n grow(gx, gy);\n }\n }\n }\n if (rect) refresh(rect);\n return rect;\n }\n\n return { field, add };\n}\n\n/** Build a depth raster over `extent` from the samples in one pass, or null\n * when there is nothing to draw or the grid is unusable. */\nexport function buildDepthField(\n samples: readonly DepthSample[],\n extent: readonly number[],\n options: BathymetryOptions,\n): DepthField | null {\n const builder = createDepthFieldBuilder(extent, options);\n if (!builder) {\n return null;\n }\n builder.add(samples);\n return builder.field;\n}\n", "/** GLSL for the LiveDepth bathymetric surface.\n *\n * A full-screen quad covering the current viewport samples the depth field\n * (`uDepth`, an R32F texture, -1 = no coverage) through a view transform\n * (`uView`, `uField`, both in EPSG:3857 metres), shades discrete depth bands up\n * to `uShadeLimit` using a colour ramp (`uLut`), and draws an isobar wherever\n * the depth crosses a multiple of `uInterval`. Deeper than the shade limit (or\n * uncovered) is transparent, so the chart shows through; isobars are still\n * drawn over unshaded data.\n *\n * The field is built once per data fetch and sampled here, so the surface \u2014 and\n * therefore the absolute-depth isobars \u2014 does not change as the view is panned\n * or zoomed. The depth is interpolated by hand (bilinear) rather than relying\n * on a LINEAR filter: a float texture is only filterable with\n * OES_texture_float_linear, which many GPUs (e.g. Firefox on some hardware) do\n * not expose, and an unfilterable LINEAR texture is incomplete and samples as\n * nothing.\n */\n\nexport const DEPTH_VERT_SRC = `#version 300 es\nin vec2 aPos;\nout vec2 vUv;\nvoid main() { vUv = aPos * 0.5 + 0.5; gl_Position = vec4(aPos, 0.0, 1.0); }`;\n\nexport const DEPTH_FRAG_SRC = `#version 300 es\nprecision highp float;\nin vec2 vUv;\nout vec4 fragColor;\nuniform sampler2D uDepth;\nuniform sampler2D uLut;\nuniform vec2 uTexel; // 1 / field size in texels\nuniform vec4 uView; // viewport: origin.xy, size.xy (metres)\nuniform vec4 uField; // field: origin.xy, size.xy (metres)\nuniform float uShadeLimit;\nuniform int uLevels;\nuniform float uInterval;\nuniform float uLineWidth;\nuniform float uIsobars; // 1 = per-fragment isobars, 0 = vector contours drawn by the runtime\nbool uncov(float v) { return v < 0.0; } // -1: no coverage\nfloat sampleDepth(vec2 uv) {\n vec2 fc = uv / uTexel - 0.5;\n vec2 f = fract(fc);\n vec2 b = (floor(fc) + 0.5) * uTexel;\n float d00 = texture(uDepth, b).r;\n float d10 = texture(uDepth, b + vec2(uTexel.x, 0.0)).r;\n float d01 = texture(uDepth, b + vec2(0.0, uTexel.y)).r;\n float d11 = texture(uDepth, b + uTexel).r;\n if (uncov(d00) || uncov(d10) || uncov(d01) || uncov(d11)) {\n return texture(uDepth, uv).r; // near an edge/gap: no interpolation\n }\n return mix(mix(d00, d10, f.x), mix(d01, d11, f.x), f.y);\n}\nvoid main() {\n // viewport pixel -> world metres -> field uv\n vec2 world = uView.xy + vUv * uView.zw;\n vec2 fuv = (world - uField.xy) / uField.zw;\n if (fuv.x < 0.0 || fuv.x > 1.0 || fuv.y < 0.0 || fuv.y > 1.0) { discard; }\n float d = sampleDepth(fuv);\n if (uncov(d)) { discard; }\n // Shading bands, only up to the shade limit; deeper water is left to the chart.\n bool shaded = d < uShadeLimit;\n vec3 fill = vec3(0.0);\n if (shaded) {\n float bandF = d / uShadeLimit * float(uLevels);\n float band = floor(bandF);\n float tCenter = (band + 0.5) / float(uLevels);\n fill = texture(uLut, vec2(clamp(tCenter, 0.0, 1.0), 0.5)).rgb;\n }\n // Isobars at every absolute multiple of the interval, over ALL data. The\n // runtime can disable these and draw vector contours instead.\n float line = 0.0;\n if (uIsobars > 0.5) {\n float ci = d / uInterval;\n float dv = min(fract(ci), 1.0 - fract(ci));\n float px = dv / max(fwidth(ci), 1e-6);\n line = 1.0 - smoothstep(uLineWidth, uLineWidth + 1.5, px);\n }\n vec3 col = mix(fill, vec3(0.0), line);\n float alpha = max(line, shaded ? 1.0 : 0.0);\n fragColor = vec4(col, alpha);\n}`;\n", "/** Vector isobars from a depth field, via marching squares.\n *\n * The field is a raster, so the per-fragment isobars it can shade are limited by\n * its resolution. Tracing the same crossings as geometry instead gives lines\n * that stay crisp at any zoom (the vector-tile look). Segments are in the\n * field's EPSG:3857 metres; the renderer maps them to pixels. Cells with an\n * uncovered corner are skipped, so a contour stops at the data edge.\n *\n * Each cell emits only the contour levels it actually straddles, so the cost\n * scales with the number of crossings rather than cells \u00D7 levels.\n *\n * Levels are walked at the finer of the two intervals; a level that also lands\n * on the major grid is returned as a major (index) contour, the rest as minor.\n */\n\nimport type { DepthField } from \"./bathymetry.js\";\n\n/** One contour segment in world metres: `[x0, y0, x1, y1]`. */\nexport type ContourSegment = [number, number, number, number];\n\n/** Major (index) and minor contours of a field. */\nexport interface ContourSet {\n major: ContourSegment[];\n minor: ContourSegment[];\n}\n\n/** Whether `level` is a whole multiple of `step` (within float tolerance). */\nfunction onGrid(level: number, step: number): boolean {\n const n = level / step;\n return Math.abs(n - Math.round(n)) < 1e-6;\n}\n\n/** Marching-squares contours of `field`: every multiple of `minorStep` is a\n * minor contour, every multiple of `majorStep` a major one, up to `maxLevel`\n * (inclusive). A level on both grids is major. */\nexport function extractContours(\n field: DepthField,\n minorStep: number,\n majorStep: number,\n maxLevel: number,\n): ContourSet {\n const { width, height, depth, extent } = field;\n const result: ContourSet = { major: [], minor: [] };\n const minor = minorStep > 0 ? minorStep : 1;\n const major = majorStep > 0 ? majorStep : minor;\n if (width < 2 || height < 2) return result;\n const cell = (extent[2] - extent[0]) / width;\n const at = (gx: number, gy: number) => depth[gy * width + gx];\n // Corner offsets around a cell, in order (0,0) (1,0) (1,1) (0,1).\n const ox = [0, 1, 1, 0];\n const oy = [0, 0, 1, 1];\n // Walk at the finer interval so both grids are covered.\n const step = Math.min(minor, major);\n\n for (let gy = 0; gy < height - 1; gy++) {\n for (let gx = 0; gx < width - 1; gx++) {\n const d = [\n at(gx, gy),\n at(gx + 1, gy),\n at(gx + 1, gy + 1),\n at(gx, gy + 1),\n ];\n if (\n !Number.isFinite(d[0]) ||\n !Number.isFinite(d[1]) ||\n !Number.isFinite(d[2]) ||\n !Number.isFinite(d[3])\n ) {\n continue;\n }\n const lo = Math.min(d[0], d[1], d[2], d[3]);\n const hi = Math.max(d[0], d[1], d[2], d[3]);\n if (hi <= lo) continue;\n // The first contour level strictly inside (lo, hi), then every step up.\n const first = Math.floor(lo / step) * step + step;\n for (let level = first; level < hi && level <= maxLevel; level += step) {\n if (level <= lo) continue;\n const out = onGrid(level, major) ? result.major : result.minor;\n // Edge i runs corner i -> i+1 (0:(0-1) 1:(1-2) 2:(2-3) 3:(3-0)).\n const cross: Array<[number, number] | null> = [null, null, null, null];\n const crossed: number[] = [];\n for (let i = 0; i < 4; i++) {\n const j = (i + 1) % 4;\n if (d[i] >= level === d[j] >= level) {\n continue;\n }\n const t = (level - d[i]) / (d[j] - d[i]);\n cross[i] = [\n extent[0] + (gx + ox[i] + (ox[j] - ox[i]) * t) * cell,\n extent[1] + (gy + oy[i] + (oy[j] - oy[i]) * t) * cell,\n ];\n crossed.push(i);\n }\n if (crossed.length === 2) {\n const [a, b] = crossed;\n out.push([...cross[a]!, ...cross[b]!]);\n } else if (crossed.length === 4) {\n // Ambiguous saddle: split by the cell-centre average.\n const centre = (d[0] + d[1] + d[2] + d[3]) / 4;\n if (centre >= level) {\n out.push([...cross[0]!, ...cross[1]!]);\n out.push([...cross[2]!, ...cross[3]!]);\n } else {\n out.push([...cross[0]!, ...cross[3]!]);\n out.push([...cross[1]!, ...cross[2]!]);\n }\n }\n }\n }\n }\n return result;\n}\n", "/** Live depth layer: pure helpers turning depth-at-position samples (v2 Track\n * API `properties`) into a safe, drawable aggregate.\n *\n * The overriding rule is safety: a rendered mark must never display deeper than\n * the shallowest sample it covers. Every aggregate therefore takes the\n * MINIMUM depth \u2014 never a mean, which would let a shoal vanish into deeper\n * water \u2014 and the layer draws shallower marks over deeper ones. Samples at or\n * below a shoal threshold are returned individually so a critical minimum is\n * never smeared to a cell centre.\n *\n * Pure functions (no Angular, no map) so they can be unit tested directly.\n */\n\n/** A `[lon, lat]` position. */\nexport type Position = [number, number];\n\n/** A URL query string from the given params, skipping absent or empty ones. */\nfunction queryString(\n params: Record<string, string | number | undefined>,\n): string {\n return Object.entries(params)\n .filter(([, v]) => v !== undefined && v !== \"\")\n .map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`)\n .join(\"&\");\n}\n\n/** A recorded depth at a position, depth in metres (canonical Signal K). */\nexport interface DepthSample {\n lon: number;\n lat: number;\n depth: number;\n}\n\n/** One grid cell of the aggregate. */\nexport interface DepthCell {\n /** Grid indices (Web Mercator cell coordinates), for neighbour lookup. */\n ix: number;\n iy: number;\n /** Cell centre (mean of the samples in it), lon/lat. */\n lon: number;\n lat: number;\n /** Shallowest depth among the cell's samples, in metres. */\n depth: number;\n /** How many samples fell in the cell. */\n count: number;\n}\n\n/** One drawn circle: a group of adjacent cells at a similar depth, merged. */\nexport interface DepthCluster {\n /** Circle centre (weighted centroid), lon/lat. */\n lon: number;\n lat: number;\n /** Shallowest depth in the group, in metres. */\n depth: number;\n /** Samples the group covers. */\n count: number;\n /** Spatial radius of the group, in metres (Web Mercator). */\n radiusMetres: number;\n}\n\n/** Depth (metres) the shading runs to by default: at this depth and beyond the\n * layer draws nothing, so the chart shows through. */\nexport const DEFAULT_SHADE_LIMIT_M = 10;\n\n/** Default shoal threshold (metres): samples at or below this are marked\n * individually. Matches the S-57 renderer's default safety depth. */\nexport const DEFAULT_SAFETY_DEPTH_M = 3;\n\nconst EARTH_RADIUS = 6378137;\nconst MERCATOR_MAX_LAT = 85.05112878;\nconst RAD = Math.PI / 180;\n\n/** Web Mercator easting (metres) of a longitude. */\nexport function mercX(lon: number): number {\n return lon * RAD * EARTH_RADIUS;\n}\n\n/** Web Mercator northing (metres) of a latitude, clamped to the projection. */\nexport function mercY(lat: number): number {\n const c = Math.max(-MERCATOR_MAX_LAT, Math.min(MERCATOR_MAX_LAT, lat));\n return EARTH_RADIUS * Math.log(Math.tan(Math.PI / 4 + (c * RAD) / 2));\n}\n\n/** Longitude of a Web Mercator easting. */\nexport function lonOf(x: number): number {\n return x / (RAD * EARTH_RADIUS);\n}\n\n/** Latitude of a Web Mercator northing. */\nexport function latOf(y: number): number {\n return (2 * Math.atan(Math.exp(y / EARTH_RADIUS)) - Math.PI / 2) / RAD;\n}\n\n/** Depth tint, after the Navionics SonarChart water palette: a saturated\n * azure at the surface, fading through pale blues to white at the shade\n * limit. Stops are `[t, r, g, b]` with `t` the fraction from the surface (0)\n * to the shade limit (1). */\nconst SHADE_RAMP: ReadonlyArray<readonly [number, number, number, number]> = [\n [0, 37, 175, 248],\n [0.25, 143, 215, 248],\n [0.5, 167, 223, 248],\n [0.75, 183, 223, 248],\n [1, 248, 248, 248],\n];\n\n/** Isobaths drawn between the surface and the shade limit. */\nexport const SHADE_CONTOUR_LEVELS = 6;\n\n/** RGB at fraction `t` (0 = surface, 1 = the shade limit), sampling\n * {@link SHADE_RAMP}. */\nexport function shadeColor(t: number): [number, number, number] {\n const c = Math.min(1, Math.max(0, Number.isFinite(t) ? t : 0));\n let lo = SHADE_RAMP[0];\n let hi = SHADE_RAMP[SHADE_RAMP.length - 1];\n for (let i = 0; i < SHADE_RAMP.length - 1; i++) {\n if (c >= SHADE_RAMP[i][0] && c <= SHADE_RAMP[i + 1][0]) {\n lo = SHADE_RAMP[i];\n hi = SHADE_RAMP[i + 1];\n break;\n }\n }\n const span = hi[0] - lo[0];\n const f = span > 0 ? (c - lo[0]) / span : 0;\n const lerp = (a: number, b: number) => Math.round(a + (b - a) * f);\n return [lerp(lo[1], hi[1]), lerp(lo[2], hi[2]), lerp(lo[3], hi[3])];\n}\n\n/** RGB for a depth: azure at the surface, white at `shadeLimit` and beyond. */\nexport function depthColor(\n depth: number,\n shadeLimit: number,\n): [number, number, number] {\n const limit = shadeLimit > 0 ? shadeLimit : DEFAULT_SHADE_LIMIT_M;\n return shadeColor(depth / limit);\n}\n\n/** {@link depthColor} as a CSS `rgb()`/`rgba()` string. */\nexport function depthCss(depth: number, shadeLimit: number, alpha = 1): string {\n const [r, g, b] = depthColor(depth, shadeLimit);\n return `rgba(${r},${g},${b},${alpha})`;\n}\n\n/** Aggregate samples into a Web Mercator grid, keeping the SHALLOWEST depth in\n * each cell. `maxDepthMetres` drops samples deeper than the display range\n * (they can never form a cell's minimum anyway); a sample that idles in the\n * grid alone still carries its real depth. Samples with a non-finite or\n * negative depth are ignored \u2014 a negative reading is not water. */\nexport function aggregateMinDepth(\n samples: readonly DepthSample[],\n cellSizeMetres: number,\n maxDepthMetres = Infinity,\n): DepthCell[] {\n if (!(cellSizeMetres > 0)) {\n return [];\n }\n const cells = new Map<\n string,\n {\n ix: number;\n iy: number;\n sx: number;\n sy: number;\n n: number;\n depth: number;\n }\n >();\n for (const s of samples) {\n if (\n !Number.isFinite(s.lon) ||\n !Number.isFinite(s.lat) ||\n !Number.isFinite(s.depth) ||\n s.depth < 0 ||\n s.depth > maxDepthMetres\n ) {\n continue;\n }\n const x = mercX(s.lon);\n const y = mercY(s.lat);\n const ix = Math.floor(x / cellSizeMetres);\n const iy = Math.floor(y / cellSizeMetres);\n const key = `${ix},${iy}`;\n const c = cells.get(key);\n if (c) {\n c.sx += x;\n c.sy += y;\n c.n++;\n if (s.depth < c.depth) {\n c.depth = s.depth;\n }\n } else {\n cells.set(key, { ix, iy, sx: x, sy: y, n: 1, depth: s.depth });\n }\n }\n const out: DepthCell[] = [];\n cells.forEach((c) => {\n out.push({\n ix: c.ix,\n iy: c.iy,\n lon: lonOf(c.sx / c.n),\n lat: latOf(c.sy / c.n),\n depth: c.depth,\n count: c.n,\n });\n });\n return out;\n}\n\n/** Cells ordered deepest first, so a shallow cell drawn later paints over a\n * deeper one that overlaps it \u2014 a shoal is never occluded. */\nexport function orderDeepToShallow<T extends { depth: number }>(\n cells: readonly T[],\n): T[] {\n return [...cells].sort((a, b) => b.depth - a.depth);\n}\n\n/** Samples at or below `safetyDepthMetres`, drawn individually on top of the\n * aggregate so a critical minimum keeps its exact position and depth. */\nexport function selectShoals(\n samples: readonly DepthSample[],\n safetyDepthMetres: number,\n): DepthSample[] {\n return samples.filter(\n (s) =>\n Number.isFinite(s.depth) && s.depth >= 0 && s.depth <= safetyDepthMetres,\n );\n}\n\n/** Share of the anchor's depth two samples may differ by and still merge.\n * Depth readings are noisy, so exact equality merges almost nothing. */\nexport const DEFAULT_MERGE_TOLERANCE = 0.15;\n\n/** How far a merged group may grow from its shallowest cell, in cells. Bounds\n * a circle's size so a uniform-depth passage does not become one giant blob. */\nexport const MERGE_MAX_RADIUS_CELLS = 2.5;\n\n/** Merge adjacent grid cells whose depths are within `tolerance` of the\n * group's shallowest cell, into one circle per group. The shallowest cell is\n * the anchor and comparisons are always against the anchor (not the previous\n * neighbour), so depths cannot chain up to merge a wide range; and a group is\n * not allowed to grow beyond `maxRadiusCells` from its anchor, so a long\n * uniform run becomes a row of modest circles rather than one huge one. */\nexport function mergeDepthCells(\n cells: readonly DepthCell[],\n cellSizeMetres: number,\n toleranceOrMaxRadius: number = DEFAULT_MERGE_TOLERANCE,\n maxRadiusCells: number = MERGE_MAX_RADIUS_CELLS,\n): DepthCluster[] {\n if (cells.length === 0 || !(cellSizeMetres > 0)) {\n return [];\n }\n const tolerance = Math.max(0, toleranceOrMaxRadius);\n const maxRadius = Math.max(cellSizeMetres, maxRadiusCells * cellSizeMetres);\n const byKey = new Map<string, DepthCell>();\n cells.forEach((c) => byKey.set(`${c.ix},${c.iy}`, c));\n const visited = new Set<string>();\n const ordered = [...cells].sort((a, b) => a.depth - b.depth);\n const neighbours = [\n [1, 0],\n [-1, 0],\n [0, 1],\n [0, -1],\n [1, 1],\n [1, -1],\n [-1, 1],\n [-1, -1],\n ];\n const clusters: DepthCluster[] = [];\n\n for (const start of ordered) {\n if (visited.has(`${start.ix},${start.iy}`)) {\n continue;\n }\n const anchor = start.depth;\n const ax = mercX(start.lon);\n const ay = mercY(start.lat);\n const members: DepthCell[] = [];\n const queue: DepthCell[] = [start];\n visited.add(`${start.ix},${start.iy}`);\n\n while (queue.length) {\n const cell = queue.pop();\n if (!cell) {\n continue;\n }\n members.push(cell);\n for (const [dx, dy] of neighbours) {\n const key = `${cell.ix + dx},${cell.iy + dy}`;\n const nb = byKey.get(key);\n if (!nb || visited.has(key)) {\n continue;\n }\n // anchor-relative, not neighbour-to-neighbour, so depth cannot drift\n if (nb.depth - anchor > Math.max(tolerance * anchor, 0.1)) {\n continue;\n }\n if (Math.hypot(mercX(nb.lon) - ax, mercY(nb.lat) - ay) > maxRadius) {\n continue;\n }\n visited.add(key);\n queue.push(nb);\n }\n }\n\n let cx = 0;\n let cy = 0;\n let total = 0;\n let minDepth = Infinity;\n members.forEach((m) => {\n cx += mercX(m.lon) * m.count;\n cy += mercY(m.lat) * m.count;\n total += m.count;\n if (m.depth < minDepth) {\n minDepth = m.depth;\n }\n });\n cx /= total;\n cy /= total;\n let radius = 0;\n members.forEach((m) => {\n radius = Math.max(\n radius,\n Math.hypot(mercX(m.lon) - cx, mercY(m.lat) - cy),\n );\n });\n clusters.push({\n lon: lonOf(cx),\n lat: latOf(cy),\n depth: minDepth,\n count: total,\n radiusMetres: Math.min(maxRadius, radius + cellSizeMetres / 2),\n });\n }\n return clusters;\n}\n\n/** Aggregate, merge similar-depth neighbours and order deep \u2192 shallow, ready\n * to draw. */\nexport function depthClusters(\n samples: readonly DepthSample[],\n cellSizeMetres: number,\n tolerance: number = DEFAULT_MERGE_TOLERANCE,\n maxDepthMetres = Infinity,\n maxRadiusCells: number = MERGE_MAX_RADIUS_CELLS,\n): DepthCluster[] {\n return orderDeepToShallow(\n mergeDepthCells(\n aggregateMinDepth(samples, cellSizeMetres, maxDepthMetres),\n cellSizeMetres,\n tolerance,\n maxRadiusCells,\n ),\n );\n}\n\n// ******** History API ********\n//\n// The History API supplies the depth series (position and depth together) over\n// a recent time window; the Track API is not used, as on servers whose track\n// provider does not co-record depth it costs seconds per query just to find a\n// time span. Each depth sample carries its own position.\n\n/** Aggregation requested for depth in the History API: the SHALLOWEST sample.\n * At native resolution (no `resolution`) each bucket is one recorded sample, so\n * this is an identity; it only matters if a provider buckets unasked, where it\n * keeps the shallowest value rather than an average. */\nexport const DEPTH_HISTORY_METHOD = \"min\";\n\n/** Query for the depth series with the position of each sample. An explicit\n * `resolution` (seconds, fractional allowed) is honoured by the provider; when\n * it is omitted most providers auto-downsample to a few hundred points, so the\n * caller should always pass the finest interval it wants. `context` defaults to\n * the server's own vessel. */\nexport function historyValuesQuery(opts: {\n depthPath: string;\n from: number;\n to: number;\n resolution?: number;\n context?: string;\n}): string {\n return queryString({\n context: opts.context,\n paths: `navigation.position,${opts.depthPath}:${DEPTH_HISTORY_METHOD},navigation.headingTrue:average`,\n from: new Date(opts.from).toISOString(),\n to: new Date(opts.to).toISOString(),\n resolution: opts.resolution,\n });\n}\n\n/** One depth sample from the History API, with its recorded position. */\nexport interface DepthHistoryRow {\n time: number;\n depth: number;\n position?: Position;\n /** True heading (radians) at the sample, to place it at the transducer. */\n heading?: number;\n}\n\n/** A `[lon, lat]` from either array or `{latitude, longitude}` form. */\nfunction toPosition(value: unknown): Position | undefined {\n if (Array.isArray(value) && value.length >= 2) {\n const [lon, lat] = value as number[];\n if (Number.isFinite(lon) && Number.isFinite(lat)) {\n return [lon, lat];\n }\n return undefined;\n }\n if (value && typeof value === \"object\") {\n const v = value as { latitude?: unknown; longitude?: unknown };\n if (typeof v.latitude === \"number\" && typeof v.longitude === \"number\") {\n return [v.longitude, v.latitude];\n }\n }\n return undefined;\n}\n\n/** Depth rows from a History API `values` response, matched to `depthPath`.\n * The `position` column is attached when the response carried one; rows with a\n * null, non-number or negative depth are skipped. */\nexport function parseDepthSeries(\n result: unknown,\n depthPath: string,\n): DepthHistoryRow[] {\n const values = (result as { values?: Array<{ path?: string }> })?.values;\n const data = (result as { data?: unknown[] })?.data;\n if (!Array.isArray(values) || !Array.isArray(data)) {\n return [];\n }\n const depthCol = values.findIndex((v) => v?.path === depthPath);\n if (depthCol < 0) {\n return [];\n }\n const posCol = values.findIndex((v) => v?.path === \"navigation.position\");\n const headingCol = values.findIndex(\n (v) => v?.path === \"navigation.headingTrue\",\n );\n const rows: DepthHistoryRow[] = [];\n for (const row of data) {\n if (!Array.isArray(row)) {\n continue;\n }\n const time = Date.parse(String(row[0]));\n const depth = row[depthCol + 1];\n if (\n !Number.isFinite(time) ||\n typeof depth !== \"number\" ||\n !Number.isFinite(depth) ||\n depth < 0\n ) {\n continue;\n }\n const rec: DepthHistoryRow = { time, depth };\n if (posCol >= 0) {\n const position = toPosition(row[posCol + 1]);\n if (position) {\n rec.position = position;\n }\n }\n if (headingCol >= 0) {\n const heading = row[headingCol + 1];\n if (typeof heading === \"number\" && Number.isFinite(heading)) {\n rec.heading = heading;\n }\n }\n rows.push(rec);\n }\n return rows;\n}\n", "/** The extension's user settings, shared by the config panel (which edits\n * them), the runtime (which applies them) and the render Worker (which draws\n * with them). Persisted in host state under the extension scope. */\n\nexport type DepthSource = \"history\" | \"tiles\";\n\n/** Overlay render quality. `auto` measures the worker's own frame cost and\n * scales to a budget; the rest pin a scale, so a weak GPU (a laptop iGPU) can\n * be dialled down and a fast one dialled up. */\nexport type DepthQuality = \"auto\" | \"high\" | \"balanced\" | \"low\";\n\nexport interface LiveDepthSettings {\n enabled: boolean;\n /** Where the soundings come from: the server's History API (the vessel's own\n * recent track) or the plugin's gridded bathymetry tiles. */\n source: DepthSource;\n /** Overlay render quality (see {@link DepthQuality}). */\n quality: DepthQuality;\n /** Render the shaded surface at the host's full requested size, ignoring the\n * render-quality scale. */\n fullResolution: boolean;\n /** Draw isobars as vector polylines (crisp at any zoom) instead of the\n * per-fragment raster approximation. */\n vectorContours: boolean;\n /** Antialias the WebGL surface, so the coverage/discard edges are smooth. */\n msaa: boolean;\n /** Cap on either depth-field dimension. Higher resolves finer contours at the\n * cost of memory and build time. */\n fieldCells: number;\n /** Major (index) vector isobar line width, in screen pixels. */\n contourWidthPx: number;\n /** Minor vector isobar line width, in screen pixels. */\n minorContourWidthPx: number;\n /** Major (index) isobar colour (CSS). */\n majorContourColor: string;\n /** Minor isobar colour (CSS). */\n minorContourColor: string;\n /** History look-back window, in hours. */\n historyWindowHours: number;\n /** History sample interval, in seconds. */\n historyResolutionS: number;\n /** Depth (metres) the shading runs to; deeper is left to the chart. */\n shadeLimitMetres: number;\n /** Depth (metres) between major isobaths (the index contours). */\n contourIntervalMetres: number;\n /** Depth (metres) between minor isobaths. */\n minorContourMetres: number;\n /** Radius (metres) over which a sounding fills water with no samples of its\n * own, so a single pass paints a readable swath instead of a hairline track.\n * Measured cells keep their own depth; the fill fades linearly to nothing at\n * this radius. 0 disables the fill. */\n fillMetres: number;\n /** Grid cell size in screen pixels; also the base label size. */\n cellPx: number;\n /** Fractional depth difference (0.15 = 15%) within which adjacent cells\n * merge into one group. */\n mergeTolerance: number;\n /** Samples deeper than this (metres) are not drawn. */\n maxDepthMetres: number;\n /** Samples at or below this depth (metres) are marked individually. */\n safetyDepthMetres: number;\n}\n\nexport const DEFAULT_SETTINGS: LiveDepthSettings = {\n enabled: true,\n source: \"history\",\n quality: \"auto\",\n fullResolution: true,\n vectorContours: true,\n msaa: true,\n fieldCells: 2048,\n contourWidthPx: 1,\n minorContourWidthPx: 0.75,\n majorContourColor: \"#000000\",\n minorContourColor: \"#9aa4a8\",\n historyWindowHours: 1,\n historyResolutionS: 0.1,\n shadeLimitMetres: 10,\n contourIntervalMetres: 1.524,\n minorContourMetres: 0.3048,\n fillMetres: 6,\n cellPx: 24,\n mergeTolerance: 0.15,\n maxDepthMetres: 100,\n safetyDepthMetres: 3,\n};\n\n/** The render scale (fraction of the host's requested frame size) each fixed\n * quality pins; `auto` is driven by the worker's own measurement instead. */\nexport const QUALITY_SCALE: Record<Exclude<DepthQuality, \"auto\">, number> = {\n high: 1,\n balanced: 0.6,\n low: 0.4,\n};\n\n/** The allowed values of the settings that are a closed set of strings. */\nconst ENUM_VALUES: Partial<Record<keyof LiveDepthSettings, readonly string[]>> =\n {\n source: [\"history\", \"tiles\"],\n quality: [\"auto\", \"high\", \"balanced\", \"low\"],\n };\n\n/** The settings keys, with the bounds the config panel clamps to. */\nexport const SETTINGS_BOUNDS: Partial<\n Record<keyof LiveDepthSettings, { min: number; max: number; step: number }>\n> = {\n historyWindowHours: { min: 0.1, max: 168, step: 0.1 },\n historyResolutionS: { min: 0.01, max: 60, step: 0.01 },\n shadeLimitMetres: { min: 1, max: 200, step: 1 },\n contourIntervalMetres: { min: 0.3048, max: 50, step: 0.3048 },\n minorContourMetres: { min: 0.3048, max: 50, step: 0.3048 },\n fillMetres: { min: 0, max: 20, step: 1 },\n cellPx: { min: 4, max: 128, step: 1 },\n fieldCells: { min: 256, max: 4096, step: 256 },\n contourWidthPx: { min: 0.5, max: 6, step: 0.5 },\n minorContourWidthPx: { min: 0.5, max: 6, step: 0.25 },\n mergeTolerance: { min: 0, max: 1, step: 0.01 },\n maxDepthMetres: { min: 1, max: 500, step: 1 },\n safetyDepthMetres: { min: 0, max: 50, step: 0.5 },\n};\n\n/** Merge stored state over the defaults, ignoring unknown/ill-typed keys. */\nexport function mergeSettings(\n stored: Record<string, unknown> | undefined,\n): LiveDepthSettings {\n const out = { ...DEFAULT_SETTINGS };\n if (!stored) {\n return out;\n }\n for (const key of Object.keys(\n DEFAULT_SETTINGS,\n ) as (keyof LiveDepthSettings)[]) {\n const value = stored[key];\n if (typeof value !== typeof DEFAULT_SETTINGS[key]) {\n continue;\n }\n const allowed = ENUM_VALUES[key];\n if (allowed && !allowed.includes(value as string)) {\n continue;\n }\n (out[key] as unknown) = value;\n }\n return out;\n}\n", "/// <reference lib=\"webworker\" />\n//\n// The render Worker: turns the soundings into one overlay frame.\n//\n// It is kept off the runtime iframe's main thread \u2014 on several engines an\n// extension iframe shares the host page's main thread, so a heavy render here\n// would stall the whole chart. The runtime sends it the soundings and settings;\n// on each `render` request it builds (once per data change) the depth field,\n// runs a WebGL2 pass that shades the surface and draws isobaths, composites\n// that onto a 2D canvas that adds the shoal/cluster dots and their depth\n// labels, and hands the result back as an ImageBitmap.\n//\n// Geometry: the host asks for a north-up, axis-aligned EPSG:3857 box whose\n// pixels are evenly spaced in Mercator metres. The field is built in the same\n// space, so a frame is a re-raster of the cached field \u2014 panning or zooming\n// never changes the surface (and the absolute-depth isobars stay put).\n\nimport {\n createDepthFieldBuilder,\n type CellRect,\n type DepthField,\n type DepthFieldBuilder,\n} from \"./shared/bathymetry.js\";\nimport { DEPTH_FRAG_SRC, DEPTH_VERT_SRC } from \"./shared/shaders.js\";\nimport {\n extractContours,\n type ContourSegment,\n type ContourSet,\n} from \"./shared/contours.js\";\nimport {\n DEFAULT_COVERAGE_MIN_M,\n medianConsecutiveGap,\n} from \"./shared/bathymetry.js\";\nimport {\n SHADE_CONTOUR_LEVELS,\n depthClusters,\n depthCss,\n latOf,\n lonOf,\n mercX,\n mercY,\n selectShoals,\n shadeColor,\n type DepthCluster,\n type DepthSample,\n} from \"./shared/live-depth.js\";\nimport { QUALITY_SCALE, type LiveDepthSettings } from \"./shared/settings.js\";\nimport type {\n RenderRequest,\n RenderWorkerIn,\n RenderWorkerOut,\n} from \"./shared/protocol.js\";\n\n/** Hard cap on a field dimension; if the cell would exceed it the cells are\n * grown to fit, so the grid always covers the whole data extent. */\nconst MAX_FIELD_CELLS = 1024;\n/** Label size (CSS px): scaled with zoom between a readable floor and a cap. */\nconst LABEL_FONT_MIN = 12;\nconst LABEL_FONT_MAX = 20;\n/** Smallest on-screen radius (CSS px) at which a group is labelled. */\nconst LABEL_MIN_RADIUS_PX = 8;\n\nconst ORIGIN_SHIFT = Math.PI * 6378137;\nconst WORLD_WIDTH = 2 * ORIGIN_SHIFT;\n\nlet samples: DepthSample[] = [];\n/** The server-derived soundings, replaced as a whole on a base update. */\nlet baseSamples: DepthSample[] = [];\n/** The own-vessel soundings folded in since, appended and never rebuilt. */\nlet liveSamples: DepthSample[] = [];\nlet settings: LiveDepthSettings | null = null;\nlet builder: DepthFieldBuilder | null = null;\nlet fieldDirty = true;\n/** Bumped whenever the depth texture changes, so the cached surface is rebuilt. */\nlet fieldVersion = 0;\n/** Last rendered surface (WebGL) and 2D overlay, keyed on everything that\n * affects them. Reusing them skips the per-pixel shader and the overlay's\n * canvas work on requests where nothing changed (the host asks for a frame\n * far more often than the field or the view changes). */\nlet surfaceCache: {\n key: string;\n image: ImageBitmap;\n box: [number, number, number, number];\n width: number;\n height: number;\n} | null = null;\n/** The output frame canvas, reused across requests (a fresh 2560\u00D71267 canvas\n * per frame is a ~13 MB allocation and a full clear every time). */\nlet outCanvas: OffscreenCanvas | null = null;\nlet outCtx: OffscreenCanvasRenderingContext2D | null = null;\n/** Each side of the surface is rendered this much wider than the view, so a\n * following or panning map reuses the rendered surface instead of re-running\n * the full-screen shader for every small move. 0.1 makes the bitmap 1.2\u00D7 the\n * view in each dimension, matching the host's OVERLAY_MARGIN. */\nconst SURFACE_PAD = 0.1;\nlet overlayCache: { key: string; image: ImageBitmap } | null = null;\nlet overlayCanvas: OffscreenCanvas | null = null;\nlet overlayCtx: OffscreenCanvasRenderingContext2D | null = null;\n/** The vessel's live position/heading + dimensions, for the debug footprint. */\nlet vessel: {\n position: [number, number] | null;\n heading?: number;\n loa?: number;\n beam?: number;\n} = { position: null };\n/** The user's preferred depth unit (`\"m\"` or `\"foot\"`), for the labels. */\nlet depthUnit = \"m\";\n\n/** Bumped whenever `samples` changes, so the overlay caches below recompute on\n * a data change rather than on every render. */\nlet samplesVersion = 0;\n/** The shoal samples and depth clusters, keyed on their inputs: a frame that\n * only moves the view (or is a live tick with no new soundings) reuses them\n * instead of re-aggregating every sample. */\nlet shoalCache: { key: string; value: DepthSample[] } | null = null;\nlet clusterCache: { key: string; value: DepthCluster[] } | null = null;\n/** Vector contours for the current field, in world metres. */\nlet contours: ContourSet = { major: [], minor: [] };\n/** The MSAA setting the live GL context was created with. */\nlet gpuMsaa = true;\n\n/** Budget for one frame's worker work, in ms; `auto` scales the shaded surface\n * to hold it. */\nconst AUTO_FRAME_BUDGET_MS = 10;\n/** Frames between auto adjustments. Adjusting every frame makes the surface\n * hunt (and the map shimmer); a couple of seconds apart lets it settle. */\nconst AUTO_ADJUST_EVERY = 30;\n/** Frames between perf readouts to the runtime. */\nconst PERF_REPORT_EVERY = 20;\n/** The scale `auto` has settled on; stepped occasionally. */\nlet autoScale = 0.7;\n/** Rolling average of the frame cost, for a stable auto decision. */\nlet frameAvgMs = 0;\n/** Frames since the last auto adjustment. */\nlet framesSinceAdjust = 0;\n/** Frames since the last perf readout. */\nlet perfFrames = 0;\n\n/** The shaded-surface scale for this frame: pinned by the quality setting, or\n * the adaptive value in `auto`. Only the WebGL surface is drawn at this scale;\n * the dots, clusters and labels are always drawn at full resolution, so the\n * map stays readable and keeps every depth measurement. */\nfunction renderScale(): number {\n // Full-resolution renders the surface at the host's requested size, ignoring\n // the quality scale (crowd-depth parity).\n if (settings?.fullResolution) return 1;\n const quality = settings?.quality ?? \"auto\";\n return quality === \"auto\" ? autoScale : QUALITY_SCALE[quality];\n}\n\n/** A depth in the user's unit: whole feet, or a tenth of a metre. */\nfunction formatDepth(metres: number): string {\n return depthUnit === \"foot\"\n ? (metres / 0.3048).toFixed(0)\n : metres.toFixed(1);\n}\n\n// ******** WebGL ********\n\nlet glCanvas: OffscreenCanvas | null = null;\nlet gl: WebGL2RenderingContext | null = null;\nlet program: WebGLProgram | null = null;\nlet quad: WebGLBuffer | null = null;\nlet depthTexture: WebGLTexture | null = null;\nlet lutTexture: WebGLTexture | null = null;\nlet aPos = 0;\nlet uDepth: WebGLUniformLocation | null = null;\nlet uLut: WebGLUniformLocation | null = null;\nlet uTexel: WebGLUniformLocation | null = null;\nlet uView: WebGLUniformLocation | null = null;\nlet uField: WebGLUniformLocation | null = null;\nlet uShadeLimit: WebGLUniformLocation | null = null;\nlet uLevels: WebGLUniformLocation | null = null;\nlet uInterval: WebGLUniformLocation | null = null;\nlet uLineWidth: WebGLUniformLocation | null = null;\nlet uIsobars: WebGLUniformLocation | null = null;\n\nfunction compile(\n ctx: WebGL2RenderingContext,\n type: number,\n src: string,\n): WebGLShader | null {\n const shader = ctx.createShader(type);\n if (!shader) return null;\n ctx.shaderSource(shader, src);\n ctx.compileShader(shader);\n if (!ctx.getShaderParameter(shader, ctx.COMPILE_STATUS)) {\n ctx.deleteShader(shader);\n return null;\n }\n return shader;\n}\n\nfunction initWebgl(): boolean {\n if (gl && !gl.isContextLost()) return true;\n if (gl) releaseGpu();\n glCanvas = new OffscreenCanvas(1, 1);\n const msaa = settings?.msaa ?? true;\n const ctx = glCanvas.getContext(\"webgl2\", {\n alpha: true,\n premultipliedAlpha: true,\n antialias: msaa,\n }) as WebGL2RenderingContext | null;\n if (!ctx) return false;\n // A backgrounded tab can have its WebGL context evicted. Without this the\n // worker keeps drawing into a dead context and the next frame is garbage\n // (the surface dissolves into large blocks). Prevent default so the browser\n // may restore it, drop the GPU state and re-upload the field on re-init.\n ctx.canvas.addEventListener(\"webglcontextlost\", (event) => {\n event.preventDefault();\n releaseGpu();\n fieldDirty = true;\n });\n gpuMsaa = msaa;\n const vs = compile(ctx, ctx.VERTEX_SHADER, DEPTH_VERT_SRC);\n const fs = compile(ctx, ctx.FRAGMENT_SHADER, DEPTH_FRAG_SRC);\n const prog = ctx.createProgram();\n if (!vs || !fs || !prog) return false;\n ctx.attachShader(prog, vs);\n ctx.attachShader(prog, fs);\n ctx.linkProgram(prog);\n if (!ctx.getProgramParameter(prog, ctx.LINK_STATUS)) return false;\n\n gl = ctx;\n program = prog;\n aPos = ctx.getAttribLocation(prog, \"aPos\");\n uDepth = ctx.getUniformLocation(prog, \"uDepth\");\n uLut = ctx.getUniformLocation(prog, \"uLut\");\n uTexel = ctx.getUniformLocation(prog, \"uTexel\");\n uView = ctx.getUniformLocation(prog, \"uView\");\n uField = ctx.getUniformLocation(prog, \"uField\");\n uShadeLimit = ctx.getUniformLocation(prog, \"uShadeLimit\");\n uLevels = ctx.getUniformLocation(prog, \"uLevels\");\n uInterval = ctx.getUniformLocation(prog, \"uInterval\");\n uLineWidth = ctx.getUniformLocation(prog, \"uLineWidth\");\n uIsobars = ctx.getUniformLocation(prog, \"uIsobars\");\n\n quad = ctx.createBuffer();\n ctx.bindBuffer(ctx.ARRAY_BUFFER, quad);\n ctx.bufferData(\n ctx.ARRAY_BUFFER,\n new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),\n ctx.STATIC_DRAW,\n );\n\n depthTexture = ctx.createTexture();\n ctx.bindTexture(ctx.TEXTURE_2D, depthTexture);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MIN_FILTER, ctx.NEAREST);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MAG_FILTER, ctx.NEAREST);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_S, ctx.CLAMP_TO_EDGE);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_T, ctx.CLAMP_TO_EDGE);\n\n lutTexture = ctx.createTexture();\n const lut = new Uint8Array(256 * 4);\n for (let i = 0; i < 256; i++) {\n const [r, g, b] = shadeColor(i / 255);\n lut[i * 4] = r;\n lut[i * 4 + 1] = g;\n lut[i * 4 + 2] = b;\n lut[i * 4 + 3] = 255;\n }\n ctx.bindTexture(ctx.TEXTURE_2D, lutTexture);\n ctx.texImage2D(\n ctx.TEXTURE_2D,\n 0,\n ctx.RGBA,\n 256,\n 1,\n 0,\n ctx.RGBA,\n ctx.UNSIGNED_BYTE,\n lut,\n );\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MIN_FILTER, ctx.LINEAR);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MAG_FILTER, ctx.LINEAR);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_S, ctx.CLAMP_TO_EDGE);\n ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_T, ctx.CLAMP_TO_EDGE);\n\n return true;\n}\n\n/** Release the GL context while the overlay is hidden or suspended. */\nfunction releaseGpu(): void {\n if (gl) {\n gl.getExtension(\"WEBGL_lose_context\")?.loseContext();\n }\n gl = null;\n program = null;\n quad = null;\n depthTexture = null;\n lutTexture = null;\n glCanvas = null;\n surfaceCache?.image.close();\n surfaceCache = null;\n outCanvas = null;\n outCtx = null;\n overlayCache?.image.close();\n overlayCache = null;\n overlayCanvas = null;\n overlayCtx = null;\n}\n\nfunction uploadField(f: DepthField): void {\n if (!gl || !depthTexture) return;\n // GLSL ES does not require NaN support, so the no-coverage marker the field\n // uses on the CPU (NaN) is uploaded as -1 \u2014 a value no real depth can take \u2014\n // and the shader tests `v < 0` for it.\n const gpu = new Float32Array(f.depth.length);\n for (let i = 0; i < f.depth.length; i++) {\n const d = f.depth[i];\n gpu[i] = Number.isNaN(d) ? -1 : d;\n }\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, depthTexture);\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n gl.R32F,\n f.width,\n f.height,\n 0,\n gl.RED,\n gl.FLOAT,\n gpu,\n );\n fieldVersion++;\n}\n\n/** Upload only the cells in `rect` (half-open), so folding a live sounding in\n * costs a few rows of texture rather than the whole field. */\nfunction uploadRegion(f: DepthField, [x0, y0, x1, y1]: CellRect): void {\n if (!gl || !depthTexture) return;\n const w = x1 - x0;\n const h = y1 - y0;\n if (w <= 0 || h <= 0) return;\n const gpu = new Float32Array(w * h);\n for (let y = 0; y < h; y++) {\n const src = (y0 + y) * f.width + x0;\n const dst = y * w;\n for (let x = 0; x < w; x++) {\n const d = f.depth[src + x];\n gpu[dst + x] = Number.isNaN(d) ? -1 : d;\n }\n }\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, depthTexture);\n gl.texSubImage2D(gl.TEXTURE_2D, 0, x0, y0, w, h, gl.RED, gl.FLOAT, gpu);\n fieldVersion++;\n}\n\n/** Rebuild the field around the current base (a view change or a settings\n * change), then fold the own-vessel soundings back in. This is the only path\n * that pays for a whole-grid rebuild; a live flush just patches cells. */\nfunction rebuildField(): void {\n builder = null;\n samples = [...baseSamples, ...liveSamples];\n // Size the grid off the server data, which covers the view; a live sounding\n // outside it is clipped until the next base refresh rather than growing the\n // grid (and forcing a rebuild) on every flush.\n const source = baseSamples.length ? baseSamples : samples;\n if (source.length === 0 || !settings) return;\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const s of source) {\n const x = mercX(s.lon);\n const y = mercY(s.lat);\n minX = Math.min(minX, x);\n maxX = Math.max(maxX, x);\n minY = Math.min(minY, y);\n maxY = Math.max(maxY, y);\n }\n const padX = (maxX - minX) * 0.1 + 1;\n const padY = (maxY - minY) * 0.1 + 1;\n const extent: [number, number, number, number] = [\n minX - padX,\n minY - padY,\n maxX + padX,\n maxY + padY,\n ];\n const extMax = Math.max(extent[2] - extent[0], extent[3] - extent[1]);\n // Tile samples are already gridded, not along-track ordered, so a\n // consecutive-gap estimate is meaningless; the target cell size is the field\n // resolution instead.\n const tiles = settings.source === \"tiles\";\n const gap = tiles ? 0 : medianConsecutiveGap(source);\n const minCover = tiles ? 0 : DEFAULT_COVERAGE_MIN_M;\n const maxCells = Math.max(\n 64,\n Math.round(settings.fieldCells) || MAX_FIELD_CELLS,\n );\n const cell = Math.max(gap, extMax / maxCells, minCover / 8, 0.01);\n const next = createDepthFieldBuilder(extent, {\n cellMetres: cell,\n maxCells,\n // `gap` bridges the along-track gaps, the fill radius widens a sounding\n // into empty water, and a sounding always covers its own cell.\n coverageMetres: Math.max(cell * 1.5, gap * 1.5, settings.fillMetres),\n coverageMinMetres: minCover,\n });\n if (!next) return;\n next.add(baseSamples);\n if (liveSamples.length) next.add(liveSamples);\n builder = next;\n uploadField(next.field);\n // Trace the vector isobars once per (re)build. Live soundings update the\n // surface immediately; the contours catch up on the next base fetch.\n contours = settings.vectorContours\n ? extractContours(\n next.field,\n Math.max(0.01, settings.minorContourMetres),\n Math.max(0.01, settings.contourIntervalMetres),\n Math.max(0, settings.maxDepthMetres),\n )\n : { major: [], minor: [] };\n}\n\n/** Fold new own-vessel soundings in, refreshing only the cells they reach. */\nfunction addLive(newSamples: DepthSample[]): void {\n liveSamples.push(...newSamples);\n samples.push(...newSamples);\n samplesVersion++;\n if (!builder) {\n // No field yet (the base has not arrived): rebuild once it does.\n fieldDirty = true;\n return;\n }\n const rect = builder.add(newSamples);\n if (rect) uploadRegion(builder.field, rect);\n}\n\nfunction renderGpu(\n box: readonly [number, number, number, number],\n canvasW: number,\n canvasH: number,\n ratio: number,\n): void {\n const f = builder?.field;\n if (!gl || !f || !glCanvas) return;\n if (glCanvas.width !== canvasW || glCanvas.height !== canvasH) {\n glCanvas.width = canvasW;\n glCanvas.height = canvasH;\n }\n gl.viewport(0, 0, canvasW, canvasH);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.useProgram(program);\n\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, depthTexture);\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, lutTexture);\n\n const ext = f.extent;\n const fieldCentreX = (ext[0] + ext[2]) / 2;\n const viewCentreX = (box[0] + box[2]) / 2;\n const shift =\n Math.round((fieldCentreX - viewCentreX) / WORLD_WIDTH) * WORLD_WIDTH;\n // Pass field-relative metres to the shader: absolute EPSG:3857 values\n // (~8.6e6) lose about a metre of precision in the shader's float32 maths.\n gl.uniform4f(\n uView,\n box[0] + shift - ext[0],\n box[1] - ext[1],\n box[2] - box[0],\n box[3] - box[1],\n );\n gl.uniform4f(uField, 0, 0, ext[2] - ext[0], ext[3] - ext[1]);\n gl.uniform2f(uTexel, 1 / f.width, 1 / f.height);\n gl.uniform1i(uDepth, 0);\n gl.uniform1i(uLut, 1);\n gl.uniform1f(uShadeLimit, Math.max(0.01, settings?.shadeLimitMetres ?? 10));\n gl.uniform1i(uLevels, SHADE_CONTOUR_LEVELS);\n gl.uniform1f(uInterval, Math.max(0.01, settings?.contourIntervalMetres ?? 1));\n gl.uniform1f(uLineWidth, Math.max(1, ratio));\n gl.uniform1f(uIsobars, settings?.vectorContours ? 0 : 1);\n\n gl.bindBuffer(gl.ARRAY_BUFFER, quad);\n gl.enableVertexAttribArray(aPos);\n gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n}\n\n// ******** 2D overlay (dots + labels) ********\n\n/** Draw the vector isobars (traced from the field) on the 2D overlay. */\nfunction drawContours(\n ctx: OffscreenCanvasRenderingContext2D,\n box: readonly [number, number, number, number],\n canvasW: number,\n canvasH: number,\n ratio: number,\n): void {\n if (!settings?.vectorContours) return;\n const f = builder?.field;\n if (!f || (contours.major.length === 0 && contours.minor.length === 0)) {\n return;\n }\n const boxW = box[2] - box[0];\n const boxH = box[3] - box[1];\n if (!(boxW > 0) || !(boxH > 0)) return;\n // Match the surface's world copy (see renderGpu).\n const ext = f.extent;\n const shift =\n Math.round(((ext[0] + ext[2]) / 2 - (box[0] + box[2]) / 2) / WORLD_WIDTH) *\n WORLD_WIDTH;\n const px = (x: number) => ((x + shift - box[0]) / boxW) * canvasW;\n const py = (y: number) => ((box[3] - y) / boxH) * canvasH;\n const stroke = (\n segments: ContourSegment[],\n color: string,\n widthPx: number,\n ) => {\n if (segments.length === 0) return;\n ctx.beginPath();\n for (const [x0, y0, x1, y1] of segments) {\n ctx.moveTo(px(x0), py(y0));\n ctx.lineTo(px(x1), py(y1));\n }\n ctx.lineWidth = Math.max(0.5, widthPx * ratio);\n ctx.lineCap = \"round\";\n ctx.lineJoin = \"round\";\n ctx.strokeStyle = color;\n ctx.stroke();\n };\n // Minor first, so the darker index contours sit on top.\n stroke(\n contours.minor,\n settings.minorContourColor,\n settings.minorContourWidthPx,\n );\n stroke(contours.major, settings.majorContourColor, settings.contourWidthPx);\n}\n\nfunction drawOverlay(\n ctx: OffscreenCanvasRenderingContext2D,\n box: readonly [number, number, number, number],\n canvasW: number,\n canvasH: number,\n resolution: number,\n ratio: number,\n zoom: number,\n): void {\n if (!settings) return;\n const boxW = box[2] - box[0];\n const boxH = box[3] - box[1];\n const centreX = (box[0] + box[2]) / 2;\n const toPixel = (lon: number, lat: number): [number, number] => {\n const mx = mercX(lon);\n const my = mercY(lat);\n const x = mx + Math.round((centreX - mx) / WORLD_WIDTH) * WORLD_WIDTH;\n return [((x - box[0]) / boxW) * canvasW, ((box[3] - my) / boxH) * canvasH];\n };\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n\n const dot = (p: [number, number], depth: number) => {\n ctx.beginPath();\n ctx.arc(\n p[0],\n p[1],\n Math.max(1.5, settings!.cellPx * 0.12) * ratio,\n 0,\n Math.PI * 2,\n );\n ctx.fillStyle = depthCss(depth, settings!.shadeLimitMetres, 0.95);\n ctx.fill();\n };\n const label = (text: string, p: [number, number], fontCss: number) => {\n ctx.font = `${Math.round(fontCss * ratio)}px Roboto, Arial, sans-serif`;\n ctx.lineWidth = Math.max(2, 2 * ratio);\n ctx.strokeStyle = \"rgba(255,255,255,0.85)\";\n ctx.strokeText(text, p[0], p[1]);\n ctx.fillStyle = \"rgba(0,0,0,0.95)\";\n ctx.fillText(text, p[0], p[1]);\n };\n\n drawContours(ctx, box, canvasW, canvasH, ratio);\n\n // Recomputing these over every sample each frame is the worker's dominant\n // cost; they depend only on the data and the settings (the cluster cell size\n // also on the view resolution), so cache them and rebuild only on a key\n // change \u2014 a frame that just moves the view reuses them.\n const shoalKey = `${samplesVersion}:${settings.safetyDepthMetres}`;\n if (!shoalCache || shoalCache.key !== shoalKey) {\n shoalCache = {\n key: shoalKey,\n value: selectShoals(samples, settings.safetyDepthMetres),\n };\n }\n shoalCache.value.forEach((s) => dot(toPixel(s.lon, s.lat), s.depth));\n\n const cellSize = Math.max(1, settings.cellPx * resolution);\n const clusterKey = `${samplesVersion}:${cellSize}:${settings.mergeTolerance}:${settings.maxDepthMetres}`;\n if (!clusterCache || clusterCache.key !== clusterKey) {\n clusterCache = {\n key: clusterKey,\n value: depthClusters(\n samples,\n cellSize,\n settings.mergeTolerance,\n settings.maxDepthMetres,\n ),\n };\n }\n const clusters = clusterCache.value;\n for (const c of clusters) {\n const p = toPixel(c.lon, c.lat);\n const radiusPx = (c.radiusMetres / resolution) * ratio;\n if (radiusPx >= LABEL_MIN_RADIUS_PX) {\n const fontCss = Math.max(\n LABEL_FONT_MIN,\n Math.min(LABEL_FONT_MAX, (zoom - 8) * 1.5 + LABEL_FONT_MIN),\n );\n label(formatDepth(c.depth), p, fontCss);\n } else {\n dot(p, c.depth);\n }\n }\n\n // Debug: the vessel's footprint (LOA \u00D7 beam) centred on the reported position\n // and rotated to the heading, so where a sounding lands relative to the hull\n // can be seen. Off unless the plugin's debug config turns it on.\n const v = vessel;\n if (v.position && v.heading !== undefined && v.loa && v.beam) {\n const cx = mercX(v.position[0]);\n const cy = mercY(v.position[1]);\n const cos = Math.cos(v.heading);\n const sin = Math.sin(v.heading);\n const halfLength = v.loa / 2;\n const halfBeam = v.beam / 2;\n const corners = (\n [\n [halfLength, halfBeam],\n [halfLength, -halfBeam],\n [-halfLength, -halfBeam],\n [-halfLength, halfBeam],\n ] as const\n ).map(([fwd, stbd]) =>\n toPixel(\n lonOf(cx + fwd * sin + stbd * cos),\n latOf(cy + fwd * cos - stbd * sin),\n ),\n );\n ctx.beginPath();\n corners.forEach(([px, py], i) =>\n i === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py),\n );\n ctx.closePath();\n ctx.lineWidth = Math.max(1.5, 1.5 * ratio);\n ctx.strokeStyle = \"rgba(255, 0, 255, 0.95)\";\n ctx.stroke();\n ctx.fillStyle = \"rgba(255, 0, 255, 0.12)\";\n ctx.fill();\n }\n}\n\n// ******** message handling ********\n\nfunction render(req: RenderRequest): void {\n try {\n if (!initWebgl()) {\n post({ type: \"error\", message: \"WebGL2 unavailable\" });\n return;\n }\n if (fieldDirty || !builder) {\n rebuildField();\n fieldDirty = false;\n }\n const { extent, width, height, pixelRatio, resolution, zoom } = req;\n const box: [number, number, number, number] = [\n extent[0],\n extent[1],\n extent[2],\n extent[3],\n ];\n // Only the shaded WebGL surface is drawn at the quality scale (the shading\n // is smooth, so it tolerates it). It is upscaled onto the frame, and the\n // dots, clusters and labels are then drawn at full resolution on top \u2014 so\n // every sounding stays sharp and the cluster cell size (which depends on\n // the frame resolution) never coarsens. The frame stays exactly\n // `width \u00D7 height`, as the API requires.\n const scale = renderScale();\n const surfaceW = Math.max(1, Math.round(width * scale));\n const surfaceH = Math.max(1, Math.round(height * scale));\n const started = performance.now();\n if (\n !outCanvas ||\n !outCtx ||\n outCanvas.width !== width ||\n outCanvas.height !== height\n ) {\n outCanvas = new OffscreenCanvas(width, height);\n outCtx = outCanvas.getContext(\"2d\");\n }\n const out = outCanvas;\n const ctx = outCtx;\n if (!ctx) {\n post({ type: \"error\", message: \"no 2D context\" });\n return;\n }\n ctx.clearRect(0, 0, width, height);\n let redrew = false;\n if (builder && glCanvas) {\n const padX = (box[2] - box[0]) * SURFACE_PAD;\n const padY = (box[3] - box[1]) * SURFACE_PAD;\n const padBox: [number, number, number, number] = [\n box[0] - padX,\n box[1] - padY,\n box[2] + padX,\n box[3] + padY,\n ];\n const padW = Math.max(1, Math.round(surfaceW * (1 + SURFACE_PAD * 2)));\n const padH = Math.max(1, Math.round(surfaceH * (1 + SURFACE_PAD * 2)));\n const surfaceKey = [\n fieldVersion,\n padW,\n padH,\n pixelRatio,\n settings?.shadeLimitMetres,\n settings?.contourIntervalMetres,\n settings?.vectorContours,\n settings?.msaa,\n ].join(\"|\");\n const cached = surfaceCache;\n const covers =\n !!cached &&\n cached.key === surfaceKey &&\n box[0] >= cached.box[0] &&\n box[1] >= cached.box[1] &&\n box[2] <= cached.box[2] &&\n box[3] <= cached.box[3];\n if (!covers) {\n renderGpu(padBox, padW, padH, pixelRatio * scale);\n cached?.image.close();\n surfaceCache = {\n key: surfaceKey,\n image: glCanvas.transferToImageBitmap(),\n box: padBox,\n width: padW,\n height: padH,\n };\n redrew = true;\n }\n const cache = surfaceCache!;\n // Blit the part of the padded surface the current view covers.\n const cw = cache.box[2] - cache.box[0];\n const ch = cache.box[3] - cache.box[1];\n const sx = ((box[0] - cache.box[0]) / cw) * cache.width;\n const sy = ((cache.box[3] - box[3]) / ch) * cache.height;\n const sw = ((box[2] - box[0]) / cw) * cache.width;\n const sh = ((box[3] - box[1]) / ch) * cache.height;\n ctx.drawImage(cache.image, sx, sy, sw, sh, 0, 0, width, height);\n }\n const overlayKey = [\n fieldVersion,\n samplesVersion,\n box.join(\",\"),\n width,\n height,\n resolution,\n pixelRatio,\n zoom,\n settings?.vectorContours,\n settings?.contourIntervalMetres,\n settings?.minorContourMetres,\n settings?.contourWidthPx,\n settings?.minorContourWidthPx,\n settings?.majorContourColor,\n settings?.minorContourColor,\n settings?.safetyDepthMetres,\n settings?.cellPx,\n settings?.mergeTolerance,\n settings?.maxDepthMetres,\n settings?.shadeLimitMetres,\n ].join(\"|\");\n if (!overlayCache || overlayCache.key !== overlayKey) {\n if (\n !overlayCanvas ||\n !overlayCtx ||\n overlayCanvas.width !== width ||\n overlayCanvas.height !== height\n ) {\n overlayCanvas = new OffscreenCanvas(width, height);\n overlayCtx = overlayCanvas.getContext(\"2d\");\n }\n if (overlayCtx) {\n overlayCtx.clearRect(0, 0, width, height);\n drawOverlay(\n overlayCtx,\n box,\n width,\n height,\n resolution,\n pixelRatio,\n zoom,\n );\n overlayCache?.image.close();\n overlayCache = {\n key: overlayKey,\n image: overlayCanvas.transferToImageBitmap(),\n };\n redrew = true;\n }\n }\n if (overlayCache) ctx.drawImage(overlayCache.image, 0, 0);\n const image = out.transferToImageBitmap();\n const frameMs = performance.now() - started;\n // Auto: smooth the cost and adjust only every few frames, with hysteresis,\n // so the surface settles instead of hunting under the user. `drawImage`\n // syncs the GL pass, so the window covers the real per-frame work.\n if (redrew && (settings?.quality ?? \"auto\") === \"auto\") {\n frameAvgMs =\n frameAvgMs === 0 ? frameMs : frameAvgMs * 0.8 + frameMs * 0.2;\n if (++framesSinceAdjust >= AUTO_ADJUST_EVERY) {\n framesSinceAdjust = 0;\n if (frameAvgMs > AUTO_FRAME_BUDGET_MS * 1.25) {\n autoScale = Math.max(0.4, autoScale - 0.1);\n } else if (frameAvgMs < AUTO_FRAME_BUDGET_MS * 0.6) {\n autoScale = Math.min(1, autoScale + 0.1);\n }\n }\n }\n if (++perfFrames >= PERF_REPORT_EVERY) {\n perfFrames = 0;\n post({ type: \"perf\", scale, frameMs });\n }\n post({ type: \"frame\", requestId: req.requestId, image }, [image]);\n } catch (err) {\n post({ type: \"error\", message: String(err) });\n }\n}\n\nfunction post(message: RenderWorkerOut, transfer?: Transferable[]): void {\n (self as unknown as Worker).postMessage(message, transfer ?? []);\n}\n\nself.onmessage = (e: MessageEvent<RenderWorkerIn>) => {\n const msg = e.data;\n switch (msg.type) {\n case \"base\":\n baseSamples = msg.samples;\n fieldDirty = true;\n samplesVersion++;\n break;\n case \"live\":\n addLive(msg.samples);\n break;\n case \"vessel\":\n vessel = msg;\n break;\n case \"settings\": {\n // MSAA is fixed when the GL context is created; drop it so the next\n // render recreates it with the new setting.\n if (msg.settings.msaa !== gpuMsaa) releaseGpu();\n settings = msg.settings;\n fieldDirty = true;\n break;\n }\n case \"units\":\n depthUnit = msg.depth;\n break;\n case \"render\":\n render(msg.req);\n break;\n case \"release\":\n releaseGpu();\n break;\n }\n};\n"],
5
+ "mappings": "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",
6
6
  "names": ["samples", "EARTH_RADIUS", "MERCATOR_MAX_LAT", "RAD", "mercX", "mercY", "samples", "mercX", "mercY"]
7
7
  }