signalk-livedepth-plugin 0.2.1 → 0.3.2
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/CHANGELOG.md +113 -0
- package/README.md +67 -41
- package/dist/collector.d.ts +1 -1
- package/dist/collector.d.ts.map +1 -1
- package/dist/config.d.ts +52 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +41 -4
- package/dist/config.js.map +1 -1
- package/dist/plotterext/manifest.d.ts +24 -0
- package/dist/plotterext/manifest.d.ts.map +1 -0
- package/dist/plotterext/manifest.js +79 -0
- package/dist/plotterext/manifest.js.map +1 -0
- package/dist/plotterext/provider.d.ts +7 -0
- package/dist/plotterext/provider.d.ts.map +1 -0
- package/dist/plotterext/provider.js +61 -0
- package/dist/plotterext/provider.js.map +1 -0
- package/dist/plugin.d.ts +15 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +40 -8
- package/dist/plugin.js.map +1 -1
- package/dist/streams/live.d.ts +16 -4
- package/dist/streams/live.d.ts.map +1 -1
- package/dist/streams/live.js +164 -43
- package/dist/streams/live.js.map +1 -1
- package/dist/tiles/api.d.ts +21 -11
- package/dist/tiles/api.d.ts.map +1 -1
- package/dist/tiles/api.js +36 -27
- package/dist/tiles/api.js.map +1 -1
- package/dist/tiles/core/accumulator.d.ts +37 -0
- package/dist/tiles/core/accumulator.d.ts.map +1 -0
- package/dist/tiles/core/accumulator.js +75 -0
- package/dist/tiles/core/accumulator.js.map +1 -0
- package/dist/tiles/core/correction.d.ts +32 -0
- package/dist/tiles/core/correction.d.ts.map +1 -0
- package/dist/tiles/core/correction.js +43 -0
- package/dist/tiles/core/correction.js.map +1 -0
- package/dist/tiles/core/grid.d.ts +64 -0
- package/dist/tiles/core/grid.d.ts.map +1 -0
- package/dist/tiles/core/grid.js +127 -0
- package/dist/tiles/core/grid.js.map +1 -0
- package/dist/tiles/core/index.d.ts +7 -0
- package/dist/tiles/core/index.d.ts.map +1 -0
- package/dist/tiles/core/index.js +7 -0
- package/dist/tiles/core/index.js.map +1 -0
- package/dist/tiles/core/store.d.ts +37 -0
- package/dist/tiles/core/store.d.ts.map +1 -0
- package/dist/tiles/core/store.js +86 -0
- package/dist/tiles/core/store.js.map +1 -0
- package/dist/tiles/core/tile-source.d.ts +50 -0
- package/dist/tiles/core/tile-source.d.ts.map +1 -0
- package/dist/tiles/core/tile-source.js +86 -0
- package/dist/tiles/core/tile-source.js.map +1 -0
- package/dist/tiles/core/types.d.ts +11 -0
- package/dist/tiles/core/types.d.ts.map +1 -0
- package/dist/tiles/core/types.js +2 -0
- package/dist/tiles/core/types.js.map +1 -0
- package/dist/tiles/index.d.ts +1 -1
- package/dist/tiles/index.d.ts.map +1 -1
- package/dist/tiles/index.js +1 -1
- package/dist/tiles/index.js.map +1 -1
- package/dist/tiles/seed.d.ts +1 -1
- package/dist/tiles/seed.d.ts.map +1 -1
- package/dist/tiles/seed.js +1 -1
- package/dist/tiles/seed.js.map +1 -1
- package/dist/tiles/store.d.ts +1 -1
- package/dist/tiles/store.d.ts.map +1 -1
- package/dist/tiles/store.js +1 -1
- package/dist/tiles/store.js.map +1 -1
- package/dist/tiles/writer.d.ts +1 -1
- package/dist/tiles/writer.d.ts.map +1 -1
- package/dist/tiles/writer.js +1 -1
- package/dist/tiles/writer.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +27 -6
- package/public/config.html +14 -0
- package/public/index.html +15 -0
- package/public/js/config.js +939 -0
- package/public/js/config.js.map +7 -0
- package/public/js/render-worker.js +866 -0
- package/public/js/render-worker.js.map +7 -0
- package/public/js/renderer.js +1159 -0
- package/public/js/renderer.js.map +7 -0
- package/public/renderer.html +14 -0
- package/public/styles.css +110 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/web/shared/bathymetry.ts", "../../src/web/shared/shaders.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. Interpolation is Inverse Distance Weighting, biased toward\n * the shallowest sample in range by `shallowBias` (1 = never deeper than that\n * sample) so a smoothed surface can never overstate depth.\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 /** Distance-weighting power (2 is the usual IDW choice). */\n power?: number;\n /** 0 = pure IDW, 1 = never deeper than the shallowest sample in 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 = 2;\nexport const DEFAULT_SHALLOW_BIAS = 0.5;\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 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, never deeper\n depth[i] =\n (1 - shallowBias) * idw + shallowBias * Math.min(idw, shallowest);\n }\n }\n }\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 (\n !Number.isFinite(s.lon) ||\n !Number.isFinite(s.lat) ||\n !Number.isFinite(s.depth) ||\n s.depth < 0\n ) {\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 const w = 1 / (Math.pow(dist2, power / 2) + 1e-6);\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;\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.\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 float line = 1.0 - smoothstep(uLineWidth, uLineWidth + 1.5, px);\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", "/** 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, Navionics-like: a saturated azure at the surface, fading\n * through pale blue to white at the shade limit. Stops are `[t, r, g, b]` with\n * `t` the fraction from the surface (0) to the shade limit (1). */\nconst SHADE_RAMP: ReadonlyArray<readonly [number, number, number, number]> = [\n [0, 26, 132, 216],\n [0.3, 96, 178, 232],\n [0.55, 156, 209, 240],\n [0.78, 205, 232, 248],\n [1, 255, 255, 255],\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 /** 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 isobaths. */\n contourIntervalMetres: 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 historyWindowHours: 1,\n historyResolutionS: 0.1,\n shadeLimitMetres: 10,\n contourIntervalMetres: 1,\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.5, max: 50, step: 0.5 },\n cellPx: { min: 4, max: 128, step: 1 },\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 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/** 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\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 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;\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 ctx = glCanvas.getContext(\"webgl2\", {\n alpha: true,\n premultipliedAlpha: true,\n }) as WebGL2RenderingContext | null;\n if (!ctx) return false;\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\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}\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}\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}\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 cell = Math.max(gap, extMax / MAX_FIELD_CELLS, minCover / 8, 0.01);\n const next = createDepthFieldBuilder(extent, {\n cellMetres: cell,\n coverageMetres: Math.max(cell * 1.5, gap * 1.5),\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}\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\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\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 // 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 dot(p, c.depth);\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 }\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 const out = new OffscreenCanvas(width, height);\n const ctx = out.getContext(\"2d\");\n if (!ctx) {\n post({ type: \"error\", message: \"no 2D context\" });\n return;\n }\n ctx.clearRect(0, 0, width, height);\n if (builder) {\n renderGpu(box, surfaceW, surfaceH, pixelRatio * scale);\n ctx.drawImage(\n glCanvas as unknown as CanvasImageSource,\n 0,\n 0,\n surfaceW,\n surfaceH,\n 0,\n 0,\n width,\n height,\n );\n }\n drawOverlay(ctx, box, width, height, resolution, pixelRatio, zoom);\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 ((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 settings = msg.settings;\n fieldDirty = true;\n break;\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": ";AA8CO,IAAM,yBAAyB;AAE/B,IAAM,yBAAyB;AAC/B,IAAM,oBAAoB;AAC1B,IAAM,uBAAuB;AAC7B,IAAM,oBAAoB;AAEjC,IAAM,eAAe;AACrB,IAAM,mBAAmB;AACzB,IAAM,MAAM,KAAK,KAAK;AAEtB,IAAM,QAAQ,CAAC,QAAgB,MAAM,MAAM;AAC3C,IAAM,QAAQ,CAAC,QACb,eACA,KAAK;AAAA,EACH,KAAK;AAAA,IACH,KAAK,KAAK,IACP,KAAK,IAAI,CAAC,kBAAkB,KAAK,IAAI,kBAAkB,GAAG,CAAC,IAAI,MAC9D;AAAA,EACN;AACF;AAMK,SAAS,qBACdA,UACQ;AACR,MAAIA,SAAQ,SAAS,GAAG;AACtB,WAAO;AAAA,EACT;AACA,QAAM,OAAiB,CAAC;AACxB,WAAS,IAAI,GAAG,IAAIA,SAAQ,QAAQ,KAAK;AACvC,UAAM,KAAK,MAAMA,SAAQ,IAAI,CAAC,EAAE,GAAG;AACnC,UAAM,KAAK,MAAMA,SAAQ,IAAI,CAAC,EAAE,GAAG;AACnC,SAAK;AAAA,MACH,KAAK,MAAM,MAAMA,SAAQ,CAAC,EAAE,GAAG,IAAI,IAAI,MAAMA,SAAQ,CAAC,EAAE,GAAG,IAAI,EAAE;AAAA,IACnE;AAAA,EACF;AACA,OAAK,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACzB,SAAO,KAAK,KAAK,MAAM,KAAK,SAAS,CAAC,CAAC;AACzC;AAkBO,SAAS,wBACd,QACA,SAC0B;AAC1B,QAAM,aAAa,QAAQ;AAC3B,MACE,EAAE,aAAa,MACf,OAAO,WAAW,KAClB,CAAC,OAAO,MAAM,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC,KACvC,OAAO,CAAC,KAAK,OAAO,CAAC,KACrB,OAAO,CAAC,KAAK,OAAO,CAAC,GACrB;AACA,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,cAAc,KAAK;AAAA,IACvB;AAAA,IACA,KAAK,IAAI,GAAG,QAAQ,eAAe,oBAAoB;AAAA,EACzD;AACA,QAAM,WAAW,QAAQ,YAAY;AAErC,QAAM,QAAQ,KAAK;AAAA,IACjB;AAAA,IACA,KAAK,MAAM,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,UAAU;AAAA,EAChD;AACA,QAAM,SAAS,KAAK;AAAA,IAClB;AAAA,IACA,KAAK,MAAM,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,UAAU;AAAA,EAChD;AACA,MAAI,SAAS,KAAK,UAAU,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,OAAO,CAAC;AACrB,QAAM,OAAO,OAAO,CAAC;AACrB,QAAM,aAA+C;AAAA,IACnD;AAAA,IACA;AAAA,IACA,OAAO,QAAQ;AAAA,IACf,OAAO,SAAS;AAAA,EAClB;AAEA,QAAM,QAAQ,IAAI,aAAa,QAAQ,MAAM,EAAE,KAAK,GAAG;AACvD,QAAM,OAAO,IAAI,aAAa,QAAQ,MAAM;AAC5C,QAAM,QAAQ,IAAI,aAAa,QAAQ,MAAM;AAC7C,QAAM,UAAU,IAAI,aAAa,QAAQ,MAAM,EAAE,KAAK,QAAQ;AAE9D,QAAM,iBAAiB,KAAK;AAAA,IAC1B;AAAA,IACA,QAAQ,qBAAqB;AAAA,IAC7B,QAAQ,mBACL,QAAQ,iBAAiB,0BAA0B;AAAA,EACxD;AACA,QAAM,YAAY,iBAAiB;AAEnC,QAAM,cAAc,KAAK,IAAI,IAAI,KAAK,KAAK,iBAAiB,UAAU,CAAC;AAEvE,QAAM,QAAoB,EAAE,OAAO,QAAQ,QAAQ,YAAY,MAAM;AAIrE,WAAS,QAAQ,CAAC,KAAK,KAAK,KAAK,GAAG,GAAmB;AACrD,aAAS,KAAK,KAAK,KAAK,KAAK,MAAM;AACjC,YAAM,MAAM,KAAK;AACjB,eAAS,KAAK,KAAK,KAAK,KAAK,MAAM;AACjC,cAAM,IAAI,MAAM;AAChB,cAAM,IAAI,KAAK,CAAC;AAChB,YAAI,KAAK,GAAG;AACV,gBAAM,CAAC,IAAI;AACX;AAAA,QACF;AACA,cAAM,MAAM,MAAM,CAAC,IAAI;AACvB,cAAM,aAAa,QAAQ,CAAC;AAE5B,cAAM,CAAC,KACJ,IAAI,eAAe,MAAM,cAAc,KAAK,IAAI,KAAK,UAAU;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AAEA,WAAS,IAAIA,UAAkD;AAC7D,QAAI,OAAwB;AAC5B,UAAM,OAAO,CAAC,IAAY,OAAe;AACvC,UAAI,CAAC,MAAM;AACT,eAAO,CAAC,IAAI,IAAI,KAAK,GAAG,KAAK,CAAC;AAC9B;AAAA,MACF;AACA,UAAI,KAAK,KAAK,CAAC,EAAG,MAAK,CAAC,IAAI;AAC5B,UAAI,KAAK,KAAK,CAAC,EAAG,MAAK,CAAC,IAAI;AAC5B,UAAI,KAAK,IAAI,KAAK,CAAC,EAAG,MAAK,CAAC,IAAI,KAAK;AACrC,UAAI,KAAK,IAAI,KAAK,CAAC,EAAG,MAAK,CAAC,IAAI,KAAK;AAAA,IACvC;AACA,eAAW,KAAKA,UAAS;AACvB,UACE,CAAC,OAAO,SAAS,EAAE,GAAG,KACtB,CAAC,OAAO,SAAS,EAAE,GAAG,KACtB,CAAC,OAAO,SAAS,EAAE,KAAK,KACxB,EAAE,QAAQ,GACV;AACA;AAAA,MACF;AACA,YAAM,KAAK,MAAM,EAAE,GAAG;AACtB,YAAM,KAAK,MAAM,EAAE,GAAG;AACtB,YAAM,KAAK,KAAK,OAAO,KAAK,QAAQ,UAAU;AAC9C,YAAM,KAAK,KAAK,OAAO,KAAK,QAAQ,UAAU;AAC9C,eAAS,KAAK,CAAC,aAAa,MAAM,aAAa,MAAM;AACnD,cAAM,KAAK,KAAK;AAChB,YAAI,KAAK,KAAK,MAAM,QAAQ;AAC1B;AAAA,QACF;AACA,cAAM,cAAc,QAAQ,KAAK,OAAO;AACxC,cAAM,MAAM,cAAc;AAC1B,iBAAS,KAAK,CAAC,aAAa,MAAM,aAAa,MAAM;AACnD,gBAAM,KAAK,KAAK;AAChB,cAAI,KAAK,KAAK,MAAM,OAAO;AACzB;AAAA,UACF;AACA,gBAAM,cAAc,QAAQ,KAAK,OAAO;AACxC,gBAAM,MAAM,cAAc;AAC1B,gBAAM,QAAQ,MAAM,MAAM,MAAM;AAChC,cAAI,QAAQ,WAAW;AACrB;AAAA,UACF;AACA,gBAAM,MAAM,KAAK,QAAQ;AACzB,gBAAM,IAAI,KAAK,KAAK,IAAI,OAAO,QAAQ,CAAC,IAAI;AAC5C,eAAK,GAAG,KAAK;AACb,gBAAM,GAAG,KAAK,IAAI,EAAE;AACpB,cAAI,EAAE,QAAQ,QAAQ,GAAG,GAAG;AAC1B,oBAAQ,GAAG,IAAI,EAAE;AAAA,UACnB;AACA,eAAK,IAAI,EAAE;AAAA,QACb;AAAA,MACF;AAAA,IACF;AACA,QAAI,KAAM,SAAQ,IAAI;AACtB,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,OAAO,IAAI;AACtB;;;AClOO,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAKvB,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACsCvB,IAAM,wBAAwB;AAMrC,IAAMC,gBAAe;AACrB,IAAMC,oBAAmB;AACzB,IAAMC,OAAM,KAAK,KAAK;AAGf,SAASC,OAAM,KAAqB;AACzC,SAAO,MAAMD,OAAMF;AACrB;AAGO,SAASI,OAAM,KAAqB;AACzC,QAAM,IAAI,KAAK,IAAI,CAACH,mBAAkB,KAAK,IAAIA,mBAAkB,GAAG,CAAC;AACrE,SAAOD,gBAAe,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,IAAK,IAAIE,OAAO,CAAC,CAAC;AACtE;AAGO,SAAS,MAAM,GAAmB;AACvC,SAAO,KAAKA,OAAMF;AACpB;AAGO,SAAS,MAAM,GAAmB;AACvC,UAAQ,IAAI,KAAK,KAAK,KAAK,IAAI,IAAIA,aAAY,CAAC,IAAI,KAAK,KAAK,KAAKE;AACrE;AAKA,IAAM,aAAuE;AAAA,EAC3E,CAAC,GAAG,IAAI,KAAK,GAAG;AAAA,EAChB,CAAC,KAAK,IAAI,KAAK,GAAG;AAAA,EAClB,CAAC,MAAM,KAAK,KAAK,GAAG;AAAA,EACpB,CAAC,MAAM,KAAK,KAAK,GAAG;AAAA,EACpB,CAAC,GAAG,KAAK,KAAK,GAAG;AACnB;AAGO,IAAM,uBAAuB;AAI7B,SAAS,WAAW,GAAqC;AAC9D,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,SAAS,CAAC,IAAI,IAAI,CAAC,CAAC;AAC7D,MAAI,KAAK,WAAW,CAAC;AACrB,MAAI,KAAK,WAAW,WAAW,SAAS,CAAC;AACzC,WAAS,IAAI,GAAG,IAAI,WAAW,SAAS,GAAG,KAAK;AAC9C,QAAI,KAAK,WAAW,CAAC,EAAE,CAAC,KAAK,KAAK,WAAW,IAAI,CAAC,EAAE,CAAC,GAAG;AACtD,WAAK,WAAW,CAAC;AACjB,WAAK,WAAW,IAAI,CAAC;AACrB;AAAA,IACF;AAAA,EACF;AACA,QAAM,OAAO,GAAG,CAAC,IAAI,GAAG,CAAC;AACzB,QAAM,IAAI,OAAO,KAAK,IAAI,GAAG,CAAC,KAAK,OAAO;AAC1C,QAAM,OAAO,CAAC,GAAW,MAAc,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC;AACjE,SAAO,CAAC,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AACpE;AAGO,SAAS,WACd,OACA,YAC0B;AAC1B,QAAM,QAAQ,aAAa,IAAI,aAAa;AAC5C,SAAO,WAAW,QAAQ,KAAK;AACjC;AAGO,SAAS,SAAS,OAAe,YAAoB,QAAQ,GAAW;AAC7E,QAAM,CAAC,GAAG,GAAG,CAAC,IAAI,WAAW,OAAO,UAAU;AAC9C,SAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK;AACrC;AAOO,SAAS,kBACdG,UACA,gBACA,iBAAiB,UACJ;AACb,MAAI,EAAE,iBAAiB,IAAI;AACzB,WAAO,CAAC;AAAA,EACV;AACA,QAAM,QAAQ,oBAAI,IAUhB;AACF,aAAW,KAAKA,UAAS;AACvB,QACE,CAAC,OAAO,SAAS,EAAE,GAAG,KACtB,CAAC,OAAO,SAAS,EAAE,GAAG,KACtB,CAAC,OAAO,SAAS,EAAE,KAAK,KACxB,EAAE,QAAQ,KACV,EAAE,QAAQ,gBACV;AACA;AAAA,IACF;AACA,UAAM,IAAIF,OAAM,EAAE,GAAG;AACrB,UAAM,IAAIC,OAAM,EAAE,GAAG;AACrB,UAAM,KAAK,KAAK,MAAM,IAAI,cAAc;AACxC,UAAM,KAAK,KAAK,MAAM,IAAI,cAAc;AACxC,UAAM,MAAM,GAAG,EAAE,IAAI,EAAE;AACvB,UAAM,IAAI,MAAM,IAAI,GAAG;AACvB,QAAI,GAAG;AACL,QAAE,MAAM;AACR,QAAE,MAAM;AACR,QAAE;AACF,UAAI,EAAE,QAAQ,EAAE,OAAO;AACrB,UAAE,QAAQ,EAAE;AAAA,MACd;AAAA,IACF,OAAO;AACL,YAAM,IAAI,KAAK,EAAE,IAAI,IAAI,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,OAAO,EAAE,MAAM,CAAC;AAAA,IAC/D;AAAA,EACF;AACA,QAAM,MAAmB,CAAC;AAC1B,QAAM,QAAQ,CAAC,MAAM;AACnB,QAAI,KAAK;AAAA,MACP,IAAI,EAAE;AAAA,MACN,IAAI,EAAE;AAAA,MACN,KAAK,MAAM,EAAE,KAAK,EAAE,CAAC;AAAA,MACrB,KAAK,MAAM,EAAE,KAAK,EAAE,CAAC;AAAA,MACrB,OAAO,EAAE;AAAA,MACT,OAAO,EAAE;AAAA,IACX,CAAC;AAAA,EACH,CAAC;AACD,SAAO;AACT;AAIO,SAAS,mBACd,OACK;AACL,SAAO,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACpD;AAIO,SAAS,aACdC,UACA,mBACe;AACf,SAAOA,SAAQ;AAAA,IACb,CAAC,MACC,OAAO,SAAS,EAAE,KAAK,KAAK,EAAE,SAAS,KAAK,EAAE,SAAS;AAAA,EAC3D;AACF;AAIO,IAAM,0BAA0B;AAIhC,IAAM,yBAAyB;AAQ/B,SAAS,gBACd,OACA,gBACA,uBAA+B,yBAC/B,iBAAyB,wBACT;AAChB,MAAI,MAAM,WAAW,KAAK,EAAE,iBAAiB,IAAI;AAC/C,WAAO,CAAC;AAAA,EACV;AACA,QAAM,YAAY,KAAK,IAAI,GAAG,oBAAoB;AAClD,QAAM,YAAY,KAAK,IAAI,gBAAgB,iBAAiB,cAAc;AAC1E,QAAM,QAAQ,oBAAI,IAAuB;AACzC,QAAM,QAAQ,CAAC,MAAM,MAAM,IAAI,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;AACpD,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,UAAU,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAC3D,QAAM,aAAa;AAAA,IACjB,CAAC,GAAG,CAAC;AAAA,IACL,CAAC,IAAI,CAAC;AAAA,IACN,CAAC,GAAG,CAAC;AAAA,IACL,CAAC,GAAG,EAAE;AAAA,IACN,CAAC,GAAG,CAAC;AAAA,IACL,CAAC,GAAG,EAAE;AAAA,IACN,CAAC,IAAI,CAAC;AAAA,IACN,CAAC,IAAI,EAAE;AAAA,EACT;AACA,QAAM,WAA2B,CAAC;AAElC,aAAW,SAAS,SAAS;AAC3B,QAAI,QAAQ,IAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,EAAE,GAAG;AAC1C;AAAA,IACF;AACA,UAAM,SAAS,MAAM;AACrB,UAAM,KAAKF,OAAM,MAAM,GAAG;AAC1B,UAAM,KAAKC,OAAM,MAAM,GAAG;AAC1B,UAAM,UAAuB,CAAC;AAC9B,UAAM,QAAqB,CAAC,KAAK;AACjC,YAAQ,IAAI,GAAG,MAAM,EAAE,IAAI,MAAM,EAAE,EAAE;AAErC,WAAO,MAAM,QAAQ;AACnB,YAAM,OAAO,MAAM,IAAI;AACvB,UAAI,CAAC,MAAM;AACT;AAAA,MACF;AACA,cAAQ,KAAK,IAAI;AACjB,iBAAW,CAAC,IAAI,EAAE,KAAK,YAAY;AACjC,cAAM,MAAM,GAAG,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,EAAE;AAC3C,cAAM,KAAK,MAAM,IAAI,GAAG;AACxB,YAAI,CAAC,MAAM,QAAQ,IAAI,GAAG,GAAG;AAC3B;AAAA,QACF;AAEA,YAAI,GAAG,QAAQ,SAAS,KAAK,IAAI,YAAY,QAAQ,GAAG,GAAG;AACzD;AAAA,QACF;AACA,YAAI,KAAK,MAAMD,OAAM,GAAG,GAAG,IAAI,IAAIC,OAAM,GAAG,GAAG,IAAI,EAAE,IAAI,WAAW;AAClE;AAAA,QACF;AACA,gBAAQ,IAAI,GAAG;AACf,cAAM,KAAK,EAAE;AAAA,MACf;AAAA,IACF;AAEA,QAAI,KAAK;AACT,QAAI,KAAK;AACT,QAAI,QAAQ;AACZ,QAAI,WAAW;AACf,YAAQ,QAAQ,CAAC,MAAM;AACrB,YAAMD,OAAM,EAAE,GAAG,IAAI,EAAE;AACvB,YAAMC,OAAM,EAAE,GAAG,IAAI,EAAE;AACvB,eAAS,EAAE;AACX,UAAI,EAAE,QAAQ,UAAU;AACtB,mBAAW,EAAE;AAAA,MACf;AAAA,IACF,CAAC;AACD,UAAM;AACN,UAAM;AACN,QAAI,SAAS;AACb,YAAQ,QAAQ,CAAC,MAAM;AACrB,eAAS,KAAK;AAAA,QACZ;AAAA,QACA,KAAK,MAAMD,OAAM,EAAE,GAAG,IAAI,IAAIC,OAAM,EAAE,GAAG,IAAI,EAAE;AAAA,MACjD;AAAA,IACF,CAAC;AACD,aAAS,KAAK;AAAA,MACZ,KAAK,MAAM,EAAE;AAAA,MACb,KAAK,MAAM,EAAE;AAAA,MACb,OAAO;AAAA,MACP,OAAO;AAAA,MACP,cAAc,KAAK,IAAI,WAAW,SAAS,iBAAiB,CAAC;AAAA,IAC/D,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAIO,SAAS,cACdC,UACA,gBACA,YAAoB,yBACpB,iBAAiB,UACjB,iBAAyB,wBACT;AAChB,SAAO;AAAA,IACL;AAAA,MACE,kBAAkBA,UAAS,gBAAgB,cAAc;AAAA,MACzD;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;;;AC1SO,IAAM,gBAA+D;AAAA,EAC1E,MAAM;AAAA,EACN,UAAU;AAAA,EACV,KAAK;AACP;;;ACPA,IAAM,kBAAkB;AAExB,IAAM,iBAAiB;AACvB,IAAM,iBAAiB;AAEvB,IAAM,sBAAsB;AAE5B,IAAM,eAAe,KAAK,KAAK;AAC/B,IAAM,cAAc,IAAI;AAExB,IAAI,UAAyB,CAAC;AAE9B,IAAI,cAA6B,CAAC;AAElC,IAAI,cAA6B,CAAC;AAClC,IAAI,WAAqC;AACzC,IAAI,UAAoC;AACxC,IAAI,aAAa;AAEjB,IAAI,SAKA,EAAE,UAAU,KAAK;AAErB,IAAI,YAAY;AAIhB,IAAI,iBAAiB;AAIrB,IAAI,aAA2D;AAC/D,IAAI,eAA8D;AAIlE,IAAM,uBAAuB;AAG7B,IAAM,oBAAoB;AAE1B,IAAM,oBAAoB;AAE1B,IAAI,YAAY;AAEhB,IAAI,aAAa;AAEjB,IAAI,oBAAoB;AAExB,IAAI,aAAa;AAMjB,SAAS,cAAsB;AAC7B,QAAM,UAAU,UAAU,WAAW;AACrC,SAAO,YAAY,SAAS,YAAY,cAAc,OAAO;AAC/D;AAGA,SAAS,YAAY,QAAwB;AAC3C,SAAO,cAAc,UAChB,SAAS,QAAQ,QAAQ,CAAC,IAC3B,OAAO,QAAQ,CAAC;AACtB;AAIA,IAAI,WAAmC;AACvC,IAAI,KAAoC;AACxC,IAAI,UAA+B;AACnC,IAAI,OAA2B;AAC/B,IAAI,eAAoC;AACxC,IAAI,aAAkC;AACtC,IAAI,OAAO;AACX,IAAI,SAAsC;AAC1C,IAAI,OAAoC;AACxC,IAAI,SAAsC;AAC1C,IAAI,QAAqC;AACzC,IAAI,SAAsC;AAC1C,IAAI,cAA2C;AAC/C,IAAI,UAAuC;AAC3C,IAAI,YAAyC;AAC7C,IAAI,aAA0C;AAE9C,SAAS,QACP,KACA,MACA,KACoB;AACpB,QAAM,SAAS,IAAI,aAAa,IAAI;AACpC,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,aAAa,QAAQ,GAAG;AAC5B,MAAI,cAAc,MAAM;AACxB,MAAI,CAAC,IAAI,mBAAmB,QAAQ,IAAI,cAAc,GAAG;AACvD,QAAI,aAAa,MAAM;AACvB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,YAAqB;AAC5B,MAAI,GAAI,QAAO;AACf,aAAW,IAAI,gBAAgB,GAAG,CAAC;AACnC,QAAM,MAAM,SAAS,WAAW,UAAU;AAAA,IACxC,OAAO;AAAA,IACP,oBAAoB;AAAA,EACtB,CAAC;AACD,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,KAAK,QAAQ,KAAK,IAAI,eAAe,cAAc;AACzD,QAAM,KAAK,QAAQ,KAAK,IAAI,iBAAiB,cAAc;AAC3D,QAAM,OAAO,IAAI,cAAc;AAC/B,MAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAM,QAAO;AAChC,MAAI,aAAa,MAAM,EAAE;AACzB,MAAI,aAAa,MAAM,EAAE;AACzB,MAAI,YAAY,IAAI;AACpB,MAAI,CAAC,IAAI,oBAAoB,MAAM,IAAI,WAAW,EAAG,QAAO;AAE5D,OAAK;AACL,YAAU;AACV,SAAO,IAAI,kBAAkB,MAAM,MAAM;AACzC,WAAS,IAAI,mBAAmB,MAAM,QAAQ;AAC9C,SAAO,IAAI,mBAAmB,MAAM,MAAM;AAC1C,WAAS,IAAI,mBAAmB,MAAM,QAAQ;AAC9C,UAAQ,IAAI,mBAAmB,MAAM,OAAO;AAC5C,WAAS,IAAI,mBAAmB,MAAM,QAAQ;AAC9C,gBAAc,IAAI,mBAAmB,MAAM,aAAa;AACxD,YAAU,IAAI,mBAAmB,MAAM,SAAS;AAChD,cAAY,IAAI,mBAAmB,MAAM,WAAW;AACpD,eAAa,IAAI,mBAAmB,MAAM,YAAY;AAEtD,SAAO,IAAI,aAAa;AACxB,MAAI,WAAW,IAAI,cAAc,IAAI;AACrC,MAAI;AAAA,IACF,IAAI;AAAA,IACJ,IAAI,aAAa,CAAC,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,IAAI,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC;AAAA,IAC3D,IAAI;AAAA,EACN;AAEA,iBAAe,IAAI,cAAc;AACjC,MAAI,YAAY,IAAI,YAAY,YAAY;AAC5C,MAAI,cAAc,IAAI,YAAY,IAAI,oBAAoB,IAAI,OAAO;AACrE,MAAI,cAAc,IAAI,YAAY,IAAI,oBAAoB,IAAI,OAAO;AACrE,MAAI,cAAc,IAAI,YAAY,IAAI,gBAAgB,IAAI,aAAa;AACvE,MAAI,cAAc,IAAI,YAAY,IAAI,gBAAgB,IAAI,aAAa;AAEvE,eAAa,IAAI,cAAc;AAC/B,QAAM,MAAM,IAAI,WAAW,MAAM,CAAC;AAClC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAM,CAAC,GAAG,GAAG,CAAC,IAAI,WAAW,IAAI,GAAG;AACpC,QAAI,IAAI,CAAC,IAAI;AACb,QAAI,IAAI,IAAI,CAAC,IAAI;AACjB,QAAI,IAAI,IAAI,CAAC,IAAI;AACjB,QAAI,IAAI,IAAI,CAAC,IAAI;AAAA,EACnB;AACA,MAAI,YAAY,IAAI,YAAY,UAAU;AAC1C,MAAI;AAAA,IACF,IAAI;AAAA,IACJ;AAAA,IACA,IAAI;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ;AAAA,EACF;AACA,MAAI,cAAc,IAAI,YAAY,IAAI,oBAAoB,IAAI,MAAM;AACpE,MAAI,cAAc,IAAI,YAAY,IAAI,oBAAoB,IAAI,MAAM;AACpE,MAAI,cAAc,IAAI,YAAY,IAAI,gBAAgB,IAAI,aAAa;AACvE,MAAI,cAAc,IAAI,YAAY,IAAI,gBAAgB,IAAI,aAAa;AAEvE,SAAO;AACT;AAGA,SAAS,aAAmB;AAC1B,MAAI,IAAI;AACN,OAAG,aAAa,oBAAoB,GAAG,YAAY;AAAA,EACrD;AACA,OAAK;AACL,YAAU;AACV,SAAO;AACP,iBAAe;AACf,eAAa;AACb,aAAW;AACb;AAEA,SAAS,YAAY,GAAqB;AACxC,MAAI,CAAC,MAAM,CAAC,aAAc;AAI1B,QAAM,MAAM,IAAI,aAAa,EAAE,MAAM,MAAM;AAC3C,WAAS,IAAI,GAAG,IAAI,EAAE,MAAM,QAAQ,KAAK;AACvC,UAAM,IAAI,EAAE,MAAM,CAAC;AACnB,QAAI,CAAC,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK;AAAA,EAClC;AACA,KAAG,cAAc,GAAG,QAAQ;AAC5B,KAAG,YAAY,GAAG,YAAY,YAAY;AAC1C,KAAG;AAAA,IACD,GAAG;AAAA,IACH;AAAA,IACA,GAAG;AAAA,IACH,EAAE;AAAA,IACF,EAAE;AAAA,IACF;AAAA,IACA,GAAG;AAAA,IACH,GAAG;AAAA,IACH;AAAA,EACF;AACF;AAIA,SAAS,aAAa,GAAe,CAAC,IAAI,IAAI,IAAI,EAAE,GAAmB;AACrE,MAAI,CAAC,MAAM,CAAC,aAAc;AAC1B,QAAM,IAAI,KAAK;AACf,QAAM,IAAI,KAAK;AACf,MAAI,KAAK,KAAK,KAAK,EAAG;AACtB,QAAM,MAAM,IAAI,aAAa,IAAI,CAAC;AAClC,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,OAAO,KAAK,KAAK,EAAE,QAAQ;AACjC,UAAM,MAAM,IAAI;AAChB,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAM,IAAI,EAAE,MAAM,MAAM,CAAC;AACzB,UAAI,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK;AAAA,IACxC;AAAA,EACF;AACA,KAAG,cAAc,GAAG,QAAQ;AAC5B,KAAG,YAAY,GAAG,YAAY,YAAY;AAC1C,KAAG,cAAc,GAAG,YAAY,GAAG,IAAI,IAAI,GAAG,GAAG,GAAG,KAAK,GAAG,OAAO,GAAG;AACxE;AAKA,SAAS,eAAqB;AAC5B,YAAU;AACV,YAAU,CAAC,GAAG,aAAa,GAAG,WAAW;AAIzC,QAAM,SAAS,YAAY,SAAS,cAAc;AAClD,MAAI,OAAO,WAAW,KAAK,CAAC,SAAU;AACtC,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI,OAAO;AACX,aAAW,KAAK,QAAQ;AACtB,UAAM,IAAIC,OAAM,EAAE,GAAG;AACrB,UAAM,IAAIC,OAAM,EAAE,GAAG;AACrB,WAAO,KAAK,IAAI,MAAM,CAAC;AACvB,WAAO,KAAK,IAAI,MAAM,CAAC;AACvB,WAAO,KAAK,IAAI,MAAM,CAAC;AACvB,WAAO,KAAK,IAAI,MAAM,CAAC;AAAA,EACzB;AACA,QAAM,QAAQ,OAAO,QAAQ,MAAM;AACnC,QAAM,QAAQ,OAAO,QAAQ,MAAM;AACnC,QAAM,SAA2C;AAAA,IAC/C,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,EACT;AACA,QAAM,SAAS,KAAK,IAAI,OAAO,CAAC,IAAI,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,OAAO,CAAC,CAAC;AAIpE,QAAM,QAAQ,SAAS,WAAW;AAClC,QAAM,MAAM,QAAQ,IAAI,qBAAqB,MAAM;AACnD,QAAM,WAAW,QAAQ,IAAI;AAC7B,QAAM,OAAO,KAAK,IAAI,KAAK,SAAS,iBAAiB,WAAW,GAAG,IAAI;AACvE,QAAM,OAAO,wBAAwB,QAAQ;AAAA,IAC3C,YAAY;AAAA,IACZ,gBAAgB,KAAK,IAAI,OAAO,KAAK,MAAM,GAAG;AAAA,IAC9C,mBAAmB;AAAA,EACrB,CAAC;AACD,MAAI,CAAC,KAAM;AACX,OAAK,IAAI,WAAW;AACpB,MAAI,YAAY,OAAQ,MAAK,IAAI,WAAW;AAC5C,YAAU;AACV,cAAY,KAAK,KAAK;AACxB;AAGA,SAAS,QAAQ,YAAiC;AAChD,cAAY,KAAK,GAAG,UAAU;AAC9B,UAAQ,KAAK,GAAG,UAAU;AAC1B;AACA,MAAI,CAAC,SAAS;AAEZ,iBAAa;AACb;AAAA,EACF;AACA,QAAM,OAAO,QAAQ,IAAI,UAAU;AACnC,MAAI,KAAM,cAAa,QAAQ,OAAO,IAAI;AAC5C;AAEA,SAAS,UACP,KACA,SACA,SACA,OACM;AACN,QAAM,IAAI,SAAS;AACnB,MAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAU;AAC5B,MAAI,SAAS,UAAU,WAAW,SAAS,WAAW,SAAS;AAC7D,aAAS,QAAQ;AACjB,aAAS,SAAS;AAAA,EACpB;AACA,KAAG,SAAS,GAAG,GAAG,SAAS,OAAO;AAClC,KAAG,WAAW,GAAG,GAAG,GAAG,CAAC;AACxB,KAAG,MAAM,GAAG,gBAAgB;AAC5B,KAAG,WAAW,OAAO;AAErB,KAAG,cAAc,GAAG,QAAQ;AAC5B,KAAG,YAAY,GAAG,YAAY,YAAY;AAC1C,KAAG,cAAc,GAAG,QAAQ;AAC5B,KAAG,YAAY,GAAG,YAAY,UAAU;AAExC,QAAM,MAAM,EAAE;AACd,QAAM,gBAAgB,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK;AACzC,QAAM,eAAe,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK;AACxC,QAAM,QACJ,KAAK,OAAO,eAAe,eAAe,WAAW,IAAI;AAG3D,KAAG;AAAA,IACD;AAAA,IACA,IAAI,CAAC,IAAI,QAAQ,IAAI,CAAC;AAAA,IACtB,IAAI,CAAC,IAAI,IAAI,CAAC;AAAA,IACd,IAAI,CAAC,IAAI,IAAI,CAAC;AAAA,IACd,IAAI,CAAC,IAAI,IAAI,CAAC;AAAA,EAChB;AACA,KAAG,UAAU,QAAQ,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;AAC3D,KAAG,UAAU,QAAQ,IAAI,EAAE,OAAO,IAAI,EAAE,MAAM;AAC9C,KAAG,UAAU,QAAQ,CAAC;AACtB,KAAG,UAAU,MAAM,CAAC;AACpB,KAAG,UAAU,aAAa,KAAK,IAAI,MAAM,UAAU,oBAAoB,EAAE,CAAC;AAC1E,KAAG,UAAU,SAAS,oBAAoB;AAC1C,KAAG,UAAU,WAAW,KAAK,IAAI,MAAM,UAAU,yBAAyB,CAAC,CAAC;AAC5E,KAAG,UAAU,YAAY,KAAK,IAAI,GAAG,KAAK,CAAC;AAE3C,KAAG,WAAW,GAAG,cAAc,IAAI;AACnC,KAAG,wBAAwB,IAAI;AAC/B,KAAG,oBAAoB,MAAM,GAAG,GAAG,OAAO,OAAO,GAAG,CAAC;AACrD,KAAG,WAAW,GAAG,WAAW,GAAG,CAAC;AAClC;AAIA,SAAS,YACP,KACA,KACA,SACA,SACA,YACA,OACA,MACM;AACN,MAAI,CAAC,SAAU;AACf,QAAM,OAAO,IAAI,CAAC,IAAI,IAAI,CAAC;AAC3B,QAAM,OAAO,IAAI,CAAC,IAAI,IAAI,CAAC;AAC3B,QAAM,WAAW,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK;AACpC,QAAM,UAAU,CAAC,KAAa,QAAkC;AAC9D,UAAM,KAAKD,OAAM,GAAG;AACpB,UAAM,KAAKC,OAAM,GAAG;AACpB,UAAM,IAAI,KAAK,KAAK,OAAO,UAAU,MAAM,WAAW,IAAI;AAC1D,WAAO,EAAG,IAAI,IAAI,CAAC,KAAK,OAAQ,UAAW,IAAI,CAAC,IAAI,MAAM,OAAQ,OAAO;AAAA,EAC3E;AACA,MAAI,YAAY;AAChB,MAAI,eAAe;AAEnB,QAAM,MAAM,CAAC,GAAqB,UAAkB;AAClD,QAAI,UAAU;AACd,QAAI;AAAA,MACF,EAAE,CAAC;AAAA,MACH,EAAE,CAAC;AAAA,MACH,KAAK,IAAI,KAAK,SAAU,SAAS,IAAI,IAAI;AAAA,MACzC;AAAA,MACA,KAAK,KAAK;AAAA,IACZ;AACA,QAAI,YAAY,SAAS,OAAO,SAAU,kBAAkB,IAAI;AAChE,QAAI,KAAK;AAAA,EACX;AACA,QAAM,QAAQ,CAAC,MAAc,GAAqB,YAAoB;AACpE,QAAI,OAAO,GAAG,KAAK,MAAM,UAAU,KAAK,CAAC;AACzC,QAAI,YAAY,KAAK,IAAI,GAAG,IAAI,KAAK;AACrC,QAAI,cAAc;AAClB,QAAI,WAAW,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AAC/B,QAAI,YAAY;AAChB,QAAI,SAAS,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;AAAA,EAC/B;AAMA,QAAM,WAAW,GAAG,cAAc,IAAI,SAAS,iBAAiB;AAChE,MAAI,CAAC,cAAc,WAAW,QAAQ,UAAU;AAC9C,iBAAa;AAAA,MACX,KAAK;AAAA,MACL,OAAO,aAAa,SAAS,SAAS,iBAAiB;AAAA,IACzD;AAAA,EACF;AACA,aAAW,MAAM,QAAQ,CAAC,MAAM,IAAI,QAAQ,EAAE,KAAK,EAAE,GAAG,GAAG,EAAE,KAAK,CAAC;AAEnE,QAAM,WAAW,KAAK,IAAI,GAAG,SAAS,SAAS,UAAU;AACzD,QAAM,aAAa,GAAG,cAAc,IAAI,QAAQ,IAAI,SAAS,cAAc,IAAI,SAAS,cAAc;AACtG,MAAI,CAAC,gBAAgB,aAAa,QAAQ,YAAY;AACpD,mBAAe;AAAA,MACb,KAAK;AAAA,MACL,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,SAAS;AAAA,QACT,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACA,QAAM,WAAW,aAAa;AAC9B,aAAW,KAAK,UAAU;AACxB,UAAM,IAAI,QAAQ,EAAE,KAAK,EAAE,GAAG;AAC9B,QAAI,GAAG,EAAE,KAAK;AACd,UAAM,WAAY,EAAE,eAAe,aAAc;AACjD,QAAI,YAAY,qBAAqB;AACnC,YAAM,UAAU,KAAK;AAAA,QACnB;AAAA,QACA,KAAK,IAAI,iBAAiB,OAAO,KAAK,MAAM,cAAc;AAAA,MAC5D;AACA,YAAM,YAAY,EAAE,KAAK,GAAG,GAAG,OAAO;AAAA,IACxC;AAAA,EACF;AAKA,QAAM,IAAI;AACV,MAAI,EAAE,YAAY,EAAE,YAAY,UAAa,EAAE,OAAO,EAAE,MAAM;AAC5D,UAAM,KAAKD,OAAM,EAAE,SAAS,CAAC,CAAC;AAC9B,UAAM,KAAKC,OAAM,EAAE,SAAS,CAAC,CAAC;AAC9B,UAAM,MAAM,KAAK,IAAI,EAAE,OAAO;AAC9B,UAAM,MAAM,KAAK,IAAI,EAAE,OAAO;AAC9B,UAAM,aAAa,EAAE,MAAM;AAC3B,UAAM,WAAW,EAAE,OAAO;AAC1B,UAAM,UACJ;AAAA,MACE,CAAC,YAAY,QAAQ;AAAA,MACrB,CAAC,YAAY,CAAC,QAAQ;AAAA,MACtB,CAAC,CAAC,YAAY,CAAC,QAAQ;AAAA,MACvB,CAAC,CAAC,YAAY,QAAQ;AAAA,IACxB,EACA;AAAA,MAAI,CAAC,CAAC,KAAK,IAAI,MACf;AAAA,QACE,MAAM,KAAK,MAAM,MAAM,OAAO,GAAG;AAAA,QACjC,MAAM,KAAK,MAAM,MAAM,OAAO,GAAG;AAAA,MACnC;AAAA,IACF;AACA,QAAI,UAAU;AACd,YAAQ;AAAA,MAAQ,CAAC,CAAC,IAAI,EAAE,GAAG,MACzB,MAAM,IAAI,IAAI,OAAO,IAAI,EAAE,IAAI,IAAI,OAAO,IAAI,EAAE;AAAA,IAClD;AACA,QAAI,UAAU;AACd,QAAI,YAAY,KAAK,IAAI,KAAK,MAAM,KAAK;AACzC,QAAI,cAAc;AAClB,QAAI,OAAO;AACX,QAAI,YAAY;AAChB,QAAI,KAAK;AAAA,EACX;AACF;AAIA,SAAS,OAAO,KAA0B;AACxC,MAAI;AACF,QAAI,CAAC,UAAU,GAAG;AAChB,WAAK,EAAE,MAAM,SAAS,SAAS,qBAAqB,CAAC;AACrD;AAAA,IACF;AACA,QAAI,cAAc,CAAC,SAAS;AAC1B,mBAAa;AACb,mBAAa;AAAA,IACf;AACA,UAAM,EAAE,QAAQ,OAAO,QAAQ,YAAY,YAAY,KAAK,IAAI;AAChE,UAAM,MAAwC;AAAA,MAC5C,OAAO,CAAC;AAAA,MACR,OAAO,CAAC;AAAA,MACR,OAAO,CAAC;AAAA,MACR,OAAO,CAAC;AAAA,IACV;AAOA,UAAM,QAAQ,YAAY;AAC1B,UAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,KAAK,CAAC;AACtD,UAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,KAAK,CAAC;AACvD,UAAM,UAAU,YAAY,IAAI;AAChC,UAAM,MAAM,IAAI,gBAAgB,OAAO,MAAM;AAC7C,UAAM,MAAM,IAAI,WAAW,IAAI;AAC/B,QAAI,CAAC,KAAK;AACR,WAAK,EAAE,MAAM,SAAS,SAAS,gBAAgB,CAAC;AAChD;AAAA,IACF;AACA,QAAI,UAAU,GAAG,GAAG,OAAO,MAAM;AACjC,QAAI,SAAS;AACX,gBAAU,KAAK,UAAU,UAAU,aAAa,KAAK;AACrD,UAAI;AAAA,QACF;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,gBAAY,KAAK,KAAK,OAAO,QAAQ,YAAY,YAAY,IAAI;AACjE,UAAM,QAAQ,IAAI,sBAAsB;AACxC,UAAM,UAAU,YAAY,IAAI,IAAI;AAIpC,SAAK,UAAU,WAAW,YAAY,QAAQ;AAC5C,mBACE,eAAe,IAAI,UAAU,aAAa,MAAM,UAAU;AAC5D,UAAI,EAAE,qBAAqB,mBAAmB;AAC5C,4BAAoB;AACpB,YAAI,aAAa,uBAAuB,MAAM;AAC5C,sBAAY,KAAK,IAAI,KAAK,YAAY,GAAG;AAAA,QAC3C,WAAW,aAAa,uBAAuB,KAAK;AAClD,sBAAY,KAAK,IAAI,GAAG,YAAY,GAAG;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AACA,QAAI,EAAE,cAAc,mBAAmB;AACrC,mBAAa;AACb,WAAK,EAAE,MAAM,QAAQ,OAAO,QAAQ,CAAC;AAAA,IACvC;AACA,SAAK,EAAE,MAAM,SAAS,WAAW,IAAI,WAAW,MAAM,GAAG,CAAC,KAAK,CAAC;AAAA,EAClE,SAAS,KAAK;AACZ,SAAK,EAAE,MAAM,SAAS,SAAS,OAAO,GAAG,EAAE,CAAC;AAAA,EAC9C;AACF;AAEA,SAAS,KAAK,SAA0B,UAAiC;AACvE,EAAC,KAA2B,YAAY,SAAS,YAAY,CAAC,CAAC;AACjE;AAEA,KAAK,YAAY,CAAC,MAAoC;AACpD,QAAM,MAAM,EAAE;AACd,UAAQ,IAAI,MAAM;AAAA,IAChB,KAAK;AACH,oBAAc,IAAI;AAClB,mBAAa;AACb;AACA;AAAA,IACF,KAAK;AACH,cAAQ,IAAI,OAAO;AACnB;AAAA,IACF,KAAK;AACH,eAAS;AACT;AAAA,IACF,KAAK;AACH,iBAAW,IAAI;AACf,mBAAa;AACb;AAAA,IACF,KAAK;AACH,kBAAY,IAAI;AAChB;AAAA,IACF,KAAK;AACH,aAAO,IAAI,GAAG;AACd;AAAA,IACF,KAAK;AACH,iBAAW;AACX;AAAA,EACJ;AACF;",
|
|
6
|
+
"names": ["samples", "EARTH_RADIUS", "MERCATOR_MAX_LAT", "RAD", "mercX", "mercY", "samples", "mercX", "mercY"]
|
|
7
|
+
}
|