@tangram.gl/tangram-layers 0.0.0-stage → 0.0.0

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.
Files changed (43) hide show
  1. package/README.md +35 -2
  2. package/dist/experimental/projected-basemaps.js +1310 -0
  3. package/dist/experimental/projected-basemaps.js.map +6 -0
  4. package/dist/experimental/terrain.js +379 -0
  5. package/dist/experimental/terrain.js.map +6 -0
  6. package/dist/experimental/webxr.js +1423 -0
  7. package/dist/experimental/webxr.js.map +6 -0
  8. package/dist/index.js +756 -0
  9. package/dist/index.js.map +6 -0
  10. package/dist/types/experimental/projected-basemaps.d.ts +120 -0
  11. package/dist/types/experimental/projected-detail.d.ts +33 -0
  12. package/dist/types/experimental/projected-navigation.d.ts +117 -0
  13. package/dist/types/experimental/terrain-display.d.ts +124 -0
  14. package/dist/types/experimental/terrain.d.ts +131 -0
  15. package/dist/types/first_person_view_adapter.d.ts +29 -0
  16. package/dist/types/globe_view_adapter.d.ts +33 -0
  17. package/dist/types/index.d.ts +84 -0
  18. package/dist/types/tangram-layer-lifecycle.d.ts +132 -0
  19. package/dist/types/tangram-layer.d.ts +157 -0
  20. package/dist/types/view_adapter_types.d.ts +42 -0
  21. package/dist/types/web_mercator_view_adapter.d.ts +77 -0
  22. package/package.json +80 -3
  23. package/src/experimental/projected-basemaps.ts +301 -0
  24. package/src/experimental/projected-detail.ts +116 -0
  25. package/src/experimental/projected-navigation.ts +323 -0
  26. package/src/experimental/terrain-display.ts +243 -0
  27. package/src/experimental/terrain.ts +240 -0
  28. package/src/experimental/webxr/grabbing.ts +166 -0
  29. package/src/experimental/webxr/index.d.ts +77 -0
  30. package/src/experimental/webxr/index.ts +72 -0
  31. package/src/experimental/webxr/interaction.ts +221 -0
  32. package/src/experimental/webxr/picking.ts +119 -0
  33. package/src/experimental/webxr/presentation.ts +457 -0
  34. package/src/experimental/webxr/projection.ts +371 -0
  35. package/src/experimental/webxr/types.ts +221 -0
  36. package/src/experimental/webxr/views.ts +190 -0
  37. package/src/first_person_view_adapter.ts +107 -0
  38. package/src/globe_view_adapter.ts +103 -0
  39. package/src/index.ts +64 -0
  40. package/src/tangram-layer-lifecycle.ts +130 -0
  41. package/src/tangram-layer.ts +679 -0
  42. package/src/view_adapter_types.ts +40 -0
  43. package/src/web_mercator_view_adapter.ts +185 -0
package/dist/index.js ADDED
@@ -0,0 +1,756 @@
1
+ // src/index.ts
2
+ import { Layer } from "@deck.gl/core";
3
+ import { Renderer as ClassicWebGLRenderer } from "@tangram.gl/tangram-renderer/core";
4
+
5
+ // src/web_mercator_view_adapter.ts
6
+ import { PROJECTION_CONSTANTS } from "@tangram.gl/tangram-renderer/core";
7
+ var DECK_TO_TANGRAM_ZOOM_OFFSET = 1;
8
+ var DECK_WORLD_SIZE = 512;
9
+ var TANGRAM_HALF_WORLD_METERS = Math.PI * PROJECTION_CONSTANTS.mercatorRadius;
10
+ var WebMercatorViewAdapter = class _WebMercatorViewAdapter {
11
+ /**
12
+ * Checks that a deck viewport provides the public Web Mercator contract used
13
+ * by TangramLayer.
14
+ * @param viewport deck.gl viewport.
15
+ * @returns Validation error, or null when supported.
16
+ */
17
+ static validateViewport(viewport) {
18
+ if (viewport?.isGeospatial === false) {
19
+ return new Error("a Web Mercator viewport is required");
20
+ }
21
+ if (_WebMercatorViewAdapter.validateGeographicAnchor(viewport, "Web Mercator viewport")) {
22
+ return new Error("a Web Mercator viewport is required");
23
+ }
24
+ return null;
25
+ }
26
+ /**
27
+ * Validates the geographic anchor shared by all deck.gl view adapters.
28
+ * @param viewport deck.gl viewport.
29
+ * @param viewportName View name used in the error message.
30
+ * @returns Validation error, or null when the anchor is finite.
31
+ */
32
+ static validateGeographicAnchor(viewport, viewportName = "viewport") {
33
+ if (!Number.isFinite(viewport?.longitude) || !Number.isFinite(viewport?.latitude) || !Number.isFinite(viewport?.zoom)) {
34
+ return new Error(`${viewportName} requires finite longitude, latitude, and zoom`);
35
+ }
36
+ return null;
37
+ }
38
+ /**
39
+ * Converts deck.gl camera matrices to Tangram camera matrices.
40
+ *
41
+ * Tangram tile models use absolute EPSG:3857 meters while deck matrices
42
+ * consume zoom-zero common coordinates. Altitude uses deck's
43
+ * latitude-dependent distance scale.
44
+ * @param viewport deck.gl WebMercatorViewport.
45
+ * @returns Tangram camera matrices and eye position.
46
+ */
47
+ static getCameraFrame(viewport) {
48
+ const distanceScales = typeof viewport.getDistanceScales === "function" ? viewport.getDistanceScales() : viewport.distanceScales;
49
+ const unitsPerMeter = distanceScales && distanceScales.unitsPerMeter;
50
+ const viewMatrix = viewport.viewMatrix;
51
+ const projectionMatrix = viewport.projectionMatrix;
52
+ if (!viewMatrix || viewMatrix.length !== 16 || !projectionMatrix || projectionMatrix.length !== 16 || !unitsPerMeter || !Number.isFinite(unitsPerMeter[2])) {
53
+ throw new Error("deck viewport camera matrices and distance scales are required");
54
+ }
55
+ const xyScale = DECK_WORLD_SIZE / (TANGRAM_HALF_WORLD_METERS * 2);
56
+ const metersToCommon = new Float64Array(16);
57
+ metersToCommon[0] = xyScale;
58
+ metersToCommon[5] = xyScale;
59
+ metersToCommon[10] = unitsPerMeter[2];
60
+ metersToCommon[12] = DECK_WORLD_SIZE / 2;
61
+ metersToCommon[13] = DECK_WORLD_SIZE / 2;
62
+ metersToCommon[15] = 1;
63
+ return {
64
+ view: multiplyMatrices(viewMatrix, metersToCommon),
65
+ projection: new Float32Array(projectionMatrix),
66
+ // The view matrix places the camera at the origin in eye coordinates.
67
+ position: [0, 0, 0]
68
+ };
69
+ }
70
+ /**
71
+ * Builds Tangram's external-camera frame for a deck.gl MapView.
72
+ * @param viewport deck.gl WebMercatorViewport.
73
+ * @param dimensions Render-target size.
74
+ * @returns Tangram HostFrame legacy shape.
75
+ */
76
+ static getFrame(viewport, { width, height }) {
77
+ const longitude = viewport.longitude;
78
+ const latitude = viewport.latitude;
79
+ const zoom = viewport.zoom;
80
+ if (typeof longitude !== "number" || !Number.isFinite(longitude) || typeof latitude !== "number" || !Number.isFinite(latitude) || typeof zoom !== "number" || !Number.isFinite(zoom)) {
81
+ throw new Error("a Web Mercator viewport is required");
82
+ }
83
+ const pitch = Math.abs(viewport.pitch || 0) * Math.PI / 180;
84
+ return {
85
+ viewport: { width, height },
86
+ view: {
87
+ longitude,
88
+ latitude,
89
+ zoom: zoom + DECK_TO_TANGRAM_ZOOM_OFFSET
90
+ },
91
+ projection: { type: "web-mercator" },
92
+ camera: _WebMercatorViewAdapter.getCameraFrame(viewport),
93
+ tileBuffer: Math.min(4, Math.ceil(Math.tan(pitch) * (viewport.height || height) / 256))
94
+ };
95
+ }
96
+ };
97
+ function multiplyMatrices(left, right) {
98
+ const result = new Float64Array(16);
99
+ for (let column = 0; column < 4; column++) {
100
+ for (let row = 0; row < 4; row++) {
101
+ let value = 0;
102
+ for (let index = 0; index < 4; index++) {
103
+ value += left[index * 4 + row] * right[column * 4 + index];
104
+ }
105
+ result[column * 4 + row] = value;
106
+ }
107
+ }
108
+ return result;
109
+ }
110
+
111
+ // src/first_person_view_adapter.ts
112
+ import { Matrix4 } from "@math.gl/core";
113
+ import { calculatePlanarVolumeBounds } from "@tangram.gl/tangram-renderer/core";
114
+ var DECK_WORLD_SIZE2 = 512;
115
+ var HALF_WORLD_METERS = 20037508342789244e-9;
116
+ var TILE_SIZE = 256;
117
+ var MAX_MERCATOR_LATITUDE = 85.05112878;
118
+ var DEFAULT_GROUND_EXTENT = 2e4;
119
+ var FirstPersonViewAdapter = class {
120
+ /** Converts the finite frustum/ground intersection without changing camera matrices. */
121
+ static getFrame = getFrame;
122
+ };
123
+ function getFrame(viewport, options = {}) {
124
+ return getFirstPersonFrameForCamera(viewport, WebMercatorViewAdapter.getCameraFrame(viewport), options);
125
+ }
126
+ function getFirstPersonFrameForCamera(viewport, camera, options = {}) {
127
+ const width = options.width ?? viewport.width;
128
+ const height = options.height ?? viewport.height;
129
+ const maxGroundExtent = options.maxGroundExtent ?? DEFAULT_GROUND_EXTENT;
130
+ if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
131
+ throw new Error("FirstPersonViewport requires positive width and height");
132
+ }
133
+ if (!Number.isFinite(maxGroundExtent) || maxGroundExtent <= 0) {
134
+ throw new Error("FirstPersonViewport maxGroundExtent must be finite and positive");
135
+ }
136
+ const { longitude, latitude } = viewport;
137
+ const unprojectFlat = viewport.unprojectFlat;
138
+ if (typeof longitude !== "number" || !Number.isFinite(longitude) || typeof latitude !== "number" || !Number.isFinite(latitude) || typeof unprojectFlat !== "function") {
139
+ throw new Error("FirstPersonViewport geographic center and ground projection method are required");
140
+ }
141
+ const inverseView = new Matrix4().copy(camera.view);
142
+ if (!Number.isFinite(inverseView.determinant()) || inverseView.determinant() === 0) {
143
+ throw new Error("FirstPersonViewport camera is singular");
144
+ }
145
+ const eye = inverseView.invert().transformAsPoint([0, 0, 0]);
146
+ if (!eye.every(Number.isFinite)) throw new Error("FirstPersonViewport eye position is invalid");
147
+ const latitudeScale = Math.cos(Math.max(
148
+ -MAX_MERCATOR_LATITUDE,
149
+ Math.min(MAX_MERCATOR_LATITUDE, latitude)
150
+ ) * Math.PI / 180);
151
+ const extent = maxGroundExtent / latitudeScale;
152
+ const bounds = calculatePlanarVolumeBounds(camera, options.elevationRange ?? [0, 0], {
153
+ sw: { x: eye[0] - extent, y: eye[1] - extent },
154
+ ne: { x: eye[0] + extent, y: eye[1] + extent }
155
+ });
156
+ const xyScale = DECK_WORLD_SIZE2 / (HALF_WORLD_METERS * 2);
157
+ const toGeographic = (x, y) => unprojectFlat.call(viewport, [
158
+ x * xyScale + DECK_WORLD_SIZE2 / 2,
159
+ y * xyScale + DECK_WORLD_SIZE2 / 2
160
+ ]);
161
+ const center = bounds ? toGeographic(
162
+ (bounds.sw.x + bounds.ne.x) / 2,
163
+ (bounds.sw.y + bounds.ne.y) / 2
164
+ ) : [longitude, latitude];
165
+ const metersPerPixel = bounds ? Math.max(
166
+ (bounds.ne.x - bounds.sw.x) / width,
167
+ (bounds.ne.y - bounds.sw.y) / height
168
+ ) : extent * 2 / Math.min(width, height);
169
+ let visibleBounds = null;
170
+ if (bounds) {
171
+ const southwest = toGeographic(bounds.sw.x, bounds.sw.y);
172
+ const northeast = toGeographic(bounds.ne.x, bounds.ne.y);
173
+ visibleBounds = [southwest[0], southwest[1], northeast[0], northeast[1]];
174
+ }
175
+ if (!center.every(Number.isFinite) || !Number.isFinite(metersPerPixel) || metersPerPixel <= 0 || visibleBounds && !visibleBounds.every(Number.isFinite)) {
176
+ throw new Error("FirstPersonViewport ground footprint is invalid");
177
+ }
178
+ return {
179
+ viewport: { width, height },
180
+ view: {
181
+ longitude: center[0],
182
+ latitude: center[1],
183
+ altitude: eye[2],
184
+ zoom: Math.max(0, Math.log2(HALF_WORLD_METERS * 2 / (TILE_SIZE * metersPerPixel)))
185
+ },
186
+ projection: { type: "web-mercator", visibleBounds },
187
+ camera,
188
+ tileBuffer: 1
189
+ };
190
+ }
191
+
192
+ // src/globe_view_adapter.ts
193
+ var DECK_TO_TANGRAM_ZOOM_OFFSET2 = 1;
194
+ var GlobeViewAdapter = class {
195
+ /** Converts globe common-space matrices and visible geographic bounds. */
196
+ static getFrame = getFrame2;
197
+ };
198
+ function getFrame2(viewport, options = {}) {
199
+ if (options.maxElevation !== void 0 && (!Number.isFinite(options.maxElevation) || options.maxElevation < 0)) {
200
+ throw new Error("GlobeView maxElevation must be a finite non-negative number");
201
+ }
202
+ if (!viewport || !Number.isFinite(viewport.width) || !Number.isFinite(viewport.height) || !viewport.viewMatrix || viewport.viewMatrix.length !== 16 || !viewport.projectionMatrix || viewport.projectionMatrix.length !== 16 || !viewport.cameraPosition || viewport.cameraPosition.length !== 3 || !Array.from(viewport.cameraPosition).every(Number.isFinite) || typeof viewport.getBounds !== "function") {
203
+ throw new Error("deck GlobeViewport matrices, camera position, size, and visible bounds are required");
204
+ }
205
+ const visibleBounds = options.visibleBounds ?? viewport.getBounds({ z: 0 });
206
+ if (!Array.isArray(visibleBounds) || visibleBounds.length !== 4 || visibleBounds.some((value) => !Number.isFinite(value))) {
207
+ throw new Error("deck GlobeViewport must provide finite geographic bounds");
208
+ }
209
+ return {
210
+ viewport: { width: viewport.width, height: viewport.height },
211
+ view: {
212
+ longitude: viewport.longitude,
213
+ latitude: viewport.latitude,
214
+ zoom: Math.max(0, viewport.zoom + DECK_TO_TANGRAM_ZOOM_OFFSET2)
215
+ },
216
+ projection: {
217
+ type: "globe",
218
+ visibleBounds: [visibleBounds[0], visibleBounds[1], visibleBounds[2], visibleBounds[3]],
219
+ ...options.maxElevation === void 0 ? {} : { maxElevation: options.maxElevation }
220
+ },
221
+ camera: {
222
+ view: new Float64Array(viewport.viewMatrix),
223
+ projection: new Float32Array(
224
+ multiplyMatrices2(viewport.projectionMatrix, viewport.viewMatrix)
225
+ ),
226
+ position: [viewport.cameraPosition[0], viewport.cameraPosition[1], viewport.cameraPosition[2]]
227
+ },
228
+ tileBuffer: 0,
229
+ globePreloadZoom: options.preloadZoom
230
+ };
231
+ }
232
+ function multiplyMatrices2(left, right) {
233
+ const result = new Float64Array(16);
234
+ for (let column = 0; column < 4; column++) {
235
+ for (let row = 0; row < 4; row++) {
236
+ let value = 0;
237
+ for (let index = 0; index < 4; index++) {
238
+ value += left[index * 4 + row] * right[column * 4 + index];
239
+ }
240
+ result[column * 4 + row] = value;
241
+ }
242
+ }
243
+ return result;
244
+ }
245
+
246
+ // src/tangram-layer.ts
247
+ var VIEW_EPSILON = 1e-7;
248
+ function injectNextzenApiKey(config, apiKey) {
249
+ if (!apiKey || !config || !config.sources) {
250
+ return false;
251
+ }
252
+ let updated = false;
253
+ for (const source of Object.values(config.sources)) {
254
+ if (source && typeof source.url === "string" && source.url.includes("nextzen.org")) {
255
+ source.url_params = source.url_params || {};
256
+ source.url_params.api_key = apiKey;
257
+ updated = true;
258
+ }
259
+ }
260
+ return updated;
261
+ }
262
+ function getExternalCameraFrame(viewport) {
263
+ return WebMercatorViewAdapter.getCameraFrame(viewport);
264
+ }
265
+ function getFirstPersonViewFrame(viewport, options = {}) {
266
+ return FirstPersonViewAdapter.getFrame(viewport, options);
267
+ }
268
+ function getGlobeViewFrame(viewport, options = {}) {
269
+ return GlobeViewAdapter.getFrame(viewport, options);
270
+ }
271
+ function createTangramLayerClass({ Layer: Layer2, ClassicWebGLRenderer: ClassicWebGLRenderer2, Renderer }, viewAdapter) {
272
+ const rendererClass = ClassicWebGLRenderer2 || Renderer;
273
+ if (!Layer2 || !rendererClass) {
274
+ throw new Error("createTangramLayerClass requires Layer and ClassicWebGLRenderer");
275
+ }
276
+ class TangramLayer2 extends Layer2 {
277
+ /** Forward deck.gl's property objects without changing its constructor semantics. */
278
+ constructor(...properties) {
279
+ super(...properties);
280
+ }
281
+ initializeState(_context) {
282
+ this.setState({ tangramRecord: null });
283
+ }
284
+ updateState({ props }) {
285
+ let record = this.state.tangramRecord;
286
+ const shouldCreateScene = !record || record.sceneSource !== props.scene || record.sceneBasePath !== props.sceneBasePath || record.apiKey !== props.apiKey || record.projectionEngine !== (props.projectionEngine ?? void 0) || record.projectionEngineExecution !== (props.projectionEngineExecution ?? void 0) || Boolean(record.terrain) !== Boolean(props.terrain) || record.terrainResourceBudget !== (props.terrainResourceBudget ?? void 0) || record.maxConcurrentTileLoadsPerWorker !== (props.maxConcurrentTileLoadsPerWorker ?? void 0);
287
+ if (shouldCreateScene) {
288
+ this._disposeTangramRecord(record);
289
+ record = this._createTangramRecord(props);
290
+ this.setState({ tangramRecord: record });
291
+ }
292
+ if (record) {
293
+ record.owner = this;
294
+ if (record.terrain !== props.terrain) {
295
+ record.terrain = props.terrain;
296
+ const terrain = props.terrain;
297
+ const revision = record.terrainRevision = (record.terrainRevision || 0) + 1;
298
+ record.terrainPendingRevision = revision;
299
+ Promise.resolve(record.loadPromise).then(async () => {
300
+ try {
301
+ if (!record.disposed && revision === record.terrainRevision && terrain) {
302
+ await record.renderer.setTerrain(terrain);
303
+ if (!record.disposed && revision === record.terrainRevision) {
304
+ record.terrainPendingRevision = 0;
305
+ record.renderer.setTerrainExaggeration(record.owner.props.terrainExaggeration ?? 1);
306
+ }
307
+ }
308
+ } finally {
309
+ if (record.terrainPendingRevision === revision) record.terrainPendingRevision = 0;
310
+ }
311
+ }).catch((error) => {
312
+ if (!record.disposed && revision === record.terrainRevision) this._raiseBridgeError(normalizeError(error));
313
+ });
314
+ }
315
+ if (props.terrain && !record.terrainPendingRevision) {
316
+ try {
317
+ record.renderer.setTerrainExaggeration(props.terrainExaggeration ?? 1);
318
+ } catch (error) {
319
+ this._raiseBridgeError(normalizeError(error));
320
+ }
321
+ }
322
+ }
323
+ }
324
+ draw({ renderPass } = {}) {
325
+ const record = this.state.tangramRecord;
326
+ if (!record || record.disposed) {
327
+ return;
328
+ }
329
+ record.owner = this;
330
+ this._synchronizeTangramScene(record);
331
+ if (!this._canRender(record, this.props)) {
332
+ return;
333
+ }
334
+ const renderTangram = () => {
335
+ const update_options = { force: true };
336
+ renderPass = renderPass || this.context.renderPass;
337
+ if (renderPass) {
338
+ update_options.renderPass = renderPass;
339
+ }
340
+ const rendered = record.renderer.render(update_options);
341
+ if (record.scene.config && record.scene.config.scene && record.scene.config.scene.animated === true) {
342
+ this.setNeedsRedraw();
343
+ }
344
+ if (rendered && record.gl) {
345
+ record.gl.depthMask(true);
346
+ record.gl.clear(record.gl.DEPTH_BUFFER_BIT | record.gl.STENCIL_BUFFER_BIT);
347
+ }
348
+ };
349
+ if (record.device.type === "webgl") {
350
+ record.scene.withWebGLContext(renderTangram);
351
+ } else {
352
+ renderTangram();
353
+ }
354
+ }
355
+ finalizeState(_context) {
356
+ this._disposeTangramRecord(this.state && this.state.tangramRecord);
357
+ }
358
+ /** Asynchronously query a view-local or canvas CSS pixel; the host must keep drawing. */
359
+ getFeatureAt(pixel, options = {}) {
360
+ const record = this.state.tangramRecord;
361
+ if (!record || record.disposed || record.loadFailed) return Promise.resolve(void 0);
362
+ const pending = record.renderer.getFeatureAt(pixel, options);
363
+ record.owner.setNeedsRedraw();
364
+ return pending;
365
+ }
366
+ /** Intersect the actual rendered eye with a host-owned terrain mesh in content coordinates. */
367
+ getTerrainAt(pixel, surface, options = {}) {
368
+ const record = this.state && this.state.tangramRecord;
369
+ if (!record || record.disposed || record.loadFailed || !record.loaded) return null;
370
+ return record.renderer.getTerrainAt(pixel, surface, options);
371
+ }
372
+ /** Exact admitted ground triangles at the last frame, including temporal relief and globe projection. */
373
+ getDrapedTerrainSurface() {
374
+ const record = this.state?.tangramRecord;
375
+ return record && !record.disposed && record.loaded && !record.loadFailed ? record.renderer.getDrapedTerrainSurface() : null;
376
+ }
377
+ /** Resident worker preparation costs; GPU, source and scratch allocations are separate. */
378
+ getTerrainDrapingStatistics() {
379
+ const record = this.state?.tangramRecord;
380
+ return record && !record.disposed ? record.renderer.getTerrainDrapingStatistics() : void 0;
381
+ }
382
+ get isLoaded() {
383
+ const record = this.state && this.state.tangramRecord;
384
+ return Boolean(record && record.loaded && !record.loadFailed && !record.disposed);
385
+ }
386
+ _createTangramRecord(props) {
387
+ if (!props.scene) {
388
+ this._raiseBridgeError(new Error("scene is required"));
389
+ return null;
390
+ }
391
+ const deckCanvas = this.context.deck && this.context.deck.getCanvas();
392
+ const device = this.context.device;
393
+ if (!deckCanvas) {
394
+ this._raiseBridgeError(new Error("deck canvas is required"));
395
+ return null;
396
+ }
397
+ if (!device || device.type !== "webgl" && device.type !== "webgpu" || typeof device.createBuffer !== "function" || typeof device.createShader !== "function" || typeof device.createTexture !== "function" || typeof device.createRenderPipeline !== "function" || typeof device.createVertexArray !== "function") {
398
+ this._raiseBridgeError(new Error("a deck.gl luma.gl Device is required"));
399
+ return null;
400
+ }
401
+ let gl = null;
402
+ if (device.type === "webgl") {
403
+ gl = device.handle;
404
+ if (!gl || typeof device.pushState !== "function" || typeof device.popState !== "function") {
405
+ this._raiseBridgeError(new Error("a deck.gl WebGLDevice is required"));
406
+ return null;
407
+ }
408
+ if (gl.canvas !== deckCanvas) {
409
+ this._raiseBridgeError(
410
+ new Error("deck canvas and WebGLDevice handle must share a context")
411
+ );
412
+ return null;
413
+ }
414
+ }
415
+ const record = {
416
+ owner: this,
417
+ renderer: null,
418
+ scene: null,
419
+ device,
420
+ gl,
421
+ deckCanvas,
422
+ sceneSource: props.scene,
423
+ sceneBasePath: props.sceneBasePath,
424
+ apiKey: props.apiKey,
425
+ projectionEngine: props.projectionEngine ?? void 0,
426
+ terrain: props.terrain,
427
+ terrainResourceBudget: props.terrainResourceBudget ?? void 0,
428
+ terrainPendingRevision: 0,
429
+ projectionEngineExecution: props.projectionEngineExecution ?? void 0,
430
+ maxConcurrentTileLoadsPerWorker: props.maxConcurrentTileLoadsPerWorker ?? void 0,
431
+ canvasWidth: null,
432
+ canvasHeight: null,
433
+ disposed: false,
434
+ destroyed: false,
435
+ loadSettled: false,
436
+ loadFailed: false,
437
+ loaded: false,
438
+ lastSceneError: null,
439
+ lastViewportError: null,
440
+ reportedViewportError: null,
441
+ loadPromise: null,
442
+ webglScopeDepth: 0
443
+ };
444
+ let renderer;
445
+ try {
446
+ const renderer_options = {
447
+ device,
448
+ canvas: deckCanvas,
449
+ projectionEngine: record.projectionEngine,
450
+ terrain: record.terrain,
451
+ terrainResourceBudget: record.terrainResourceBudget,
452
+ projectionEngineExecution: record.projectionEngineExecution,
453
+ maxConcurrentTileLoadsPerWorker: record.maxConcurrentTileLoadsPerWorker,
454
+ requestRedraw: () => {
455
+ if (!record.disposed && record.owner.setNeedsRedraw) {
456
+ record.owner.setNeedsRedraw();
457
+ }
458
+ },
459
+ continuousZoom: true,
460
+ highDensityDisplay: true,
461
+ logLevel: "warn"
462
+ };
463
+ if (gl) {
464
+ renderer_options.webGLContext = gl;
465
+ renderer_options.webGLContextScope = (callback) => this._withDeviceState(record, callback);
466
+ }
467
+ renderer = rendererClass.create(props.scene, renderer_options);
468
+ } catch (error) {
469
+ this._raiseBridgeError(normalizeError(error));
470
+ return null;
471
+ }
472
+ record.renderer = renderer;
473
+ record.scene = renderer.scene;
474
+ const liveRecord = record;
475
+ renderer.subscribe({
476
+ load: (message) => {
477
+ injectNextzenApiKey(message.config, record.apiKey);
478
+ },
479
+ update: () => this._updateAttributions(liveRecord),
480
+ error: (message) => this._reportSceneError(liveRecord, normalizeError(message), message.type !== "scene_import")
481
+ });
482
+ this._synchronizeTangramScene(liveRecord);
483
+ record.loadPromise = Promise.resolve().then(() => {
484
+ if (record.disposed) {
485
+ return null;
486
+ }
487
+ return renderer.load(props.scene, {
488
+ // Creation already rejected missing scene input; this closure preserves that identity.
489
+ base_path: props.sceneBasePath,
490
+ blocking: false
491
+ });
492
+ }).then((result) => {
493
+ if (!record.disposed) {
494
+ record.loaded = true;
495
+ record.owner.setNeedsRedraw && record.owner.setNeedsRedraw();
496
+ record.owner.props.onSceneLoad(liveRecord.scene);
497
+ this._updateAttributions(liveRecord);
498
+ }
499
+ return result;
500
+ }).catch((error) => {
501
+ if (!record.disposed) {
502
+ record.loadFailed = true;
503
+ this._reportSceneError(liveRecord, normalizeError(error));
504
+ }
505
+ }).finally(() => {
506
+ record.loadSettled = true;
507
+ if (record.disposed) {
508
+ this._destroyTangramRecord(liveRecord);
509
+ }
510
+ });
511
+ return liveRecord;
512
+ }
513
+ /** Refresh credits after source recreation; ignore stale metadata and disposed layers. */
514
+ _updateAttributions(record) {
515
+ if (record.disposed || typeof record.renderer.getAttributions !== "function") return;
516
+ const generation = record.attributionGeneration = (record.attributionGeneration || 0) + 1;
517
+ Promise.resolve().then(() => record.disposed ? void 0 : record.renderer.getAttributions()).then((credits) => {
518
+ if (credits && !record.disposed && generation === record.attributionGeneration) {
519
+ record.owner.props.onAttributionChange?.(credits, record.scene);
520
+ }
521
+ }).catch((error) => {
522
+ if (!record.disposed && generation === record.attributionGeneration) {
523
+ this._reportSceneError(record, normalizeError(error), false);
524
+ }
525
+ });
526
+ }
527
+ _synchronizeTangramScene(record) {
528
+ const viewport = this.context.viewport;
529
+ const viewports = this.context.deck.getViewports ? this.context.deck.getViewports() : [viewport];
530
+ if (viewAdapter) {
531
+ try {
532
+ if (viewports.length !== 1) throw new Error("only one deck.gl viewport is supported");
533
+ const width2 = record.deckCanvas.clientWidth || viewport.width;
534
+ const height2 = record.deckCanvas.clientHeight || viewport.height;
535
+ const frame2 = viewAdapter.getFrame(viewport, this.props, { width: width2, height: height2 });
536
+ record.renderer.setFrame({ ...frame2, tileResources: this.props.tileResources ?? void 0 });
537
+ record.lastViewportError = null;
538
+ record.reportedViewportError = null;
539
+ } catch (error) {
540
+ record.lastViewportError = error.message;
541
+ this._raiseViewportError(record, normalizeError(error));
542
+ }
543
+ return;
544
+ }
545
+ const globeOptions = {
546
+ maxElevation: this.props.globeMaxElevation ?? void 0,
547
+ visibleBounds: this.props.globeVisibleBounds ?? void 0,
548
+ preloadZoom: this.props.globePreloadZoom ?? void 0
549
+ };
550
+ const firstPersonOptions = {
551
+ maxGroundExtent: this.props.firstPersonMaxGroundExtent ?? void 0,
552
+ elevationRange: this.props.firstPersonElevationRange ?? void 0
553
+ };
554
+ const viewportError = validateViewport(viewport, viewports, globeOptions, firstPersonOptions);
555
+ if (viewportError) {
556
+ record.lastViewportError = viewportError.message;
557
+ this._raiseViewportError(record, viewportError);
558
+ return;
559
+ }
560
+ record.lastViewportError = null;
561
+ record.reportedViewportError = null;
562
+ const width = record.deckCanvas.clientWidth || viewport.width;
563
+ const height = record.deckCanvas.clientHeight || viewport.height;
564
+ record.canvasWidth = width;
565
+ record.canvasHeight = height;
566
+ const frame = isGlobeViewport(viewport) ? { ...getGlobeViewFrame(viewport, globeOptions), viewport: { width, height } } : isFirstPersonViewport(viewport) ? getFirstPersonViewFrame(viewport, { width, height, ...firstPersonOptions }) : getMapViewFrame(viewport, { width, height });
567
+ record.renderer.setFrame({ ...frame, tileResources: this.props.tileResources ?? void 0 });
568
+ }
569
+ _canRender(record, props) {
570
+ return props.visible !== false && props.opacity !== 0 && record.loaded && !record.loadFailed && !record.lastViewportError;
571
+ }
572
+ _raiseViewportError(record, error) {
573
+ if (record.reportedViewportError === error.message) {
574
+ return;
575
+ }
576
+ record.reportedViewportError = error.message;
577
+ this._raiseBridgeError(error);
578
+ }
579
+ _reportSceneError(record, error, isFatal = true) {
580
+ if (record.disposed || record.lastSceneError === error.message) {
581
+ return;
582
+ }
583
+ record.lastSceneError = error.message;
584
+ if (isFatal) {
585
+ record.loadFailed = true;
586
+ }
587
+ record.owner.props.onSceneError(error, record.scene);
588
+ record.owner.raiseError(error, "TangramLayer scene");
589
+ }
590
+ _raiseBridgeError(error) {
591
+ this.raiseError(error, "TangramLayer bridge");
592
+ }
593
+ _disposeTangramRecord(record) {
594
+ if (!record || record.disposed) {
595
+ return;
596
+ }
597
+ record.disposed = true;
598
+ if (!record.loadPromise || record.loadSettled) {
599
+ this._destroyTangramRecord(record);
600
+ }
601
+ }
602
+ _destroyTangramRecord(record) {
603
+ if (!record.destroyed) {
604
+ record.destroyed = true;
605
+ record.renderer.destroy();
606
+ }
607
+ }
608
+ _withDeviceState(record, callback) {
609
+ if (record.webglScopeDepth > 0) {
610
+ return callback();
611
+ }
612
+ const { device, gl } = record;
613
+ const lumaState = gl.lumaState;
614
+ const hasTrackedProgram = Boolean(lumaState && "program" in lumaState);
615
+ const previousProgram = hasTrackedProgram ? lumaState.program : gl.getParameter(gl.CURRENT_PROGRAM);
616
+ record.webglScopeDepth++;
617
+ device.pushState();
618
+ try {
619
+ return callback();
620
+ } finally {
621
+ try {
622
+ device.popState();
623
+ gl.useProgram(previousProgram);
624
+ } finally {
625
+ record.webglScopeDepth--;
626
+ }
627
+ }
628
+ }
629
+ }
630
+ TangramLayer2.layerName = "TangramLayer";
631
+ TangramLayer2.defaultProps = {
632
+ projectionEngine: null,
633
+ projectionEngineExecution: null,
634
+ scene: null,
635
+ sceneBasePath: null,
636
+ apiKey: null,
637
+ globeMaxElevation: null,
638
+ globeVisibleBounds: null,
639
+ globePreloadZoom: null,
640
+ tileResources: null,
641
+ maxConcurrentTileLoadsPerWorker: null,
642
+ firstPersonMaxGroundExtent: 2e4,
643
+ terrain: { type: "object", value: null, compare: false },
644
+ terrainResourceBudget: { type: "object", value: null, compare: false },
645
+ terrainExaggeration: { type: "number", value: 1, min: 0, max: 1e3 },
646
+ firstPersonElevationRange: { type: "array", value: [0, 0], compare: true },
647
+ onSceneLoad: () => {
648
+ },
649
+ onAttributionChange: () => {
650
+ },
651
+ onSceneError: () => {
652
+ }
653
+ };
654
+ return TangramLayer2;
655
+ }
656
+ function validateViewport(viewport, viewports, globeOptions = {}, firstPersonOptions = {}) {
657
+ if (viewports.length !== 1) {
658
+ return new Error("only one deck.gl viewport is supported");
659
+ }
660
+ const globe = isGlobeViewport(viewport);
661
+ const firstPerson = isFirstPersonViewport(viewport);
662
+ const geographicAnchorError = WebMercatorViewAdapter.validateGeographicAnchor(
663
+ viewport,
664
+ globe ? "GlobeViewport" : "Web Mercator viewport"
665
+ );
666
+ if (geographicAnchorError) {
667
+ return globe ? geographicAnchorError : new Error("a Web Mercator viewport is required");
668
+ }
669
+ if (!globe) {
670
+ const webMercatorError = WebMercatorViewAdapter.validateViewport(viewport);
671
+ if (webMercatorError) {
672
+ return webMercatorError;
673
+ }
674
+ }
675
+ if (!Number.isFinite(viewport.bearing || 0) || !Number.isFinite(viewport.pitch || 0) || (viewport.pitch || 0) < (firstPerson ? -90 : -VIEW_EPSILON) || (firstPerson ? (viewport.pitch || 0) > 90 : (viewport.pitch || 0) >= 90)) {
676
+ return new Error("bearing and pitch must describe a finite deck.gl camera");
677
+ }
678
+ try {
679
+ if (globe) {
680
+ getGlobeViewFrame(viewport, globeOptions);
681
+ } else if (isFirstPersonViewport(viewport)) {
682
+ getFirstPersonViewFrame(viewport, firstPersonOptions);
683
+ } else {
684
+ WebMercatorViewAdapter.getCameraFrame(viewport);
685
+ }
686
+ } catch (error) {
687
+ return error;
688
+ }
689
+ return null;
690
+ }
691
+ function getMapViewFrame(viewport, { width, height }) {
692
+ return WebMercatorViewAdapter.getFrame(viewport, { width, height });
693
+ }
694
+ function isFirstPersonViewport(viewport) {
695
+ return Boolean(
696
+ viewport && viewport.constructor && viewport.constructor.displayName === "FirstPersonViewport"
697
+ );
698
+ }
699
+ function isGlobeViewport(viewport) {
700
+ return Boolean(
701
+ viewport && (viewport.constructor?.displayName === "GlobeViewport" || viewport.constructor?.name === "GlobeViewport")
702
+ );
703
+ }
704
+ function normalizeError(value) {
705
+ const fields = value;
706
+ if (value instanceof Error) {
707
+ return value;
708
+ }
709
+ if (value && fields.error instanceof Error) {
710
+ return fields.error;
711
+ }
712
+ if (value && fields.message) {
713
+ return new Error(fields.message);
714
+ }
715
+ return new Error(String(value));
716
+ }
717
+ var tangram_layer_default = createTangramLayerClass;
718
+
719
+ // src/index.ts
720
+ var BaseTangramLayer = tangram_layer_default({ Layer, ClassicWebGLRenderer, Renderer: void 0 });
721
+ var TangramLayer = class extends BaseTangramLayer {
722
+ /** Stable deck.gl metadata for the typed public subclass. */
723
+ static componentName = "TangramLayer";
724
+ /** Compatibility name for integrations using deck.gl's historical metadata. */
725
+ static layerName = "TangramLayer";
726
+ /** Preserve deck.gl's public initialization signature through the injected superclass. */
727
+ initializeState(_context) {
728
+ super.initializeState();
729
+ }
730
+ /** Forward the standard deck.gl property/state change contract. */
731
+ updateState(parameters) {
732
+ super.updateState(parameters);
733
+ }
734
+ /** Release the scene using the standard deck.gl finalization signature. */
735
+ finalizeState(_context) {
736
+ super.finalizeState();
737
+ }
738
+ /** Forward deck's complete draw options to the existing render-pass bridge. */
739
+ draw(options) {
740
+ super.draw(options);
741
+ }
742
+ };
743
+ var src_default = TangramLayer;
744
+ export {
745
+ FirstPersonViewAdapter,
746
+ GlobeViewAdapter,
747
+ TangramLayer,
748
+ WebMercatorViewAdapter,
749
+ tangram_layer_default as createTangramLayerClass,
750
+ src_default as default,
751
+ getExternalCameraFrame,
752
+ getFirstPersonViewFrame,
753
+ getGlobeViewFrame,
754
+ injectNextzenApiKey
755
+ };
756
+ //# sourceMappingURL=index.js.map