@vcmap/core 6.3.0-rc.2 → 6.3.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 (82) hide show
  1. package/README.md +24 -23
  2. package/build/postinstall.js +16 -2
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.js +2 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/ol.d.ts +10 -1
  7. package/dist/src/featureProvider/abstractFeatureProvider.d.ts +8 -0
  8. package/dist/src/featureProvider/abstractFeatureProvider.js +10 -0
  9. package/dist/src/featureProvider/abstractFeatureProvider.js.map +1 -1
  10. package/dist/src/featureProvider/jsonAttributeProvider.d.ts +2 -1
  11. package/dist/src/featureProvider/jsonAttributeProvider.js +5 -2
  12. package/dist/src/featureProvider/jsonAttributeProvider.js.map +1 -1
  13. package/dist/src/featureProvider/mapboxFeatureProvider.d.ts +18 -0
  14. package/dist/src/featureProvider/mapboxFeatureProvider.js +49 -0
  15. package/dist/src/featureProvider/mapboxFeatureProvider.js.map +1 -0
  16. package/dist/src/featureProvider/wmsFeatureProvider.d.ts +11 -0
  17. package/dist/src/featureProvider/wmsFeatureProvider.js +19 -8
  18. package/dist/src/featureProvider/wmsFeatureProvider.js.map +1 -1
  19. package/dist/src/interaction/eventHandler.js +3 -1
  20. package/dist/src/interaction/eventHandler.js.map +1 -1
  21. package/dist/src/interaction/featureAtPixelInteraction.js +5 -0
  22. package/dist/src/interaction/featureAtPixelInteraction.js.map +1 -1
  23. package/dist/src/interaction/featureProviderInteraction.d.ts +10 -1
  24. package/dist/src/interaction/featureProviderInteraction.js +19 -3
  25. package/dist/src/interaction/featureProviderInteraction.js.map +1 -1
  26. package/dist/src/layer/cesium/mapboxStyleCesiumImpl.d.ts +25 -0
  27. package/dist/src/layer/cesium/mapboxStyleCesiumImpl.js +58 -0
  28. package/dist/src/layer/cesium/mapboxStyleCesiumImpl.js.map +1 -0
  29. package/dist/src/layer/cesium/mapboxStyleImageryProvider.d.ts +26 -0
  30. package/dist/src/layer/cesium/mapboxStyleImageryProvider.js +147 -0
  31. package/dist/src/layer/cesium/mapboxStyleImageryProvider.js.map +1 -0
  32. package/dist/src/layer/cesium/vectorTileImageryProvider.d.ts +1 -1
  33. package/dist/src/layer/cesium/vectorTileImageryProvider.js.map +1 -1
  34. package/dist/src/layer/i3sLayer.d.ts +2 -10
  35. package/dist/src/layer/i3sLayer.js +9 -8
  36. package/dist/src/layer/i3sLayer.js.map +1 -1
  37. package/dist/src/layer/mapboxStyleLayer.d.ts +55 -0
  38. package/dist/src/layer/mapboxStyleLayer.js +176 -0
  39. package/dist/src/layer/mapboxStyleLayer.js.map +1 -0
  40. package/dist/src/layer/openlayers/layerOpenlayersImpl.d.ts +3 -4
  41. package/dist/src/layer/openlayers/layerOpenlayersImpl.js +11 -3
  42. package/dist/src/layer/openlayers/layerOpenlayersImpl.js.map +1 -1
  43. package/dist/src/layer/openlayers/mapboxStyleOpenlayersImpl.d.ts +15 -0
  44. package/dist/src/layer/openlayers/mapboxStyleOpenlayersImpl.js +49 -0
  45. package/dist/src/layer/openlayers/mapboxStyleOpenlayersImpl.js.map +1 -0
  46. package/dist/src/map/baseCesiumMap.d.ts +17 -0
  47. package/dist/src/map/baseCesiumMap.js +36 -0
  48. package/dist/src/map/baseCesiumMap.js.map +1 -1
  49. package/dist/src/map/baseOLMap.d.ts +6 -4
  50. package/dist/src/map/baseOLMap.js +3 -2
  51. package/dist/src/map/baseOLMap.js.map +1 -1
  52. package/dist/src/map/cesiumMap.d.ts +3 -3
  53. package/dist/src/map/cesiumMap.js +2 -3
  54. package/dist/src/map/cesiumMap.js.map +1 -1
  55. package/dist/src/map/panoramaMap.d.ts +2 -2
  56. package/dist/src/map/panoramaMap.js +1 -2
  57. package/dist/src/map/panoramaMap.js.map +1 -1
  58. package/dist/src/map/vcsMap.d.ts +3 -3
  59. package/dist/src/map/vcsMap.js +11 -3
  60. package/dist/src/map/vcsMap.js.map +1 -1
  61. package/index.ts +2 -0
  62. package/package.json +4 -3
  63. package/src/featureProvider/abstractFeatureProvider.ts +12 -0
  64. package/src/featureProvider/jsonAttributeProvider.ts +10 -2
  65. package/src/featureProvider/mapboxFeatureProvider.ts +74 -0
  66. package/src/featureProvider/wmsFeatureProvider.ts +29 -8
  67. package/src/interaction/eventHandler.ts +3 -1
  68. package/src/interaction/featureAtPixelInteraction.ts +5 -0
  69. package/src/interaction/featureProviderInteraction.ts +37 -8
  70. package/src/layer/cesium/mapboxStyleCesiumImpl.ts +95 -0
  71. package/src/layer/cesium/mapboxStyleImageryProvider.ts +214 -0
  72. package/src/layer/cesium/vectorTileImageryProvider.ts +2 -1
  73. package/src/layer/i3sLayer.ts +12 -14
  74. package/src/layer/mapboxStyleLayer.ts +244 -0
  75. package/src/layer/openlayers/layerOpenlayersImpl.ts +21 -10
  76. package/src/layer/openlayers/mapboxStyleOpenlayersImpl.ts +68 -0
  77. package/src/map/baseCesiumMap.ts +57 -0
  78. package/src/map/baseOLMap.ts +16 -14
  79. package/src/map/cesiumMap.ts +7 -6
  80. package/src/map/panoramaMap.ts +3 -3
  81. package/src/map/vcsMap.ts +16 -10
  82. package/src/ol/ol.d.ts +10 -1
@@ -0,0 +1,74 @@
1
+ import { v4 as uuid } from 'uuid';
2
+ import OLMap from 'ol/Map.js';
3
+ import type Feature from 'ol/Feature.js';
4
+ import type { Coordinate } from 'ol/coordinate.js';
5
+ import type LayerGroup from 'ol/layer/Group.js';
6
+ import type RenderFeature from 'ol/render/Feature.js';
7
+ import { toFeature } from 'ol/render/Feature.js';
8
+ import type Layer from '../layer/layer.js';
9
+ import { vcsLayerName } from '../layer/layerSymbols.js';
10
+ import AbstractFeatureProvider, {
11
+ type AbstractFeatureProviderOptions,
12
+ } from './abstractFeatureProvider.js';
13
+ import { isProvidedFeature } from './featureProviderSymbols.js';
14
+
15
+ export type MapboxFeatureProviderOptions = AbstractFeatureProviderOptions & {
16
+ styledMapboxLayerGroup: LayerGroup;
17
+ excludeLayerFromPicking?: string[];
18
+ };
19
+
20
+ export default class MapboxFeatureProvider extends AbstractFeatureProvider {
21
+ static get className(): string {
22
+ return 'MapboxFeatureProvider';
23
+ }
24
+
25
+ protected _inRenderingOrder = true;
26
+
27
+ private _renderMap = new OLMap({ target: document.createElement('div') });
28
+ private _excludeLayerFromPicking?: string[];
29
+
30
+ constructor(options: MapboxFeatureProviderOptions) {
31
+ super(options);
32
+
33
+ this._renderMap.setSize([256, 256]);
34
+ this._renderMap.addLayer(options.styledMapboxLayerGroup);
35
+ this._excludeLayerFromPicking = options.excludeLayerFromPicking;
36
+ }
37
+
38
+ getFeaturesByCoordinate(
39
+ coordinate: Coordinate,
40
+ resolution: number,
41
+ layer: Layer,
42
+ ): Promise<Feature[]> {
43
+ const view = this._renderMap.getView();
44
+ view.setCenter(coordinate);
45
+ view.setResolution(resolution);
46
+
47
+ this._renderMap.renderSync();
48
+
49
+ const featuresAtPixel = this._renderMap.getFeaturesAtPixel([
50
+ 128, 128,
51
+ ]) as RenderFeature[];
52
+ const features = featuresAtPixel
53
+ .filter((rf) => {
54
+ const layerSource = rf.get('mvt:layer') as string;
55
+ return !this._excludeLayerFromPicking?.includes(layerSource);
56
+ })
57
+ .map((f) => toFeature(f));
58
+
59
+ features.forEach((f) => {
60
+ f[vcsLayerName] = layer.name;
61
+ f[isProvidedFeature] = true;
62
+ if (!f.getId()) {
63
+ f.setId(uuid());
64
+ }
65
+ });
66
+ return Promise.resolve(features);
67
+ }
68
+
69
+ destroy(): void {
70
+ this._renderMap.setTarget(undefined);
71
+ this._renderMap.dispose();
72
+ super.destroy();
73
+ }
74
+ }
@@ -83,6 +83,11 @@ export type WMSFeatureProviderOptions = AbstractFeatureProviderOptions & {
83
83
  * Optional headers to include in GetFeatureInfo requests. Overrides layer headers.
84
84
  */
85
85
  headers?: Record<string, string>;
86
+
87
+ /**
88
+ * Optional RegExp string to evaluate text/html responses. If it matches the response, it will be treated as an empty response with no features.
89
+ */
90
+ textHTMLEvaluator?: string;
86
91
  };
87
92
 
88
93
  const gmlFormats = { GML: GML3, GML2, GML3, GML32 };
@@ -165,7 +170,7 @@ export function getFormat(
165
170
  * @param coordinate - Coordinate in geographic projection
166
171
  * @returns Meters per degree at the coordinate's latitude
167
172
  */
168
- function getMetersPerDegreeAtCoordinate(coordinate: Coordinate): number {
173
+ export function getMetersPerDegreeAtCoordinate(coordinate: Coordinate): number {
169
174
  const latitude = coordinate[1];
170
175
  const latitudeRadians = (latitude * Math.PI) / 180;
171
176
 
@@ -197,6 +202,7 @@ class WMSFeatureProvider extends AbstractFeatureProvider {
197
202
  parameters: {},
198
203
  extent: undefined,
199
204
  headers: undefined,
205
+ textHTMLEvaluator: undefined,
200
206
  };
201
207
  }
202
208
 
@@ -244,6 +250,8 @@ class WMSFeatureProvider extends AbstractFeatureProvider {
244
250
  */
245
251
  headers?: Record<string, string>;
246
252
 
253
+ private _textHTMLEvaluator: string | undefined;
254
+
247
255
  constructor(options: WMSFeatureProviderOptions) {
248
256
  const defaultOptions = WMSFeatureProvider.getDefaultOptions();
249
257
  super({ ...defaultOptions, ...options });
@@ -285,6 +293,9 @@ class WMSFeatureProvider extends AbstractFeatureProvider {
285
293
  this.headers = options.headers
286
294
  ? structuredClone(options.headers)
287
295
  : undefined;
296
+ if (options.textHTMLEvaluator) {
297
+ this._textHTMLEvaluator = options.textHTMLEvaluator;
298
+ }
288
299
  }
289
300
 
290
301
  get wmsSource(): TileWMS {
@@ -350,13 +361,10 @@ class WMSFeatureProvider extends AbstractFeatureProvider {
350
361
  return [];
351
362
  }
352
363
 
353
- const projection = this.wmsSource.getProjection() as OLProjection;
354
- let coords = coordinate;
355
364
  let res = resolution;
356
- if (projection) {
357
- const transform = getTransform(mercatorProjection.proj, projection);
358
- coords = transform(coordinate.slice());
359
- }
365
+ const projection = this.wmsSource.getProjection() as OLProjection;
366
+ const transform = getTransform(mercatorProjection.proj, projection);
367
+ const coords = transform(coordinate.slice());
360
368
  if (projection.getUnits() === 'degrees') {
361
369
  const metersPerDegree = getMetersPerDegreeAtCoordinate(coords);
362
370
  res = resolution / metersPerDegree;
@@ -366,7 +374,10 @@ class WMSFeatureProvider extends AbstractFeatureProvider {
366
374
  INFO_FORMAT: this.featureInfoResponseType,
367
375
  });
368
376
 
369
- if (this.featureInfoResponseType === 'text/html') {
377
+ if (
378
+ this.featureInfoResponseType === 'text/html' &&
379
+ !this._textHTMLEvaluator
380
+ ) {
370
381
  return this.featureResponseCallback(null, coordinate).map((f) =>
371
382
  this.getProviderFeature(f, layer),
372
383
  );
@@ -380,6 +391,13 @@ class WMSFeatureProvider extends AbstractFeatureProvider {
380
391
  this.getLogger().error(`Failed fetching WMS FeatureInfo ${url}`);
381
392
  return [];
382
393
  }
394
+ if (
395
+ this.featureInfoResponseType === 'text/html' &&
396
+ this._textHTMLEvaluator &&
397
+ RegExp(this._textHTMLEvaluator).test(data)
398
+ ) {
399
+ return [];
400
+ }
383
401
  return this.featureResponseCallback(data, coordinate).map((f) =>
384
402
  this.getProviderFeature(f, layer),
385
403
  );
@@ -444,6 +462,9 @@ class WMSFeatureProvider extends AbstractFeatureProvider {
444
462
  if (this.headers) {
445
463
  config.headers = structuredClone(this.headers);
446
464
  }
465
+ if (this._textHTMLEvaluator) {
466
+ config.textHTMLEvaluator = this._textHTMLEvaluator;
467
+ }
447
468
 
448
469
  return config as WMSFeatureProviderOptions;
449
470
  }
@@ -76,7 +76,9 @@ class EventHandler {
76
76
  this._positionInteraction = new CoordinateAtPixel();
77
77
  this._featureInteraction = new FeatureAtPixelInteraction();
78
78
  this._ensurePositionInteraction = new EnsurePositionInteraction();
79
- this._featureProviderInteraction = new FeatureProviderInteraction();
79
+ this._featureProviderInteraction = new FeatureProviderInteraction({
80
+ respectRenderingOrder: true,
81
+ });
80
82
  this._interactionChain = new InteractionChain([
81
83
  this._positionInteraction,
82
84
  this._featureInteraction,
@@ -15,6 +15,7 @@ import {
15
15
  } from '@vcmap-cesium/engine';
16
16
  import type OLMap from 'ol/Map.js';
17
17
  import Feature from 'ol/Feature.js';
18
+ import RenderFeature from 'ol/render/Feature.js';
18
19
  import { Point } from 'ol/geom.js';
19
20
  import { v4 as uuid } from 'uuid';
20
21
  import AbstractInteraction, {
@@ -67,6 +68,10 @@ function getFeatureFromOlMap(
67
68
  pixel,
68
69
  (feat) => {
69
70
  let candidateFeature = feat;
71
+ if (candidateFeature instanceof RenderFeature) {
72
+ // Mapbox Feature, need to go through FeatureProvider
73
+ return true;
74
+ }
70
75
  if (
71
76
  candidateFeature &&
72
77
  (candidateFeature.get('olcs_allowPicking') == null ||
@@ -1,3 +1,4 @@
1
+ import { parseBoolean } from '@vcsuite/parsers';
1
2
  import type { Coordinate } from 'ol/coordinate.js';
2
3
  import Point from 'ol/geom/Point.js';
3
4
  import Feature from 'ol/Feature.js';
@@ -19,17 +20,38 @@ import { vcsLayerName } from '../layer/layerSymbols.js';
19
20
  import CompositeFeatureProvider from '../featureProvider/compositeFeatureProvider.js';
20
21
  import AbstractAttributeProvider from '../featureProvider/abstractAttributeProvider.js';
21
22
 
23
+ type FeatureProviderInteractionOptions = {
24
+ /**
25
+ * Whether to respect the rendering order of features. Defaults to false, but when set to true and `inRenderingOrder` is set to true on the FeatureProvider as well, only the first feature will be returned.
26
+ * Used for MapboxFeatureProvider, to return only the top most feature on picking (respectRenderingOrder set to true in EventHandler), but return all the features for deep picking (respectRenderingOrder set to false in DeepPicking action)
27
+ */
28
+ respectRenderingOrder?: boolean;
29
+ };
30
+
22
31
  /**
23
32
  * @group Interaction
24
33
  */
25
34
  class FeatureProviderInteraction extends AbstractInteraction {
26
- constructor() {
35
+ static getDefaultOptions(): FeatureProviderInteractionOptions {
36
+ return {
37
+ respectRenderingOrder: false,
38
+ };
39
+ }
40
+
41
+ private _respectRenderingOrder: boolean;
42
+ constructor(options?: FeatureProviderInteractionOptions) {
27
43
  super(EventType.CLICK, ModificationKeyType.ALL, PointerKeyType.ALL);
28
44
 
45
+ const defaultOptions = FeatureProviderInteraction.getDefaultOptions();
46
+
47
+ this._respectRenderingOrder = parseBoolean(
48
+ options?.respectRenderingOrder,
49
+ defaultOptions.respectRenderingOrder,
50
+ );
51
+
29
52
  this.setActive();
30
53
  }
31
54
 
32
- // eslint-disable-next-line class-methods-use-this
33
55
  async pipe(event: InteractionEvent): Promise<InteractionEvent> {
34
56
  if (!event.feature) {
35
57
  const layersWithProvider = [...event.map.layerCollection]
@@ -50,15 +72,22 @@ class FeatureProviderInteraction extends AbstractInteraction {
50
72
  // TODO make sure the layers are rendered, check min/max RenderingResolution
51
73
  const features = (
52
74
  await Promise.all(
53
- layersWithProvider.map((l) =>
54
- (
55
- l.featureProvider as AbstractFeatureProvider
56
- ).getFeaturesByCoordinate?.(
75
+ layersWithProvider.map(async (l) => {
76
+ const featureProvider =
77
+ l.featureProvider as AbstractFeatureProvider;
78
+ const f = await featureProvider.getFeaturesByCoordinate?.(
57
79
  event.position as Coordinate,
58
80
  resolution,
59
81
  l,
60
- ),
61
- ),
82
+ );
83
+ if (
84
+ this._respectRenderingOrder &&
85
+ featureProvider.inRenderingOrder
86
+ ) {
87
+ return f.slice(0, 1);
88
+ }
89
+ return f;
90
+ }),
62
91
  )
63
92
  )
64
93
  .filter((f) => !!f)
@@ -0,0 +1,95 @@
1
+ import {
2
+ ImageryLayer as CesiumImageryLayer,
3
+ Rectangle,
4
+ } from '@vcmap-cesium/engine';
5
+ import type { SplitDirection } from '@vcmap-cesium/engine';
6
+ import type LayerGroup from 'ol/layer/Group.js';
7
+ import type BaseCesiumMap from '../../map/baseCesiumMap.js';
8
+ import { wgs84Projection } from '../../util/projection.js';
9
+ import { TilingScheme } from '../rasterLayer.js';
10
+ import type { LayerImplementationOptions } from '../layer.js';
11
+ import TileProvider from '../tileProvider/tileProvider.js';
12
+ import MapboxStyleImageryProvider, {
13
+ type MapboxStyleImageryProviderOptions,
14
+ } from './mapboxStyleImageryProvider.js';
15
+ import RasterLayerCesiumImpl from './rasterLayerCesiumImpl.js';
16
+
17
+ export type MapboxStyleLayerImplementationOptions =
18
+ LayerImplementationOptions & {
19
+ styledMapboxLayerGroup: LayerGroup;
20
+ splitDirection: SplitDirection;
21
+ minRenderingLevel?: number;
22
+ maxRenderingLevel?: number;
23
+ };
24
+
25
+ class MapboxStyleCesiumImpl extends RasterLayerCesiumImpl {
26
+ static get className(): string {
27
+ return 'MapboxStyleCesiumImpl';
28
+ }
29
+
30
+ private _styledMapboxLayerGroup: LayerGroup;
31
+
32
+ minRenderingLevel: number | undefined;
33
+
34
+ maxRenderingLevel: number | undefined;
35
+
36
+ imageryProvider: MapboxStyleImageryProvider | undefined = undefined;
37
+
38
+ constructor(
39
+ map: BaseCesiumMap,
40
+ options: MapboxStyleLayerImplementationOptions,
41
+ ) {
42
+ super(map, {
43
+ ...options,
44
+ minLevel: 0,
45
+ maxLevel: 25,
46
+ tilingSchema: TilingScheme.MERCATOR,
47
+ opacity: 1,
48
+ });
49
+ this._styledMapboxLayerGroup = options.styledMapboxLayerGroup;
50
+ this.minRenderingLevel = options.minRenderingLevel;
51
+ this.maxRenderingLevel = options.maxRenderingLevel;
52
+ }
53
+
54
+ async activate(): Promise<void> {
55
+ await super.activate();
56
+ if (this.active) {
57
+ this._styledMapboxLayerGroup.setVisible(true);
58
+ }
59
+ }
60
+
61
+ getCesiumLayer(): Promise<CesiumImageryLayer> {
62
+ const options: MapboxStyleImageryProviderOptions = {
63
+ headers: this.headers,
64
+ styledMapboxLayerGroup: this._styledMapboxLayerGroup,
65
+ minimumTerrainLevel: this.minRenderingLevel,
66
+ maximumTerrainLevel: this.maxRenderingLevel,
67
+ tileProvider: new TileProvider({}),
68
+ tileSize: [256, 256],
69
+ };
70
+
71
+ this.imageryProvider = new MapboxStyleImageryProvider(options);
72
+
73
+ const layerOptions = this.getCesiumLayerOptions();
74
+ if (this.extent && this.extent.isValid()) {
75
+ const extent = this.extent.getCoordinatesInProjection(wgs84Projection);
76
+ layerOptions.rectangle = Rectangle.fromDegrees(
77
+ extent[0],
78
+ extent[1],
79
+ extent[2],
80
+ extent[3],
81
+ );
82
+ }
83
+ return Promise.resolve(
84
+ // @ts-expect-error mistyped
85
+ new CesiumImageryLayer(this.imageryProvider, layerOptions),
86
+ );
87
+ }
88
+
89
+ destroy(): void {
90
+ this.imageryProvider?.destroy();
91
+ super.destroy();
92
+ }
93
+ }
94
+
95
+ export default MapboxStyleCesiumImpl;
@@ -0,0 +1,214 @@
1
+ import { parseInteger } from '@vcsuite/parsers';
2
+ import { getLogger } from '@vcsuite/logger';
3
+ import type LayerGroup from 'ol/layer/Group.js';
4
+ import { getCenter, buffer } from 'ol/extent.js';
5
+ import LRUCache from 'ol/structs/LRUCache.js';
6
+ import OLMap from 'ol/Map.js';
7
+ import { rectangleToMercatorExtent } from '../../util/math.js';
8
+ import VectorTileImageryProvider from './vectorTileImageryProvider.js';
9
+ import type { VectorTileImageryProviderOptions } from './vectorTileImageryProvider.js';
10
+
11
+ export type MapboxStyleImageryProviderOptions =
12
+ VectorTileImageryProviderOptions & {
13
+ styledMapboxLayerGroup: LayerGroup;
14
+ minimumTerrainLevel?: number;
15
+ maximumTerrainLevel?: number;
16
+ tileCacheSize?: number;
17
+ };
18
+
19
+ function getTileCacheKey(x: number, y: number, level: number): string {
20
+ return `${x}-${y}-${level}`;
21
+ }
22
+
23
+ function getGutterSize(level: number): number {
24
+ if (level < 12) {
25
+ return 32;
26
+ }
27
+ if (level < 15) {
28
+ return 48;
29
+ }
30
+ return 64;
31
+ }
32
+
33
+ /**
34
+ * Implementation of Cesium ImageryProvider Interface for Mapbox Style Tiles
35
+ */
36
+ class MapboxStyleImageryProvider extends VectorTileImageryProvider {
37
+ static get className(): string {
38
+ return 'MapboxStyleImageryProvider';
39
+ }
40
+
41
+ private _tileCacheByLevel: Map<number, LRUCache<HTMLCanvasElement>>;
42
+
43
+ private _maxCacheSize: number;
44
+
45
+ private _isRendering = false;
46
+
47
+ private _renderMap = new OLMap({
48
+ target: document.createElement('div'),
49
+ });
50
+
51
+ constructor(options: MapboxStyleImageryProviderOptions) {
52
+ super(options);
53
+
54
+ this._tileCacheByLevel = new Map();
55
+ this._maxCacheSize = parseInteger(options.tileCacheSize, 100);
56
+ this._renderMap.addLayer(options.styledMapboxLayerGroup);
57
+ }
58
+
59
+ requestImage(
60
+ x: number,
61
+ y: number,
62
+ level: number,
63
+ ): Promise<HTMLImageElement | HTMLCanvasElement> | undefined {
64
+ const cacheKey = getTileCacheKey(x, y, level);
65
+ const levelCache = this._getLevelCache(level);
66
+ if (levelCache.containsKey(cacheKey)) {
67
+ return Promise.resolve(levelCache.get(cacheKey));
68
+ }
69
+ if (this._isRendering) {
70
+ return undefined;
71
+ }
72
+ return this._doRequestImage(x, y, level, cacheKey);
73
+ }
74
+
75
+ private async _doRequestImage(
76
+ x: number,
77
+ y: number,
78
+ level: number,
79
+ cacheKey: string,
80
+ ): Promise<HTMLImageElement | HTMLCanvasElement> {
81
+ const { tilingScheme } = this.tileProvider;
82
+ const rectangle = tilingScheme.tileXYToRectangle(x, y, level);
83
+ const extent = rectangleToMercatorExtent(rectangle);
84
+
85
+ // Add gutters to prevent labels from being cut off
86
+ const gutterSize = getGutterSize(level);
87
+ const gutter = ((extent[2] - extent[0]) / this.tileWidth) * gutterSize;
88
+ const expandedExtent = buffer(extent, gutter);
89
+
90
+ const renderedTile = await this._renderTile(
91
+ level,
92
+ expandedExtent,
93
+ gutterSize,
94
+ );
95
+ const levelCache = this._getLevelCache(level);
96
+ levelCache.set(cacheKey, renderedTile);
97
+ levelCache.expireCache();
98
+ return renderedTile;
99
+ }
100
+
101
+ private _getLevelCache(level: number): LRUCache<HTMLCanvasElement> {
102
+ const existing = this._tileCacheByLevel.get(level);
103
+ if (existing) {
104
+ return existing;
105
+ }
106
+ const cache = new LRUCache<HTMLCanvasElement>(this._maxCacheSize);
107
+ this._tileCacheByLevel.set(level, cache);
108
+ return cache;
109
+ }
110
+
111
+ private _renderTile(
112
+ level: number,
113
+ extent: number[],
114
+ gutterSize: number,
115
+ ): Promise<HTMLCanvasElement> {
116
+ const TIMEOUT_MS = 2500;
117
+ return new Promise((resolve) => {
118
+ this._isRendering = true;
119
+ let isFinished = false;
120
+ const finish = (canvas: HTMLCanvasElement): void => {
121
+ if (isFinished) {
122
+ return;
123
+ }
124
+ isFinished = true;
125
+ this._isRendering = false;
126
+ resolve(canvas);
127
+ };
128
+
129
+ try {
130
+ if (!this._renderMap) {
131
+ finish(this.emptyCanvas);
132
+ return;
133
+ }
134
+
135
+ const renderWidth = this.tileWidth + 2 * gutterSize;
136
+ const renderHeight = this.tileHeight + 2 * gutterSize;
137
+
138
+ const view = this._renderMap.getView();
139
+ view.setCenter(getCenter(extent));
140
+ view.setZoom(level);
141
+ this._renderMap.setSize([renderWidth, renderHeight]);
142
+
143
+ const handleRenderComplete = (): void => {
144
+ this._renderMap?.un('rendercomplete', handleRenderComplete);
145
+
146
+ const renderedCanvas = this._renderMap
147
+ ?.getViewport()
148
+ .querySelector('canvas');
149
+
150
+ if (!renderedCanvas) {
151
+ finish(this.emptyCanvas);
152
+ return;
153
+ }
154
+
155
+ // Crop the canvas to extract the center portion (original tile size)
156
+ const tileCanvas = document.createElement('canvas');
157
+ tileCanvas.width = this.tileWidth;
158
+ tileCanvas.height = this.tileHeight;
159
+ const ctx = tileCanvas.getContext('2d');
160
+ if (ctx) {
161
+ const scaleX = renderedCanvas.width / renderWidth;
162
+ const scaleY = renderedCanvas.height / renderHeight;
163
+ ctx.drawImage(
164
+ renderedCanvas,
165
+ gutterSize * scaleX,
166
+ gutterSize * scaleY,
167
+ this.tileWidth * scaleX,
168
+ this.tileHeight * scaleY,
169
+ 0,
170
+ 0,
171
+ this.tileWidth,
172
+ this.tileHeight,
173
+ );
174
+ }
175
+
176
+ finish(tileCanvas);
177
+ };
178
+
179
+ const timeoutId = window.setTimeout(() => {
180
+ this._renderMap?.un('rendercomplete', handleRenderComplete);
181
+ getLogger('MapboxVectorTileImageryProvider').warning(
182
+ 'Tile render timed out; returning empty tile.',
183
+ );
184
+ finish(this.emptyCanvas);
185
+ }, TIMEOUT_MS);
186
+
187
+ const wrappedHandleRenderComplete = (): void => {
188
+ window.clearTimeout(timeoutId);
189
+ handleRenderComplete();
190
+ };
191
+
192
+ this._renderMap.once('rendercomplete', wrappedHandleRenderComplete);
193
+ this._renderMap.render();
194
+ } catch (e: unknown) {
195
+ getLogger('MapboxVectorTileImageryProvider').error(
196
+ `Error rendering tile: ${(e as Error).message}`,
197
+ );
198
+ finish(this.emptyCanvas);
199
+ }
200
+ });
201
+ }
202
+
203
+ destroy(): void {
204
+ for (const cache of this._tileCacheByLevel.values()) {
205
+ cache.clear();
206
+ }
207
+ this._tileCacheByLevel.clear();
208
+ this._isRendering = false;
209
+ this._renderMap?.setTarget(undefined);
210
+ this._renderMap?.dispose();
211
+ }
212
+ }
213
+
214
+ export default MapboxStyleImageryProvider;
@@ -207,7 +207,8 @@ class VectorTileImageryProvider {
207
207
  x: number,
208
208
  y: number,
209
209
  level: number,
210
- ): Promise<HTMLImageElement | HTMLCanvasElement> {
210
+ // @ts-expect-error returns undefined if there are too many active requests
211
+ ): Promise<HTMLImageElement | HTMLCanvasElement> | undefined {
211
212
  const features = await this.tileProvider.getFeaturesForTile(
212
213
  x,
213
214
  y,
@@ -33,7 +33,7 @@ export type I3SOptions = LayerOptions & {
33
33
  calculateNormals?: boolean;
34
34
  showFeatures?: boolean;
35
35
  cesium3dTilesetOptions?: Cesium3DTileset.ConstructorOptions;
36
- lightColor?: { x: number; y: number; z: number };
36
+ lightColor?: string;
37
37
  outlineColor?: string;
38
38
  screenSpaceError?: number;
39
39
  screenSpaceErrorMobile?: number;
@@ -85,7 +85,7 @@ class I3SLayer extends FeatureLayer<I3SCesiumImpl> {
85
85
  cesium3dTilesetOptions:
86
86
  | Partial<Cesium3DTileset.ConstructorOptions>
87
87
  | undefined;
88
- lightColor: { x: number; y: number; z: number } | undefined;
88
+ lightColor: string | undefined;
89
89
  outlineColor: string | undefined;
90
90
  screenSpaceError: number;
91
91
  screenSpaceErrorMobile: number;
@@ -135,18 +135,18 @@ class I3SLayer extends FeatureLayer<I3SCesiumImpl> {
135
135
  const cesium3dTilesetOptions =
136
136
  options.cesium3dTilesetOptions || defaultOptions.cesium3dTilesetOptions;
137
137
 
138
+ let lightColor: Cartesian3 | undefined;
139
+ if (options.lightColor) {
140
+ const color = Color.fromCssColorString(options.lightColor);
141
+ lightColor = new Cartesian3(...color.toBytes());
142
+ }
143
+
138
144
  this.cesium3dTilesetOptions = {
139
145
  maximumScreenSpaceError: isMobile()
140
146
  ? this.screenSpaceErrorMobile
141
147
  : this.screenSpaceError,
142
148
  ...cesium3dTilesetOptions,
143
- ...(options.lightColor && {
144
- lightColor: new Cartesian3(
145
- options.lightColor.x,
146
- options.lightColor.y,
147
- options.lightColor.z,
148
- ),
149
- }),
149
+ lightColor,
150
150
  ...(options.outlineColor && {
151
151
  outlineColor: Color.fromCssColorString(options.outlineColor),
152
152
  }),
@@ -290,11 +290,9 @@ class I3SLayer extends FeatureLayer<I3SCesiumImpl> {
290
290
  delete tilesetOptions.outlineColor;
291
291
 
292
292
  if (tilesetOptions.lightColor) {
293
- config.lightColor = {
294
- x: tilesetOptions.lightColor.x,
295
- y: tilesetOptions.lightColor.y,
296
- z: tilesetOptions.lightColor.z,
297
- };
293
+ const { x, y, z } = tilesetOptions.lightColor;
294
+ const color = Color.fromBytes(x, y, z);
295
+ config.lightColor = color.toCssHexString();
298
296
  }
299
297
  delete tilesetOptions.lightColor;
300
298