@deck.gl/geo-layers 9.3.7 → 9.4.0-alpha.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.
Files changed (33) hide show
  1. package/dist/dist.dev.js +9723 -9576
  2. package/dist/index.cjs +151 -27
  3. package/dist/index.cjs.map +3 -3
  4. package/dist/mesh-layer/mesh-layer-vertex.glsl.d.ts +1 -1
  5. package/dist/mesh-layer/mesh-layer-vertex.glsl.d.ts.map +1 -1
  6. package/dist/mesh-layer/mesh-layer-vertex.glsl.js +3 -4
  7. package/dist/mesh-layer/mesh-layer-vertex.glsl.js.map +1 -1
  8. package/dist/mesh-layer/mesh-layer.d.ts +1 -1
  9. package/dist/mesh-layer/mesh-layer.d.ts.map +1 -1
  10. package/dist/mesh-layer/mesh-layer.js +7 -14
  11. package/dist/mesh-layer/mesh-layer.js.map +1 -1
  12. package/dist/terrain-layer/terrain-layer.d.ts.map +1 -1
  13. package/dist/terrain-layer/terrain-layer.js +45 -5
  14. package/dist/terrain-layer/terrain-layer.js.map +1 -1
  15. package/dist/tileset-2d/tile-2d-header.d.ts +1 -0
  16. package/dist/tileset-2d/tile-2d-header.d.ts.map +1 -1
  17. package/dist/tileset-2d/tile-2d-header.js +3 -5
  18. package/dist/tileset-2d/tile-2d-header.js.map +1 -1
  19. package/dist/tileset-2d/tile-2d-traversal.d.ts.map +1 -1
  20. package/dist/tileset-2d/tile-2d-traversal.js +23 -1
  21. package/dist/tileset-2d/tile-2d-traversal.js.map +1 -1
  22. package/dist/tileset-2d/tileset-2d.d.ts +5 -0
  23. package/dist/tileset-2d/tileset-2d.d.ts.map +1 -1
  24. package/dist/tileset-2d/tileset-2d.js +85 -0
  25. package/dist/tileset-2d/tileset-2d.js.map +1 -1
  26. package/dist.min.js +21 -22
  27. package/package.json +14 -10
  28. package/src/mesh-layer/mesh-layer-vertex.glsl.ts +3 -4
  29. package/src/mesh-layer/mesh-layer.ts +7 -17
  30. package/src/terrain-layer/terrain-layer.ts +64 -4
  31. package/src/tileset-2d/tile-2d-header.ts +4 -4
  32. package/src/tileset-2d/tile-2d-traversal.ts +33 -1
  33. package/src/tileset-2d/tileset-2d.ts +101 -1
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "deck.gl layers supporting geospatial use cases and GIS formats",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
- "version": "9.3.7",
6
+ "version": "9.4.0-alpha.2",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -17,6 +17,10 @@
17
17
  "type": "git",
18
18
  "url": "https://github.com/visgl/deck.gl.git"
19
19
  },
20
+ "bugs": {
21
+ "url": "https://github.com/visgl/deck.gl/issues"
22
+ },
23
+ "homepage": "https://deck.gl",
20
24
  "types": "dist/index.d.ts",
21
25
  "main": "dist/index.cjs",
22
26
  "module": "dist/index.js",
@@ -46,8 +50,8 @@
46
50
  "@loaders.gl/terrain": "^4.4.3",
47
51
  "@loaders.gl/tiles": "^4.4.3",
48
52
  "@loaders.gl/wms": "^4.4.3",
49
- "@luma.gl/gltf": "^9.3.3",
50
- "@luma.gl/shadertools": "^9.3.3",
53
+ "@luma.gl/gltf": "^9.4.0-alpha.1",
54
+ "@luma.gl/shadertools": "^9.4.0-alpha.1",
51
55
  "@math.gl/core": "^4.1.0",
52
56
  "@math.gl/culling": "^4.1.0",
53
57
  "@math.gl/web-mercator": "^4.1.0",
@@ -57,13 +61,13 @@
57
61
  "long": "^3.2.0"
58
62
  },
59
63
  "peerDependencies": {
60
- "@deck.gl/core": "~9.3.0",
61
- "@deck.gl/extensions": "~9.3.0",
62
- "@deck.gl/layers": "~9.3.0",
63
- "@deck.gl/mesh-layers": "~9.3.0",
64
+ "@deck.gl/core": "~9.3.0-beta.2",
65
+ "@deck.gl/extensions": "~9.3.0-beta.2",
66
+ "@deck.gl/layers": "~9.3.0-beta.2",
67
+ "@deck.gl/mesh-layers": "~9.3.0-beta.2",
64
68
  "@loaders.gl/core": "^4.4.3",
65
- "@luma.gl/core": "~9.3.3",
66
- "@luma.gl/engine": "~9.3.3"
69
+ "@luma.gl/core": "~9.4.0-alpha.1",
70
+ "@luma.gl/engine": "~9.4.0-alpha.1"
67
71
  },
68
- "gitHead": "ac8cd3dd7a70b1f1471729425d63fcff38dc0a13"
72
+ "gitHead": "546e2e8284b00949ab26e744ffd05b9aa017c802"
69
73
  }
@@ -11,11 +11,10 @@ in vec3 normals;
11
11
  in vec3 colors;
12
12
  in vec2 texCoords;
13
13
  in vec4 uvRegions;
14
- in vec3 featureIdsPickingColors;
14
+ in float rowIndexes;
15
15
 
16
16
  // Instance attributes
17
17
  in vec4 instanceColors;
18
- in vec3 instancePickingColors;
19
18
  in vec3 instanceModelMatrixCol0;
20
19
  in vec3 instanceModelMatrixCol1;
21
20
  in vec3 instanceModelMatrixCol2;
@@ -41,9 +40,9 @@ void main(void) {
41
40
  geometry.uv = uv;
42
41
 
43
42
  if (mesh.pickFeatureIds) {
44
- geometry.pickingColor = featureIdsPickingColors;
43
+ geometry.pickingColor = picking_getPickingColorFromIndex(rowIndexes);
45
44
  } else {
46
- geometry.pickingColor = instancePickingColors;
45
+ geometry.pickingColor = picking_getPickingColorFromInstanceID();
47
46
  }
48
47
 
49
48
  mat3 instanceModelMatrix = mat3(instanceModelMatrixCol0, instanceModelMatrixCol1, instanceModelMatrixCol2);
@@ -75,12 +75,13 @@ export default class MeshLayer<DataT = any, ExtraProps extends {} = {}> extends
75
75
  if (featureIds) {
76
76
  // attributeManager is always defined in a primitive layer
77
77
  attributeManager!.add({
78
- featureIdsPickingColors: {
79
- type: 'uint8',
80
- size: 3,
78
+ /** Feature id for each mesh vertex. */
79
+ rowIndexes: {
80
+ type: 'uint32',
81
+ size: 1,
81
82
  noAlloc: true,
82
83
  // eslint-disable-next-line @typescript-eslint/unbound-method
83
- update: this.calculateFeatureIdsPickingColors
84
+ update: this.calculateFeatureIdsPickingIndexes
84
85
  }
85
86
  });
86
87
  }
@@ -179,21 +180,10 @@ export default class MeshLayer<DataT = any, ExtraProps extends {} = {}> extends
179
180
  );
180
181
  }
181
182
 
182
- calculateFeatureIdsPickingColors(attribute) {
183
+ calculateFeatureIdsPickingIndexes(attribute) {
183
184
  // This updater is only called if featureIds is not null
184
185
  const featureIds = this.props.featureIds!;
185
- const value = new Uint8ClampedArray(featureIds.length * attribute.size);
186
-
187
- const pickingColor = [];
188
- for (let index = 0; index < featureIds.length; index++) {
189
- this.encodePickingColor(featureIds[index], pickingColor);
190
-
191
- value[index * 3] = pickingColor[0];
192
- value[index * 3 + 1] = pickingColor[1];
193
- value[index * 3 + 2] = pickingColor[2];
194
- }
195
-
196
- attribute.value = value;
186
+ attribute.value = new Uint32Array(featureIds);
197
187
  }
198
188
 
199
189
  finalizeState(context: LayerContext) {
@@ -30,6 +30,10 @@ import type {
30
30
  import {getURLFromTemplate, Tile2DHeader, URLTemplate, urlType} from '../tileset-2d/index';
31
31
 
32
32
  const DUMMY_DATA = [1];
33
+ const TILE_OVERLAP_PIXELS = 1;
34
+ const MIN_TERRAIN_MESH_MAX_ERROR = 1;
35
+ const MAX_LATITUDE = 90;
36
+ const MAX_LONGITUDE = 180;
33
37
 
34
38
  const defaultProps: DefaultProps<TerrainLayerProps> = {
35
39
  ...TileLayer.defaultProps,
@@ -70,6 +74,35 @@ function urlTemplateToUpdateTrigger(template: URLTemplate): string {
70
74
  return template || '';
71
75
  }
72
76
 
77
+ function getOverlappedBounds(bounds: Bounds, tileSize: number, clampLngLat: boolean): Bounds {
78
+ const xPad = ((bounds[2] - bounds[0]) / tileSize) * TILE_OVERLAP_PIXELS;
79
+ const yPad = ((bounds[3] - bounds[1]) / tileSize) * TILE_OVERLAP_PIXELS;
80
+ const overlappedBounds: Bounds = [
81
+ bounds[0] - xPad,
82
+ bounds[1] - yPad,
83
+ bounds[2] + xPad,
84
+ bounds[3] + yPad
85
+ ];
86
+
87
+ if (!clampLngLat) {
88
+ return overlappedBounds;
89
+ }
90
+
91
+ return [
92
+ Math.max(overlappedBounds[0], -MAX_LONGITUDE),
93
+ Math.max(overlappedBounds[1], -MAX_LATITUDE),
94
+ Math.min(overlappedBounds[2], MAX_LONGITUDE),
95
+ Math.min(overlappedBounds[3], MAX_LATITUDE)
96
+ ];
97
+ }
98
+
99
+ function getEffectiveMeshMaxError(meshMaxError: number): number {
100
+ if (!Number.isFinite(meshMaxError) || meshMaxError <= 0) {
101
+ return MIN_TERRAIN_MESH_MAX_ERROR;
102
+ }
103
+ return Math.max(meshMaxError, MIN_TERRAIN_MESH_MAX_ERROR);
104
+ }
105
+
73
106
  type ElevationDecoder = {rScaler: number; gScaler: number; bScaler: number; offset: number};
74
107
  type TerrainLoadProps = {
75
108
  bounds: Bounds;
@@ -80,6 +113,12 @@ type TerrainLoadProps = {
80
113
  };
81
114
 
82
115
  type MeshAndTexture = [MeshAttributes | null, TextureSource | null];
116
+ type MeshBoundingBox = [min: number[], max: number[]];
117
+ type MeshWithBoundingBox = MeshAttributes & {
118
+ header?: {
119
+ boundingBox?: MeshBoundingBox;
120
+ };
121
+ };
83
122
 
84
123
  /** All properties supported by TerrainLayer */
85
124
  export type TerrainLayerProps = _TerrainLayerProps &
@@ -170,14 +209,15 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
170
209
  if (!elevationData) {
171
210
  return null;
172
211
  }
212
+ const effectiveMeshMaxError = getEffectiveMeshMaxError(meshMaxError);
173
213
  let loadOptions = this.getLoadOptions();
174
214
  loadOptions = {
175
215
  ...loadOptions,
176
216
  terrain: {
177
- skirtHeight: this.state.isTiled ? meshMaxError * 2 : 0,
217
+ skirtHeight: this.state.isTiled ? effectiveMeshMaxError * 2 : 0,
178
218
  ...loadOptions?.terrain,
179
219
  bounds,
180
- meshMaxError,
220
+ meshMaxError: effectiveMeshMaxError,
181
221
  elevationDecoder
182
222
  }
183
223
  };
@@ -204,10 +244,15 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
204
244
  topRight = [bbox.right, bbox.top];
205
245
  }
206
246
  const bounds: Bounds = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]];
247
+ const overlappedBounds = getOverlappedBounds(
248
+ bounds,
249
+ this.props.tileSize,
250
+ viewport instanceof GlobeViewport
251
+ );
207
252
 
208
253
  const terrain = this.loadTerrain({
209
254
  elevationData: dataUrl,
210
- bounds,
255
+ bounds: overlappedBounds,
211
256
  elevationDecoder,
212
257
  meshMaxError,
213
258
  signal
@@ -238,12 +283,27 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
238
283
 
239
284
  const [mesh, texture] = data;
240
285
 
286
+ const {viewport} = this.context;
287
+ // Bounds are baked with projectFlat. In GlobeView projectFlat is identity,
288
+ // so tiled terrain meshes are in lng/lat degrees instead of common-space
289
+ // web-mercator units.
290
+ const isGlobe = viewport instanceof GlobeViewport;
291
+ const boundingBox = (mesh as MeshWithBoundingBox | null)?.header?.boundingBox;
292
+ const hasLngLatBounds =
293
+ boundingBox &&
294
+ boundingBox.every(
295
+ ([x, y]) =>
296
+ x >= -MAX_LONGITUDE && x <= MAX_LONGITUDE && y >= -MAX_LATITUDE && y <= MAX_LATITUDE
297
+ );
298
+ const coordinateSystem =
299
+ isGlobe && hasLngLatBounds ? COORDINATE_SYSTEM.LNGLAT : COORDINATE_SYSTEM.CARTESIAN;
300
+
241
301
  return new SubLayerClass(props, {
242
302
  data: DUMMY_DATA,
243
303
  mesh,
244
304
  texture,
245
305
  _instanced: false,
246
- coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
306
+ coordinateSystem,
247
307
  getPosition: d => [0, 0, 0],
248
308
  getColor: color,
249
309
  wireframe,
@@ -10,6 +10,7 @@ import type {Layer} from '@deck.gl/core';
10
10
  export type TileLoadDataProps<DataT = any> = {
11
11
  requestScheduler: RequestScheduler;
12
12
  getData: (props: TileLoadProps) => Promise<DataT>;
13
+ getRequestPriority: (tile: Tile2DHeader<DataT>) => number;
13
14
  onLoad: (tile: Tile2DHeader<DataT>) => void;
14
15
  onError: (error: any, tile: Tile2DHeader<DataT>) => void;
15
16
  };
@@ -106,6 +107,7 @@ export class Tile2DHeader<DataT = any> {
106
107
  /* eslint-disable max-statements */
107
108
  private async _loadData({
108
109
  getData,
110
+ getRequestPriority,
109
111
  requestScheduler,
110
112
  onLoad,
111
113
  onError
@@ -116,10 +118,8 @@ export class Tile2DHeader<DataT = any> {
116
118
  this._abortController = new AbortController();
117
119
  const {signal} = this._abortController;
118
120
 
119
- // @ts-expect-error (2345) Argument of type '(tile: any) => 1 | -1' is not assignable ...
120
- const requestToken = await requestScheduler.scheduleRequest(this, tile => {
121
- return tile.isSelected ? 1 : -1;
122
- });
121
+ // @ts-expect-error (2345) loaders.gl's RequestScheduler callback type is too narrow.
122
+ const requestToken = await requestScheduler.scheduleRequest(this, getRequestPriority);
123
123
 
124
124
  if (!requestToken) {
125
125
  this._isCancelled = true;
@@ -91,6 +91,11 @@ class OSMNode {
91
91
  return false;
92
92
  }
93
93
 
94
+ // Globe: reject tiles occluded by the near hemisphere
95
+ if (project && this.beyondHorizon(viewport.cameraPosition, project, elevationBounds[1])) {
96
+ return false;
97
+ }
98
+
94
99
  // Avoid loading overlapping tiles - if a descendant is requested, do not request the ancester
95
100
  if (!this.childVisible) {
96
101
  let {z} = this;
@@ -129,6 +134,33 @@ class OSMNode {
129
134
  return result;
130
135
  }
131
136
 
137
+ beyondHorizon(
138
+ cameraPosition: number[],
139
+ project: (xyz: number[]) => number[],
140
+ elevation: number
141
+ ): boolean {
142
+ const cx = cameraPosition[0];
143
+ const cy = cameraPosition[1];
144
+ const cz = cameraPosition[2];
145
+ const cMag = Math.sqrt(cx * cx + cy * cy + cz * cz);
146
+
147
+ const camLng = (Math.atan2(cx, -cy) * 180) / Math.PI;
148
+ const camLat = (Math.asin(cz / cMag) * 180) / Math.PI;
149
+
150
+ const [west, north] = osmTile2lngLat(this.x, this.y, this.z);
151
+ const [east, south] = osmTile2lngLat(this.x + 1, this.y + 1, this.z);
152
+
153
+ const tileCenterLng = (west + east) / 2;
154
+ const wrappedCamLng = tileCenterLng + (((camLng - tileCenterLng + 540) % 360) - 180);
155
+ const closestLng = Math.max(west, Math.min(wrappedCamLng, east));
156
+ const closestLat = Math.max(south, Math.min(camLat, north));
157
+
158
+ const closest = project([closestLng, closestLat, elevation]);
159
+ const dot = closest[0] * cx + closest[1] * cy + closest[2] * cz;
160
+ const magSq = closest[0] * closest[0] + closest[1] * closest[1] + closest[2] * closest[2];
161
+ return dot <= magSq;
162
+ }
163
+
132
164
  insideBounds([minX, minY, maxX, maxY]: Bounds): boolean {
133
165
  const scale = Math.pow(2, this.z);
134
166
  const extent = TILE_SIZE / scale;
@@ -191,7 +223,7 @@ export function getOSMTileIndices(
191
223
  bounds?: Bounds
192
224
  ): TileIndex[] {
193
225
  const project: ((xyz: number[]) => number[]) | null =
194
- viewport instanceof _GlobeViewport && viewport.resolution
226
+ viewport instanceof _GlobeViewport
195
227
  ? // eslint-disable-next-line @typescript-eslint/unbound-method
196
228
  viewport.projectPosition
197
229
  : null;
@@ -10,7 +10,7 @@ import {Matrix4, equals, NumericArray} from '@math.gl/core';
10
10
  import {Tile2DHeader} from './tile-2d-header';
11
11
 
12
12
  import {getTileIndices, tileToBoundingBox, getCullBounds, transformBox} from './utils';
13
- import {Bounds, TileIndex, ZRange} from './types';
13
+ import {Bounds, TileBoundingBox, TileIndex, ZRange} from './types';
14
14
  import {TileLoadProps} from './types';
15
15
  import {memoize} from './memoize';
16
16
 
@@ -48,6 +48,9 @@ export type RefinementStrategy =
48
48
  | RefinementStrategyFunction;
49
49
 
50
50
  const DEFAULT_CACHE_SCALE = 5;
51
+ const SELECTED_TILE_PRIORITY = 0;
52
+ const VISIBLE_TILE_PRIORITY = 1e8;
53
+ const MAX_TILE_DISTANCE_PRIORITY = VISIBLE_TILE_PRIORITY - SELECTED_TILE_PRIORITY - 1;
51
54
 
52
55
  const STRATEGIES = {
53
56
  [STRATEGY_DEFAULT]: updateTileStateDefault,
@@ -437,6 +440,102 @@ export class Tileset2D {
437
440
 
438
441
  private _getCullBounds = memoize(getCullBounds);
439
442
 
443
+ private _getRequestPriority(tile: Tile2DHeader): number {
444
+ if (!tile.isSelected && !tile.isVisible) {
445
+ return -1;
446
+ }
447
+
448
+ // RequestScheduler loads lower priority values first.
449
+ const distance = this._getTileDistancePriority(tile);
450
+ if (tile.isSelected) {
451
+ return SELECTED_TILE_PRIORITY + distance;
452
+ }
453
+ return VISIBLE_TILE_PRIORITY + distance;
454
+ }
455
+
456
+ private _getTileDistancePriority(tile: Tile2DHeader): number {
457
+ const {width, height} = this._viewport || {};
458
+ if (!this._viewport || !width || !height) {
459
+ return 0;
460
+ }
461
+
462
+ try {
463
+ const points = this._getTileScreenCorners(tile.bbox);
464
+ const center: [number, number] = [width / 2, height / 2];
465
+ if (points.length === 4) {
466
+ if (this._isPointInPolygon(center, points)) {
467
+ return 0;
468
+ }
469
+ const distance = points.reduce((minDistance, point, i) => {
470
+ const nextPoint = points[(i + 1) % points.length];
471
+ return Math.min(
472
+ minDistance,
473
+ this._getPointToSegmentDistanceSquared(center, point, nextPoint)
474
+ );
475
+ }, Number.MAX_SAFE_INTEGER);
476
+ return Math.min(distance, MAX_TILE_DISTANCE_PRIORITY);
477
+ }
478
+ } catch {
479
+ // Some viewport/tile combinations are not projectable. Keep them valid but last in tier.
480
+ }
481
+ return MAX_TILE_DISTANCE_PRIORITY;
482
+ }
483
+
484
+ private _getTileScreenCorners(bbox: TileBoundingBox): [number, number][] {
485
+ const coordinates: [number, number][] =
486
+ 'west' in bbox
487
+ ? [
488
+ [bbox.west, bbox.south],
489
+ [bbox.east, bbox.south],
490
+ [bbox.east, bbox.north],
491
+ [bbox.west, bbox.north]
492
+ ]
493
+ : [
494
+ [bbox.left, bbox.top],
495
+ [bbox.right, bbox.top],
496
+ [bbox.right, bbox.bottom],
497
+ [bbox.left, bbox.bottom]
498
+ ];
499
+
500
+ return coordinates
501
+ .map(coordinate => this._viewport!.project(coordinate))
502
+ .filter(([x, y]) => Number.isFinite(x) && Number.isFinite(y)) as [number, number][];
503
+ }
504
+
505
+ private _isPointInPolygon(point: [number, number], polygon: [number, number][]): boolean {
506
+ let inside = false;
507
+ const [x, y] = point;
508
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
509
+ const [xi, yi] = polygon[i];
510
+ const [xj, yj] = polygon[j];
511
+ if (yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi) {
512
+ inside = !inside;
513
+ }
514
+ }
515
+ return inside;
516
+ }
517
+
518
+ private _getPointToSegmentDistanceSquared(
519
+ point: [number, number],
520
+ segmentStart: [number, number],
521
+ segmentEnd: [number, number]
522
+ ): number {
523
+ const [x, y] = point;
524
+ const [x1, y1] = segmentStart;
525
+ const [x2, y2] = segmentEnd;
526
+ const dx = x2 - x1;
527
+ const dy = y2 - y1;
528
+ const lengthSquared = dx * dx + dy * dy;
529
+ const t = lengthSquared
530
+ ? Math.max(0, Math.min(1, ((x - x1) * dx + (y - y1) * dy) / lengthSquared))
531
+ : 0;
532
+ const segmentX = x1 + t * dx;
533
+ const segmentY = y1 + t * dy;
534
+ const distanceX = x - segmentX;
535
+ const distanceY = y - segmentY;
536
+ return distanceX * distanceX + distanceY * distanceY;
537
+ }
538
+
440
539
  private _pruneRequests(): void {
441
540
  const {maxRequests = 0} = this.opts;
442
541
 
@@ -542,6 +641,7 @@ export class Tileset2D {
542
641
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
543
642
  tile.loadData({
544
643
  getData: this.opts.getTileData,
644
+ getRequestPriority: this._getRequestPriority.bind(this),
545
645
  requestScheduler: this._requestScheduler,
546
646
  onLoad: this.onTileLoad,
547
647
  onError: this.opts.onTileError