@webviz/subsurface-viewer 1.17.8 → 1.18.1

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.
@@ -0,0 +1,648 @@
1
+ import { CompositeLayer } from "@deck.gl/core";
2
+ import { DataFilterExtension, PathStyleExtension } from "@deck.gl/extensions";
3
+ import { PathLayer } from "@deck.gl/layers";
4
+ import { SectionViewport } from "../../viewports";
5
+ import { createPropertyData } from "../utils/layerTools";
6
+ import _ from "lodash";
7
+ const DEFAULT_GROUP_STYLE = {
8
+ color: [0, 128, 255, 255],
9
+ width: 2,
10
+ dashArray: [0, 0],
11
+ };
12
+ // ---------------------------------------------------------------------------
13
+ // Default props
14
+ // ---------------------------------------------------------------------------
15
+ const defaultProps = {
16
+ "@@type": "PolylineGroupLayer",
17
+ name: "PolylineGroupLayer",
18
+ id: "polyline-group-layer",
19
+ pickable: false,
20
+ visible: true,
21
+ depthTest: true,
22
+ ZIncreasingDownwards: true,
23
+ defaultGroupStyle: DEFAULT_GROUP_STYLE,
24
+ widthUnits: "meters",
25
+ widthScale: 1,
26
+ widthMinPixels: 0,
27
+ widthMaxPixels: Number.MAX_SAFE_INTEGER,
28
+ jointRounded: false,
29
+ capRounded: false,
30
+ miterLimit: 4,
31
+ billboard: true,
32
+ getGroupPolylines: (g) => g.polylines,
33
+ getPolylinePath: (p) => p.path,
34
+ };
35
+ // ---------------------------------------------------------------------------
36
+ // Resolution helpers
37
+ // ---------------------------------------------------------------------------
38
+ function resolveColor(polyline, group, props) {
39
+ var _a, _b, _c, _d;
40
+ const fromPolylineAccessor = (_a = props.getPolylineColor) === null || _a === void 0 ? void 0 : _a.call(props, polyline, group);
41
+ if (fromPolylineAccessor != null)
42
+ return fromPolylineAccessor;
43
+ if (polyline.color != null)
44
+ return polyline.color;
45
+ const fromGroupAccessor = (_b = props.getGroupColor) === null || _b === void 0 ? void 0 : _b.call(props, group);
46
+ if (fromGroupAccessor != null)
47
+ return fromGroupAccessor;
48
+ if (group.color != null)
49
+ return group.color;
50
+ return (_d = (_c = props.defaultGroupStyle) === null || _c === void 0 ? void 0 : _c.color) !== null && _d !== void 0 ? _d : DEFAULT_GROUP_STYLE.color;
51
+ }
52
+ function resolveWidth(polyline, group, props) {
53
+ var _a, _b, _c, _d;
54
+ const fromPolylineAccessor = (_a = props.getPolylineWidth) === null || _a === void 0 ? void 0 : _a.call(props, polyline, group);
55
+ if (fromPolylineAccessor != null)
56
+ return fromPolylineAccessor;
57
+ if (polyline.width != null)
58
+ return polyline.width;
59
+ const fromGroupAccessor = (_b = props.getGroupWidth) === null || _b === void 0 ? void 0 : _b.call(props, group);
60
+ if (fromGroupAccessor != null)
61
+ return fromGroupAccessor;
62
+ if (group.width != null)
63
+ return group.width;
64
+ return (_d = (_c = props.defaultGroupStyle) === null || _c === void 0 ? void 0 : _c.width) !== null && _d !== void 0 ? _d : DEFAULT_GROUP_STYLE.width;
65
+ }
66
+ // ---------------------------------------------------------------------------
67
+ // Flatten
68
+ // ---------------------------------------------------------------------------
69
+ function isBinaryPolylines(p) {
70
+ return !Array.isArray(p) && "positions" in p;
71
+ }
72
+ function resolveDashArray(polyline, group, props) {
73
+ var _a, _b, _c, _d;
74
+ const fromPolylineAccessor = (_a = props.getPolylineDashArray) === null || _a === void 0 ? void 0 : _a.call(props, polyline, group);
75
+ if (fromPolylineAccessor != null)
76
+ return fromPolylineAccessor;
77
+ if (polyline.dashArray != null)
78
+ return polyline.dashArray;
79
+ const fromGroupAccessor = (_b = props.getGroupDashArray) === null || _b === void 0 ? void 0 : _b.call(props, group);
80
+ if (fromGroupAccessor != null)
81
+ return fromGroupAccessor;
82
+ if (group.dashArray != null)
83
+ return group.dashArray;
84
+ return (_d = (_c = props.defaultGroupStyle) === null || _c === void 0 ? void 0 : _c.dashArray) !== null && _d !== void 0 ? _d : DEFAULT_GROUP_STYLE.dashArray;
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // Segment resolution helpers (used when polyline.path is a PolylineGroup)
88
+ // ---------------------------------------------------------------------------
89
+ // Cascade: accessor(segment) -> segment field -> subGroup field -> parent polyline
90
+ // resolution (which itself cascades through accessor -> field -> outerGroup -> default).
91
+ function resolveSegmentColor(segment, subGroup, parentPolyline, outerGroup, props) {
92
+ var _a;
93
+ const fromAccessor = (_a = props.getPolylineColor) === null || _a === void 0 ? void 0 : _a.call(props, segment, outerGroup);
94
+ if (fromAccessor != null)
95
+ return fromAccessor;
96
+ if (segment.color != null)
97
+ return segment.color;
98
+ if (subGroup.color != null)
99
+ return subGroup.color;
100
+ return resolveColor(parentPolyline, outerGroup, props);
101
+ }
102
+ function resolveSegmentWidth(segment, subGroup, parentPolyline, outerGroup, props) {
103
+ var _a;
104
+ const fromAccessor = (_a = props.getPolylineWidth) === null || _a === void 0 ? void 0 : _a.call(props, segment, outerGroup);
105
+ if (fromAccessor != null)
106
+ return fromAccessor;
107
+ if (segment.width != null)
108
+ return segment.width;
109
+ if (subGroup.width != null)
110
+ return subGroup.width;
111
+ return resolveWidth(parentPolyline, outerGroup, props);
112
+ }
113
+ function resolveSegmentDashArray(segment, subGroup, parentPolyline, outerGroup, props) {
114
+ var _a;
115
+ const fromAccessor = (_a = props.getPolylineDashArray) === null || _a === void 0 ? void 0 : _a.call(props, segment, outerGroup);
116
+ if (fromAccessor != null)
117
+ return fromAccessor;
118
+ if (segment.dashArray != null)
119
+ return segment.dashArray;
120
+ if (subGroup.dashArray != null)
121
+ return subGroup.dashArray;
122
+ return resolveDashArray(parentPolyline, outerGroup, props);
123
+ }
124
+ /**
125
+ * Expands a polyline whose `path` is a {@link PolylineGroup} (i.e. a
126
+ * discontinuous polyline made of disjoint segments) into one {@link FlatEntry}
127
+ * per segment. Returns an empty array and emits a warning if the sub-group
128
+ * contains {@link BinaryPolylines}, which are not supported at the segment level.
129
+ */
130
+ function flattenSubGroupPolyline(polyline, subGroup, group, getPath, props) {
131
+ const segPolylines = subGroup.polylines;
132
+ if (isBinaryPolylines(segPolylines)) {
133
+ console.warn("PolylineGroupLayer: BinaryPolylines inside a sub-group path is not supported. Skipping.");
134
+ return [];
135
+ }
136
+ return segPolylines.flatMap((segment) => {
137
+ if (!Array.isArray(segment.path)) {
138
+ console.warn("PolylineGroupLayer: nested PolylineGroup paths inside segment polylines are not supported. Skipping segment.");
139
+ return [];
140
+ }
141
+ return [
142
+ {
143
+ path: getPath(segment, group),
144
+ color: resolveSegmentColor(segment, subGroup, polyline, group, props),
145
+ width: resolveSegmentWidth(segment, subGroup, polyline, group, props),
146
+ dashArray: resolveSegmentDashArray(segment, subGroup, polyline, group, props),
147
+ _polyline: polyline, // root polyline — for picking & hiddenPolylines
148
+ _group: group,
149
+ },
150
+ ];
151
+ });
152
+ }
153
+ /**
154
+ * Allocates combined typed-array buffers and fills them from all raw binary
155
+ * groups. Positions and indices are concatenated; color/width are replicated
156
+ * per vertex so the GPU can read them without per-vertex JS work.
157
+ *
158
+ * @remarks * `polylines` is a sanitized copy of group.polylines with invalid
159
+ * colors/widths buffers stripped out; do not read from group.polylines directly.
160
+ */
161
+ function buildBinaryData(binaryRaw, totalVerts, totalPaths, ZIncreasingDownwards, props) {
162
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
163
+ const positions = new Float32Array(totalVerts * 3);
164
+ const colors = new Uint8Array(totalVerts * 4);
165
+ const widths = new Float32Array(totalVerts);
166
+ const vertexGroupIndex = new Uint32Array(totalVerts);
167
+ const startIndices = new Uint32Array(totalPaths + 1);
168
+ const pathGroup = new Array(totalPaths);
169
+ const groups = binaryRaw.map((b) => b.group);
170
+ let vOffset = 0;
171
+ let pOffset = 0;
172
+ for (let gIdx = 0; gIdx < binaryRaw.length; gIdx++) {
173
+ const { group, polylines } = binaryRaw[gIdx];
174
+ const src = polylines.positions;
175
+ // Ensure positions buffer length is a multiple of 3; if not, ignore
176
+ // trailing vertices.
177
+ if (src.length % 3 !== 0) {
178
+ console.warn(`PolylineGroupLayer: BinaryPolylines group (id=` +
179
+ `${(_a = group.id) !== null && _a !== void 0 ? _a : "unknown"}) has a positions buffer whose` +
180
+ `length (${src.length}) is not a multiple of 3. Trailing ` +
181
+ `incomplete vertex will be ignored.`);
182
+ }
183
+ const srcVerts = Math.floor(src.length / 3);
184
+ // Copy positions (optionally flipping Z)
185
+ const base = vOffset * 3;
186
+ const srcLen = srcVerts * 3;
187
+ if (ZIncreasingDownwards) {
188
+ for (let i = 0; i < srcLen; i += 3) {
189
+ positions[base + i] = src[i];
190
+ positions[base + i + 1] = src[i + 1];
191
+ positions[base + i + 2] = -src[i + 2];
192
+ }
193
+ }
194
+ else {
195
+ // Slice to srcLen to exclude any trailing incomplete vertex.
196
+ positions.set(srcLen < src.length ? src.subarray(0, srcLen) : src, base);
197
+ }
198
+ if (polylines.colors) {
199
+ colors.set(polylines.colors, vOffset * 4);
200
+ }
201
+ else {
202
+ const color = (_f = (_d = (_c = (_b = props.getGroupColor) === null || _b === void 0 ? void 0 : _b.call(props, group)) !== null && _c !== void 0 ? _c : group.color) !== null && _d !== void 0 ? _d : (_e = props.defaultGroupStyle) === null || _e === void 0 ? void 0 : _e.color) !== null && _f !== void 0 ? _f : [0, 128, 255, 255];
203
+ // Flood-fill the group color across all vertices efficiently:
204
+ // 1. Write the first RGBA (4 × Uint8) into the combined Uint8Array
205
+ // at byte offset vOffset*4.
206
+ // 2. Reinterpret the same ArrayBuffer as Uint32 (1 element = 4 bytes
207
+ // = one RGBA pixel). Index vOffset in the Uint32 view therefore
208
+ // aliases the four bytes we just wrote.
209
+ // 3. Use TypedArray.fill to copy that single 32-bit value into every
210
+ // remaining vertex slot in one call — no per-channel bit-shifting.
211
+ colors.set([color[0], color[1], color[2], (_g = color[3]) !== null && _g !== void 0 ? _g : 255], vOffset * 4);
212
+ new Uint32Array(colors.buffer).fill(new Uint32Array(colors.buffer)[vOffset], vOffset + 1, vOffset + srcVerts);
213
+ }
214
+ if (polylines.widths) {
215
+ widths.set(polylines.widths, vOffset);
216
+ }
217
+ else {
218
+ widths.fill((_m = (_k = (_j = (_h = props.getGroupWidth) === null || _h === void 0 ? void 0 : _h.call(props, group)) !== null && _j !== void 0 ? _j : group.width) !== null && _k !== void 0 ? _k : (_l = props.defaultGroupStyle) === null || _l === void 0 ? void 0 : _l.width) !== null && _m !== void 0 ? _m : 2, vOffset, vOffset + srcVerts);
219
+ }
220
+ for (let v = 0; v < srcVerts; v++) {
221
+ vertexGroupIndex[vOffset + v] = gIdx;
222
+ }
223
+ // Offset and copy startIndices into the combined buffer.
224
+ const srcIdx = polylines.startIndices;
225
+ for (let p = 0; p < srcIdx.length; p++) {
226
+ startIndices[pOffset + p] = srcIdx[p] + vOffset;
227
+ pathGroup[pOffset + p] = group;
228
+ }
229
+ vOffset += srcVerts;
230
+ pOffset += srcIdx.length;
231
+ }
232
+ startIndices[totalPaths] = totalVerts; // terminal entry
233
+ return {
234
+ positions,
235
+ startIndices,
236
+ colors,
237
+ widths,
238
+ pathGroup,
239
+ vertexGroupIndex,
240
+ groups,
241
+ };
242
+ }
243
+ /**
244
+ * Builds the per-vertex GPU filter buffer for the binary sub-layer.
245
+ * Each vertex gets 1.0 if its group is visible, 0.0 if it is hidden.
246
+ * This is a plain typed-array allocation (O(vertices)); separated so it can
247
+ * be re-run cheaply when only `hiddenGroups` changes, without rebuilding the
248
+ * full `BinaryData`.
249
+ */
250
+ function buildFilterValues(binaryData, hiddenGroups) {
251
+ const filterValues = new Float32Array(binaryData.vertexGroupIndex.length);
252
+ for (let i = 0; i < filterValues.length; i++) {
253
+ const g = binaryData.groups[binaryData.vertexGroupIndex[i]];
254
+ filterValues[i] = g.id != null && (hiddenGroups === null || hiddenGroups === void 0 ? void 0 : hiddenGroups.has(g.id)) ? 0 : 1;
255
+ }
256
+ return filterValues;
257
+ }
258
+ /**
259
+ * Assembles the `data` object passed to the binary PathLayer sub-layer.
260
+ * Thin wrapper around the cached typed arrays; cheap to call, but creating a
261
+ * new object reference signals deck.gl to re-examine attribute buffers.
262
+ */
263
+ function buildBinaryLayerData(binaryData, filterValues) {
264
+ return {
265
+ length: binaryData.startIndices.length - 1,
266
+ startIndices: binaryData.startIndices,
267
+ attributes: {
268
+ getPath: { value: binaryData.positions, size: 3 },
269
+ getFilterValue: { value: filterValues, size: 1 },
270
+ getColor: { value: binaryData.colors, size: 4, normalized: true },
271
+ getWidth: { value: binaryData.widths, size: 1 },
272
+ },
273
+ };
274
+ }
275
+ function flattenGroupData(data, props) {
276
+ var _a, _b, _c, _d, _e, _f, _g;
277
+ const getPolylines = (_a = props.getGroupPolylines) !== null && _a !== void 0 ? _a : ((g) => g.polylines);
278
+ const getPath = (_b = props.getPolylinePath) !== null && _b !== void 0 ? _b : ((p) => p.path);
279
+ const flatData = [];
280
+ const ZIncreasingDownwards = (_c = props.ZIncreasingDownwards) !== null && _c !== void 0 ? _c : true;
281
+ // First pass over binary groups: collect groups + sum sizes so we can
282
+ // allocate combined typed arrays once.
283
+ const binaryRaw = [];
284
+ let totalVerts = 0;
285
+ let totalPaths = 0;
286
+ for (const group of data) {
287
+ const polylines = getPolylines(group);
288
+ if (isBinaryPolylines(polylines)) {
289
+ if (group.dashArray != null ||
290
+ ((_d = props.getGroupDashArray) === null || _d === void 0 ? void 0 : _d.call(props, group)) != null) {
291
+ console.warn("PolylineGroupLayer: dashArray is not supported on BinaryPolylines groups; ignoring.");
292
+ }
293
+ if (polylines.positions.length % 3 !== 0) {
294
+ console.warn(`PolylineGroupLayer: BinaryPolylines group (id=${(_e = group.id) !== null && _e !== void 0 ? _e : "unknown"}) has a positions buffer whose length (${polylines.positions.length}) is not a multiple of 3. Trailing incomplete vertex will be ignored.`);
295
+ }
296
+ const srcVerts = Math.floor(polylines.positions.length / 3);
297
+ let colors = polylines.colors;
298
+ if (colors && colors.length !== srcVerts * 4) {
299
+ console.warn(`PolylineGroupLayer: BinaryPolylines group (id=${(_f = group.id) !== null && _f !== void 0 ? _f : "unknown"}) has a colors buffer with wrong length (expected ${srcVerts * 4}, got ${colors.length}). Ignoring colors.`);
300
+ colors = undefined;
301
+ }
302
+ let widths = polylines.widths;
303
+ if (widths && widths.length !== srcVerts) {
304
+ console.warn(`PolylineGroupLayer: BinaryPolylines group (id=${(_g = group.id) !== null && _g !== void 0 ? _g : "unknown"}) has a widths buffer with wrong length (expected ${srcVerts}, got ${widths.length}). Ignoring widths.`);
305
+ widths = undefined;
306
+ }
307
+ binaryRaw.push({
308
+ group,
309
+ polylines: Object.assign(Object.assign({}, polylines), { colors, widths }),
310
+ });
311
+ totalVerts += srcVerts;
312
+ totalPaths += polylines.startIndices.length;
313
+ }
314
+ else {
315
+ for (const polyline of polylines) {
316
+ if (!Array.isArray(polyline.path)) {
317
+ flatData.push(...flattenSubGroupPolyline(polyline, polyline.path, group, getPath, props));
318
+ }
319
+ else {
320
+ flatData.push({
321
+ path: getPath(polyline, group),
322
+ color: resolveColor(polyline, group, props),
323
+ width: resolveWidth(polyline, group, props),
324
+ dashArray: resolveDashArray(polyline, group, props),
325
+ _polyline: polyline,
326
+ _group: group,
327
+ });
328
+ }
329
+ }
330
+ }
331
+ }
332
+ if (binaryRaw.length === 0) {
333
+ return { flatData, binaryData: null };
334
+ }
335
+ return {
336
+ flatData,
337
+ binaryData: buildBinaryData(binaryRaw, totalVerts, totalPaths, ZIncreasingDownwards, props),
338
+ };
339
+ }
340
+ function buildSectionIndex(path) {
341
+ const cumDist = [0];
342
+ for (let i = 1; i < path.length; i++) {
343
+ const dx = path[i][0] - path[i - 1][0];
344
+ const dy = path[i][1] - path[i - 1][1];
345
+ cumDist.push(cumDist[i - 1] + Math.sqrt(dx * dx + dy * dy));
346
+ }
347
+ return { cumDist, path };
348
+ }
349
+ /** Return the (x, y) world position on the fence at the given cumulative distance. */
350
+ function projectAbscissa(abscissa, idx) {
351
+ const { cumDist, path } = idx;
352
+ if (path.length === 0)
353
+ return [0, 0];
354
+ if (abscissa <= cumDist[0])
355
+ return [path[0][0], path[0][1]];
356
+ const last = cumDist.length - 1;
357
+ if (abscissa >= cumDist[last])
358
+ return [path[last][0], path[last][1]];
359
+ let lo = 0, hi = last;
360
+ while (hi - lo > 1) {
361
+ const mid = (lo + hi) >> 1;
362
+ if (cumDist[mid] <= abscissa)
363
+ lo = mid;
364
+ else
365
+ hi = mid;
366
+ }
367
+ const t = (abscissa - cumDist[lo]) / (cumDist[hi] - cumDist[lo]);
368
+ return [
369
+ path[lo][0] + t * (path[hi][0] - path[lo][0]),
370
+ path[lo][1] + t * (path[hi][1] - path[lo][1]),
371
+ ];
372
+ }
373
+ // ---------------------------------------------------------------------------
374
+ // Layer class
375
+ // ---------------------------------------------------------------------------
376
+ /**
377
+ * A deck.gl {@link CompositeLayer} that renders collections of polylines
378
+ * organised into named groups.
379
+ *
380
+ * **Data formats**
381
+ *
382
+ * Each {@link PolylineGroup} holds one or more polylines. Two formats are
383
+ * supported for `PolylineGroup.polylines`:
384
+ * - `Polyline[]` — per-object format; supports `id`, and per-polyline `color`,
385
+ * `width`, and `dashArray` overrides.
386
+ * - {@link BinaryPolylines} — flat typed-array format; more efficient for
387
+ * large datasets loaded from binary sources. Group-level styling only.
388
+ *
389
+ * **Discontinuous polylines**
390
+ *
391
+ * A `Polyline.path` may be a nested {@link PolylineGroup}, making one logical
392
+ * polyline consist of disjoint segments (e.g. a seismic horizon cut by faults).
393
+ * All segments share the root `Polyline.id` for picking and visibility filtering.
394
+ *
395
+ * **Styling cascade**
396
+ *
397
+ * Color, width, and dash pattern are resolved per-path in this order:
398
+ * polyline accessor → `Polyline` field → group accessor → `PolylineGroup` field
399
+ * → layer default prop.
400
+ *
401
+ * **Section-view projection**
402
+ *
403
+ * When {@link PolylineGroupLayerProps.sectionPath} is provided, path coordinates
404
+ * are interpreted as `[abscissa, depth]`. The layer renders two sub-layers:
405
+ * one in abscissa/depth space for {@link SectionViewport}s, and one with
406
+ * abscissa values projected back to world XY for 3-D viewports.
407
+ *
408
+ * **GPU-side visibility**
409
+ *
410
+ * {@link PolylineGroupLayerProps.hiddenGroups} and
411
+ * {@link PolylineGroupLayerProps.hiddenPolylines} use `DataFilterExtension`.
412
+ * Changing these sets triggers only a GPU attribute update; the flattened
413
+ * data buffer is never rebuilt.
414
+ */
415
+ export class PolylineGroupLayer extends CompositeLayer {
416
+ /** @override Builds the initial flat data buffer and section index from `props.data`. */
417
+ initializeState() {
418
+ const { data, sectionPath, hiddenGroups } = this.props;
419
+ const { flatData, binaryData } = flattenGroupData(data, this.props);
420
+ const binaryLayerData = binaryData
421
+ ? buildBinaryLayerData(binaryData, buildFilterValues(binaryData, hiddenGroups))
422
+ : null;
423
+ this.setState({
424
+ flatData,
425
+ binaryData,
426
+ binaryLayerData,
427
+ sectionIndex: sectionPath ? buildSectionIndex(sectionPath) : null,
428
+ });
429
+ }
430
+ /**
431
+ * @override
432
+ * Rebuilds the flat data buffer when `data` or any accessor/default prop changes.
433
+ * Rebuilds the section index when `sectionPath` changes.
434
+ */
435
+ updateState({ props, oldProps, }) {
436
+ const needsRebuild = props.data !== oldProps.data ||
437
+ props.getGroupColor !== oldProps.getGroupColor ||
438
+ props.getGroupWidth !== oldProps.getGroupWidth ||
439
+ props.getGroupDashArray !== oldProps.getGroupDashArray ||
440
+ props.getPolylineColor !== oldProps.getPolylineColor ||
441
+ props.getPolylineWidth !== oldProps.getPolylineWidth ||
442
+ props.getPolylineDashArray !== oldProps.getPolylineDashArray ||
443
+ props.getGroupPolylines !== oldProps.getGroupPolylines ||
444
+ props.getPolylinePath !== oldProps.getPolylinePath ||
445
+ !_.isEqual(props.defaultGroupStyle, oldProps.defaultGroupStyle) ||
446
+ props.ZIncreasingDownwards !== oldProps.ZIncreasingDownwards;
447
+ if (needsRebuild) {
448
+ const { flatData, binaryData } = flattenGroupData(props.data, props);
449
+ const binaryLayerData = binaryData
450
+ ? buildBinaryLayerData(binaryData, buildFilterValues(binaryData, props.hiddenGroups))
451
+ : null;
452
+ this.setState({ flatData, binaryData, binaryLayerData });
453
+ }
454
+ else if (props.hiddenGroups !== oldProps.hiddenGroups) {
455
+ // Only the per-vertex filter buffer needs rebuilding; the large
456
+ // position/color/width buffers are untouched.
457
+ const binaryData = this.state["binaryData"];
458
+ if (binaryData) {
459
+ this.setState({
460
+ binaryLayerData: buildBinaryLayerData(binaryData, buildFilterValues(binaryData, props.hiddenGroups)),
461
+ });
462
+ }
463
+ }
464
+ if (props.sectionPath !== oldProps.sectionPath) {
465
+ this.setState({
466
+ sectionIndex: props.sectionPath
467
+ ? buildSectionIndex(props.sectionPath)
468
+ : null,
469
+ });
470
+ }
471
+ }
472
+ /**
473
+ * @override
474
+ * Routes the `paths-section` sub-layer to {@link SectionViewport}s and
475
+ * the `paths-3d` sub-layer to all other viewports.
476
+ * Only active when `sectionPath` is set; otherwise the single `paths` sub-layer
477
+ * is always visible.
478
+ */
479
+ filterSubLayer({ layer, viewport }) {
480
+ const isSV = viewport.constructor === SectionViewport;
481
+ if (layer.id.endsWith("-paths-section"))
482
+ return isSV;
483
+ if (layer.id.endsWith("-paths-3d"))
484
+ return !isSV;
485
+ return true;
486
+ }
487
+ /**
488
+ * @override
489
+ * Returns one or more `PathLayer` sub-layers.
490
+ * - Without `sectionPath`: a single `paths` sub-layer for non-binary groups,
491
+ * plus a single `paths-binary` sub-layer that merges all binary groups into
492
+ * combined typed-array buffers and passes them straight to the GPU.
493
+ * - With `sectionPath`: a `paths-section` sub-layer (abscissa/depth space)
494
+ * and a `paths-3d` sub-layer (world XY space), routed by {@link filterSubLayer}.
495
+ * Binary groups are not supported in section mode and are skipped with a warning.
496
+ */
497
+ renderLayers() {
498
+ const flatData = this.state["flatData"];
499
+ const binaryData = this.state["binaryData"];
500
+ const sectionIndex = this.state["sectionIndex"];
501
+ const { widthUnits, widthScale, widthMinPixels, widthMaxPixels, jointRounded, capRounded, miterLimit, billboard, pickable, depthTest, ZIncreasingDownwards, hiddenGroups, hiddenPolylines, highPrecisionDash, } = this.props;
502
+ // Shared props for all sub-layers
503
+ const sharedProps = {
504
+ data: flatData,
505
+ pickable,
506
+ billboard,
507
+ widthUnits,
508
+ widthScale,
509
+ widthMinPixels,
510
+ widthMaxPixels,
511
+ jointRounded,
512
+ capRounded,
513
+ miterLimit,
514
+ parameters: { depthTest },
515
+ extensions: [
516
+ new DataFilterExtension({ filterSize: 1 }),
517
+ new PathStyleExtension({
518
+ dash: true,
519
+ highPrecisionDash: highPrecisionDash !== null && highPrecisionDash !== void 0 ? highPrecisionDash : false,
520
+ }),
521
+ ],
522
+ getFilterValue: (d) => {
523
+ var _a;
524
+ if (d._group.id != null && (hiddenGroups === null || hiddenGroups === void 0 ? void 0 : hiddenGroups.has(d._group.id)))
525
+ return 0;
526
+ if (((_a = d._polyline) === null || _a === void 0 ? void 0 : _a.id) != null &&
527
+ (hiddenPolylines === null || hiddenPolylines === void 0 ? void 0 : hiddenPolylines.has(d._polyline.id)))
528
+ return 0;
529
+ return 1;
530
+ },
531
+ filterRange: [1, 1],
532
+ getColor: (d) => d.color,
533
+ getWidth: (d) => d.width,
534
+ getDashArray: (d) => d.dashArray,
535
+ };
536
+ const updateTriggers = {
537
+ getFilterValue: [hiddenGroups, hiddenPolylines],
538
+ getColor: [flatData],
539
+ getWidth: [flatData],
540
+ getDashArray: [flatData],
541
+ getPath: [ZIncreasingDownwards],
542
+ };
543
+ const layers = [];
544
+ if (sectionIndex) {
545
+ if (binaryData) {
546
+ console.warn("PolylineGroupLayer: BinaryPolylines are not supported when sectionPath is set; skipping binary groups.");
547
+ }
548
+ // Two separate sub-layers — one per coordinate system — so each has
549
+ // its own GPU attribute buffer. filterSubLayer() routes each to the
550
+ // appropriate viewport type (SectionViewport vs. 3D).
551
+ const sectionUpdateTriggers = Object.assign(Object.assign({}, updateTriggers), { getPath: [...updateTriggers.getPath, sectionIndex] });
552
+ layers.push(new PathLayer(this.getSubLayerProps(Object.assign(Object.assign({}, sharedProps), { id: "paths-section", getPath: (d) => d.path.map((pt) => {
553
+ const z = ZIncreasingDownwards ? -pt[1] : pt[1];
554
+ return [pt[0], z, 0];
555
+ }), updateTriggers: sectionUpdateTriggers }))), new PathLayer(this.getSubLayerProps(Object.assign(Object.assign({}, sharedProps), { id: "paths-3d", getPath: (d) => d.path.map((pt) => {
556
+ const z = ZIncreasingDownwards ? -pt[1] : pt[1];
557
+ const [wx, wy] = projectAbscissa(pt[0], sectionIndex);
558
+ return [wx, wy, z];
559
+ }), updateTriggers: sectionUpdateTriggers }))));
560
+ return layers;
561
+ }
562
+ if (flatData.length > 0) {
563
+ layers.push(new PathLayer(this.getSubLayerProps(Object.assign(Object.assign({}, sharedProps), { id: "paths", getPath: (d) => {
564
+ if (!ZIncreasingDownwards)
565
+ return d.path;
566
+ return d.path.map(([x, y, z]) => [x, y, -(z !== null && z !== void 0 ? z : 0)]);
567
+ }, updateTriggers }))));
568
+ }
569
+ const binaryLayerData = this.state["binaryLayerData"];
570
+ if (binaryLayerData) {
571
+ layers.push(new PathLayer(this.getSubLayerProps(Object.assign(Object.assign({ id: "paths-binary" }, sharedProps), { extensions: [
572
+ new DataFilterExtension({ filterSize: 1 }),
573
+ ], data: binaryLayerData,
574
+ // Experimental deck.gl prop: tells PathLayer the paths
575
+ // are open (not closed rings), skipping the closure
576
+ // check. Safe here since BinaryPolylines are never
577
+ // closed. Re-evaluate if deck.gl removes/renames this.
578
+ _pathType: "open", getColor: undefined, getWidth: undefined, getFilterValue: undefined, getDashArray: undefined }))));
579
+ }
580
+ return layers;
581
+ }
582
+ /**
583
+ * @override
584
+ * Enriches the pick result with the originating {@link PolylineGroup} (`info.group`),
585
+ * the root {@link Polyline} (`info.object`), and layer properties for display
586
+ * (group name, polyline id, depth at the picked coordinate).
587
+ */
588
+ getPickingInfo({ info }) {
589
+ var _a, _b;
590
+ if (info.index < 0)
591
+ return info;
592
+ // Resolve the source group/polyline. For Polyline[] groups the picked
593
+ // FlatEntry carries them directly. For BinaryPolylines the sub-layer id
594
+ // (`paths-binary`) is used to look up the source group; the polyline
595
+ // is unavailable.
596
+ let pickedGroup;
597
+ let pickedPolyline;
598
+ let pickedSource;
599
+ const entry = info.object;
600
+ if (entry && entry._group) {
601
+ pickedGroup = entry._group;
602
+ pickedPolyline = entry._polyline;
603
+ pickedSource = entry;
604
+ }
605
+ else {
606
+ const sourceId = (_a = info.sourceLayer) === null || _a === void 0 ? void 0 : _a.id;
607
+ // Robust check: deck.gl may prefix/suffix the sub-layer id.
608
+ if (sourceId && sourceId.indexOf("paths-binary") !== -1) {
609
+ const binaryData = this.state["binaryData"];
610
+ const group = binaryData === null || binaryData === void 0 ? void 0 : binaryData.pathGroup[info.index];
611
+ if (group) {
612
+ pickedGroup = group;
613
+ pickedSource = group;
614
+ }
615
+ }
616
+ }
617
+ if (!pickedSource)
618
+ return info;
619
+ const layerProperties = [];
620
+ if (pickedGroup === null || pickedGroup === void 0 ? void 0 : pickedGroup.name) {
621
+ layerProperties.push(createPropertyData("Group", pickedGroup.name));
622
+ }
623
+ if ((pickedPolyline === null || pickedPolyline === void 0 ? void 0 : pickedPolyline.id) != null) {
624
+ layerProperties.push(createPropertyData("Polyline", String(pickedPolyline.id)));
625
+ }
626
+ const zScale = this.props.modelMatrix ? this.props.modelMatrix[10] : 1;
627
+ // Omit Depth property when the pick originated from a SectionView.
628
+ const viewport = info
629
+ .viewport;
630
+ const isSectionView = (viewport === null || viewport === void 0 ? void 0 : viewport.constructor) === SectionViewport;
631
+ if (!isSectionView && typeof ((_b = info.coordinate) === null || _b === void 0 ? void 0 : _b[2]) !== "undefined") {
632
+ const depth = (this.props.ZIncreasingDownwards
633
+ ? -info.coordinate[2]
634
+ : info.coordinate[2]) / Math.max(0.001, zScale);
635
+ layerProperties.push(createPropertyData("Depth", depth));
636
+ }
637
+ return Object.assign(Object.assign({}, info), {
638
+ // Expose the original polyline (when available) and group for
639
+ // onHover/onClick handlers. For binary groups `object` falls back
640
+ // to the BinaryGroupEntry so consumers always receive something stable.
641
+ object: pickedPolyline !== null && pickedPolyline !== void 0 ? pickedPolyline : pickedSource,
642
+ // @ts-expect-error -- deck.gl PickingInfo doesn't type extra fields; consumers can cast
643
+ group: pickedGroup, properties: layerProperties });
644
+ }
645
+ }
646
+ PolylineGroupLayer.layerName = "PolylineGroupLayer";
647
+ PolylineGroupLayer.defaultProps = defaultProps;
648
+ //# sourceMappingURL=polylineGroupLayer.js.map