@map-colonies/react-components 3.13.1 → 3.14.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.
@@ -19,8 +19,11 @@ var cesium_1 = require("cesium");
19
19
  var lodash_1 = require("lodash");
20
20
  var boolean_point_in_polygon_1 = __importDefault(require("@turf/boolean-point-in-polygon"));
21
21
  var point_geojson_1 = require("./tools/geojson/point.geojson");
22
+ var customImageryProviders_1 = require("./helpers/customImageryProviders");
23
+ var utils_1 = require("./helpers/utils");
22
24
  var INC = 1;
23
25
  var DEC = -1;
26
+ var TRANSPARENT_LAYER_ID = 'TRANSPARENT_BASE_LAYER';
24
27
  var LayerManager = /** @class */ (function () {
25
28
  function LayerManager(mapViewer, legendsExtractor, onLayersUpdate) {
26
29
  var _this = this;
@@ -33,18 +36,44 @@ var LayerManager = /** @class */ (function () {
33
36
  if (onLayersUpdate) {
34
37
  this.layerUpdated.addEventListener(onLayersUpdate, this);
35
38
  }
36
- this.mapViewer.imageryLayers.layerRemoved.addEventListener(function () {
37
- _this.setLegends();
38
- _this.layerUpdated.raiseEvent();
39
- });
39
+ // Binding layer's relevancy check to cesium lifecycle if optimized tile requests enabled.
40
+ if (this.mapViewer.shouldOptimizedTileRequests) {
41
+ this.layerUpdated.addEventListener(function (meta) {
42
+ var newMetaKeys = Object.keys(meta);
43
+ var shouldTriggerRelevancyCheck = newMetaKeys.length === 1 &&
44
+ newMetaKeys[0] === customImageryProviders_1.HAS_TRANSPARENCY_META_PROP;
45
+ if (shouldTriggerRelevancyCheck) {
46
+ _this.markRelevantLayersForExtent();
47
+ _this.hideNonRelevantLayers();
48
+ }
49
+ });
50
+ this.mapViewer.imageryLayers.layerRemoved.addEventListener(function () {
51
+ _this.setLegends();
52
+ _this.markRelevantLayersForExtent();
53
+ _this.hideNonRelevantLayers();
54
+ });
55
+ this.mapViewer.imageryLayers.layerMoved.addEventListener(function () {
56
+ _this.markRelevantLayersForExtent();
57
+ _this.hideNonRelevantLayers();
58
+ });
59
+ this.mapViewer.imageryLayers.layerAdded.addEventListener(function () {
60
+ _this.markRelevantLayersForExtent();
61
+ _this.hideNonRelevantLayers();
62
+ });
63
+ this.mapViewer.camera.moveEnd.addEventListener(function () {
64
+ _this.markRelevantLayersForExtent();
65
+ _this.hideNonRelevantLayers();
66
+ });
67
+ }
40
68
  }
41
69
  /* eslint-disable */
42
70
  LayerManager.prototype.addMetaToLayer = function (meta, layerPredicate) {
71
+ var _a;
43
72
  var layer = this.layers.find(layerPredicate);
44
73
  if (layer) {
45
- layer.meta = meta;
74
+ layer.meta = __assign(__assign({}, ((_a = layer.meta) !== null && _a !== void 0 ? _a : {})), meta);
46
75
  this.setLegends();
47
- this.layerUpdated.raiseEvent();
76
+ this.layerUpdated.raiseEvent(meta);
48
77
  }
49
78
  };
50
79
  /* eslint-enable */
@@ -54,19 +83,50 @@ var LayerManager = /** @class */ (function () {
54
83
  sortedBaseMapLayers.forEach(function (layer, idx) {
55
84
  _this.addRasterLayer(layer, idx, baseMap.id);
56
85
  });
86
+ /**
87
+ * Set transparent layer as the first layer. if using optimized tile requests.
88
+ *
89
+ * Apparently, cesium layer's rectangle is not affective when:
90
+ * - There is only one active layer && The layer's rectangle contains the extent rectangle.
91
+ *
92
+ * As a result, when using optimized tile requesting and we zoom in a discrete layer,
93
+ * there are some visual artifacts due to tiles requesting outside of the layer's rectangle boundary.
94
+ *
95
+ * A simple workaround would be adding a transparent layer as the very first layer at all times,
96
+ * so that we ensure the rectangle will always be affective.
97
+ */
98
+ if (this.mapViewer.shouldOptimizedTileRequests) {
99
+ this.removeLayer(TRANSPARENT_LAYER_ID);
100
+ this.addTransparentImageryProvider();
101
+ }
57
102
  };
58
103
  LayerManager.prototype.addRasterLayer = function (layer, index, parentId) {
59
104
  var cesiumLayer;
60
105
  switch (layer.type) {
61
- case 'XYZ_LAYER':
62
- cesiumLayer = this.mapViewer.imageryLayers.addImageryProvider(new cesium_1.UrlTemplateImageryProvider(layer.options), index);
106
+ case 'XYZ_LAYER': {
107
+ var options = layer.options;
108
+ var providerInstance = this.mapViewer.shouldOptimizedTileRequests
109
+ ? new customImageryProviders_1.CustomUrlTemplateImageryProvider(options, this.mapViewer)
110
+ : new cesium_1.UrlTemplateImageryProvider(options);
111
+ cesiumLayer = this.mapViewer.imageryLayers.addImageryProvider(providerInstance, index);
63
112
  break;
64
- case 'WMS_LAYER':
65
- cesiumLayer = this.mapViewer.imageryLayers.addImageryProvider(new cesium_1.WebMapServiceImageryProvider(layer.options), index);
113
+ }
114
+ case 'WMS_LAYER': {
115
+ var options = layer.options;
116
+ var providerInstance = this.mapViewer.shouldOptimizedTileRequests
117
+ ? new customImageryProviders_1.CustomWebMapServiceImageryProvider(options, this.mapViewer)
118
+ : new cesium_1.WebMapServiceImageryProvider(options);
119
+ cesiumLayer = this.mapViewer.imageryLayers.addImageryProvider(providerInstance, index);
66
120
  break;
67
- case 'WMTS_LAYER':
68
- cesiumLayer = this.mapViewer.imageryLayers.addImageryProvider(new cesium_1.WebMapTileServiceImageryProvider(layer.options), index);
121
+ }
122
+ case 'WMTS_LAYER': {
123
+ var options = layer.options;
124
+ var providerInstance = this.mapViewer.shouldOptimizedTileRequests
125
+ ? new customImageryProviders_1.CustomWebMapTileServiceImageryProvider(options, this.mapViewer)
126
+ : new cesium_1.WebMapTileServiceImageryProvider(options);
127
+ cesiumLayer = this.mapViewer.imageryLayers.addImageryProvider(providerInstance, index);
69
128
  break;
129
+ }
70
130
  case 'OSM_LAYER':
71
131
  break;
72
132
  }
@@ -205,6 +265,19 @@ var LayerManager = /** @class */ (function () {
205
265
  return ((_a = layer2.meta) === null || _a === void 0 ? void 0 : _a.zIndex) - ((_b = layer1.meta) === null || _b === void 0 ? void 0 : _b.zIndex);
206
266
  });
207
267
  };
268
+ LayerManager.prototype.addTransparentImageryProvider = function () {
269
+ // Worldwide transparent layer
270
+ var transparentLayer = this.mapViewer.imageryLayers.addImageryProvider(new cesium_1.SingleTileImageryProvider({
271
+ url: './assets/img/transparent-tile.png',
272
+ /* eslint-disable @typescript-eslint/no-magic-numbers */
273
+ rectangle: new cesium_1.Rectangle(-3.141592653589793, -1.5707963267948966, 3.141592653589793, 1.5707963267948966),
274
+ /* eslint-enable @typescript-eslint/no-magic-numbers */
275
+ }), 0);
276
+ transparentLayer.meta = {
277
+ id: TRANSPARENT_LAYER_ID,
278
+ skipRelevancyCheck: true,
279
+ };
280
+ };
208
281
  LayerManager.prototype.setLegends = function () {
209
282
  if (typeof this.legendsExtractor !== 'undefined') {
210
283
  this.legendsList = this.legendsExtractor(this.layers);
@@ -241,6 +314,90 @@ var LayerManager = /** @class */ (function () {
241
314
  }
242
315
  });
243
316
  };
317
+ LayerManager.prototype.hideNonRelevantLayers = function () {
318
+ var _a, _b, _c;
319
+ for (var _i = 0, _d = this.layers; _i < _d.length; _i++) {
320
+ var layer = _d[_i];
321
+ if (((_a = layer.meta) === null || _a === void 0 ? void 0 : _a.relevantToExtent) !== layer.show &&
322
+ layer.imageryProvider.ready) {
323
+ //@ts-ignore
324
+ layer.show = (_c = (_b = layer.meta) === null || _b === void 0 ? void 0 : _b.relevantToExtent) !== null && _c !== void 0 ? _c : true;
325
+ }
326
+ }
327
+ };
328
+ LayerManager.prototype.markRelevantLayersForExtent = function () {
329
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
330
+ try {
331
+ var extent = this.mapViewer.camera.computeViewRectangle();
332
+ // Iterating in reverse order so that top layer is first.
333
+ for (var i = this.layers.length - 1; i >= 0; i--) {
334
+ var layer = this.layers[i];
335
+ var intersectsExtent = !lodash_1.isEmpty(extent) &&
336
+ !lodash_1.isEmpty(layer.rectangle) &&
337
+ cesium_1.Rectangle.intersection(extent, layer.rectangle);
338
+ // Iterating from top layer until the current. (inclusive)
339
+ for (var j = this.layers.length - 1; j >= i; j--) {
340
+ if (((_a = layer.meta) === null || _a === void 0 ? void 0 : _a.skipRelevancyCheck) === true) {
341
+ layer.meta = __assign(__assign({}, layer.meta), { relevantToExtent: true });
342
+ continue;
343
+ }
344
+ var layerAbove = this.layers[j];
345
+ var layerAboveHasTransparency = ((_b = layerAbove.meta) === null || _b === void 0 ? void 0 : _b[customImageryProviders_1.HAS_TRANSPARENCY_META_PROP]) === true;
346
+ if (layer !== layerAbove) {
347
+ // Layer is relevant if in extent and there is no layer above it which is opaque and contains it.
348
+ if (intersectsExtent instanceof cesium_1.Rectangle) {
349
+ if (utils_1.cesiumRectangleContained(extent, layer.rectangle)) {
350
+ // Layer contains the extent.
351
+ if (utils_1.cesiumRectangleContained(extent, layerAbove.rectangle) &&
352
+ !layerAboveHasTransparency) {
353
+ layer.meta = __assign(__assign({}, ((_c = layer.meta) !== null && _c !== void 0 ? _c : {})), { relevantToExtent: false });
354
+ break;
355
+ }
356
+ else {
357
+ layer.meta = __assign(__assign({}, ((_d = layer.meta) !== null && _d !== void 0 ? _d : {})), { relevantToExtent: true });
358
+ }
359
+ }
360
+ if (utils_1.cesiumRectangleContained(extent, layerAbove.rectangle) &&
361
+ !layerAboveHasTransparency) {
362
+ layer.meta = __assign(__assign({}, ((_e = layer.meta) !== null && _e !== void 0 ? _e : {})), { relevantToExtent: false });
363
+ break;
364
+ }
365
+ if (utils_1.cesiumRectangleContained(layer.rectangle, layerAbove.rectangle)) {
366
+ layer.meta = __assign(__assign({}, ((_f = layer.meta) !== null && _f !== void 0 ? _f : {})), { relevantToExtent: layerAboveHasTransparency });
367
+ // Once there is layer above that hides it, no need to continue to check.
368
+ if (!layerAboveHasTransparency) {
369
+ break;
370
+ }
371
+ }
372
+ else {
373
+ // Not contained by layer above it, and inside the extent.
374
+ layer.meta = __assign(__assign({}, ((_g = layer.meta) !== null && _g !== void 0 ? _g : {})), { relevantToExtent: true });
375
+ }
376
+ }
377
+ else {
378
+ layer.meta = __assign(__assign({}, ((_h = layer.meta) !== null && _h !== void 0 ? _h : {})), { relevantToExtent: false });
379
+ }
380
+ }
381
+ else {
382
+ // Handle top layer
383
+ if (i === this.layers.length - 1) {
384
+ layer.meta = __assign(__assign({}, ((_j = layer.meta) !== null && _j !== void 0 ? _j : {})), { relevantToExtent: intersectsExtent instanceof cesium_1.Rectangle });
385
+ }
386
+ }
387
+ }
388
+ }
389
+ }
390
+ catch (e) {
391
+ console.error(e);
392
+ }
393
+ };
394
+ Object.defineProperty(LayerManager.prototype, "layerList", {
395
+ get: function () {
396
+ return this.layers;
397
+ },
398
+ enumerable: false,
399
+ configurable: true
400
+ });
244
401
  return LayerManager;
245
402
  }());
246
403
  exports.default = LayerManager;
@@ -9,7 +9,10 @@ import { CesiumSceneModeEnum } from './map.types';
9
9
  import './map.css';
10
10
  export declare class CesiumViewer extends CesiumViewerCls {
11
11
  layersManager?: LayerManager;
12
+ private useOptimizedTileRequests?;
12
13
  constructor(container: string | Element, options?: CesiumViewerCls.ConstructorOptions);
14
+ get shouldOptimizedTileRequests(): boolean;
15
+ set shouldOptimizedTileRequests(useOptimizedTileRequests: boolean);
13
16
  }
14
17
  export interface IContextMenuData {
15
18
  data: Record<string, unknown>[];
@@ -49,6 +52,7 @@ export interface CesiumMapProps extends ViewerProps {
49
52
  };
50
53
  sceneModes?: CesiumSceneModeEnum[];
51
54
  baseMaps?: IBaseMaps;
55
+ useOptimizedTileRequests?: boolean;
52
56
  terrainProvider?: TerrainProvider;
53
57
  imageryContextMenu?: React.ReactElement<IContextMenuData>;
54
58
  imageryContextMenuSize?: {
@@ -73,6 +73,17 @@ var CesiumViewer = /** @class */ (function (_super) {
73
73
  function CesiumViewer(container, options) {
74
74
  return _super.call(this, container, options) || this;
75
75
  }
76
+ Object.defineProperty(CesiumViewer.prototype, "shouldOptimizedTileRequests", {
77
+ get: function () {
78
+ var _a;
79
+ return (_a = this.useOptimizedTileRequests) !== null && _a !== void 0 ? _a : false;
80
+ },
81
+ set: function (useOptimizedTileRequests) {
82
+ this.useOptimizedTileRequests = useOptimizedTileRequests;
83
+ },
84
+ enumerable: false,
85
+ configurable: true
86
+ });
76
87
  return CesiumViewer;
77
88
  }(cesium_1.Viewer));
78
89
  exports.CesiumViewer = CesiumViewer;
@@ -134,14 +145,24 @@ var CesiumMap = function (props) {
134
145
  setMapViewRef((_a = ref.current) === null || _a === void 0 ? void 0 : _a.cesiumElement);
135
146
  }, [ref, props.imageryContextMenu]);
136
147
  react_1.useEffect(function () {
137
- var _a;
148
+ var _a, _b;
138
149
  if (mapViewRef) {
139
- mapViewRef.layersManager = new layers_manager_1.default(mapViewRef, (_a = props.legends) === null || _a === void 0 ? void 0 : _a.mapLegendsExtractor, function () {
150
+ mapViewRef.shouldOptimizedTileRequests =
151
+ (_a = props.useOptimizedTileRequests) !== null && _a !== void 0 ? _a : false;
152
+ mapViewRef.layersManager = new layers_manager_1.default(mapViewRef, (_b = props.legends) === null || _b === void 0 ? void 0 : _b.mapLegendsExtractor, function () {
140
153
  var _a;
141
154
  setLegendsList((_a = mapViewRef === null || mapViewRef === void 0 ? void 0 : mapViewRef.layersManager) === null || _a === void 0 ? void 0 : _a.legendsList);
142
155
  });
143
156
  }
157
+ // eslint-disable-next-line react-hooks/exhaustive-deps
144
158
  }, [mapViewRef]);
159
+ react_1.useEffect(function () {
160
+ var _a;
161
+ if (mapViewRef) {
162
+ mapViewRef.shouldOptimizedTileRequests =
163
+ (_a = props.useOptimizedTileRequests) !== null && _a !== void 0 ? _a : false;
164
+ }
165
+ }, [props.useOptimizedTileRequests, mapViewRef]);
145
166
  react_1.useEffect(function () {
146
167
  var _a;
147
168
  setSceneModes((_a = props.sceneModes) !== null && _a !== void 0 ? _a : [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@map-colonies/react-components",
3
- "version": "3.13.1",
3
+ "version": "3.14.0",
4
4
  "module": "dist/index.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -93,7 +93,7 @@
93
93
  "jest-enzyme": "^7.1.2",
94
94
  "react-test-renderer": "^16.13.1"
95
95
  },
96
- "gitHead": "2b22c0f4e5e2b632bd656ec88b29a4d02d339af0",
96
+ "gitHead": "71cc65e8ccd3f45ab1b0eb9de2bd3aab10735654",
97
97
  "jest": {
98
98
  "coverageReporters": [
99
99
  "text",
@@ -0,0 +1,173 @@
1
+ import {
2
+ Request,
3
+ ImageryProvider,
4
+ UrlTemplateImageryProvider,
5
+ WebMapServiceImageryProvider,
6
+ WebMapTileServiceImageryProvider,
7
+ ImageryLayer,
8
+ } from 'cesium';
9
+ import { get, isEmpty } from 'lodash';
10
+ import { ICesiumImageryLayer } from '../layers-manager';
11
+ import { CesiumViewer } from '../map';
12
+ import { imageHasTransparency } from './utils';
13
+
14
+ export interface CustomImageryProvider extends ImageryProvider {
15
+ readonly layerListInstance: ICesiumImageryLayer[];
16
+ tileTransparencyCheckedCounter: number;
17
+ mapViewer: CesiumViewer;
18
+ readonly maxTilesForTransparencyCheck: number;
19
+ }
20
+
21
+ const NUMBER_OF_TILES_TO_CHECK = 3;
22
+ export const HAS_TRANSPARENCY_META_PROP = 'hasTransparency';
23
+
24
+ function customCommonRequestImage(
25
+ this: CustomImageryProvider,
26
+ requestImageFn: ImageryProvider['requestImage'],
27
+ x: number,
28
+ y: number,
29
+ level: number,
30
+ request?: Request | undefined
31
+ ): Promise<HTMLImageElement | HTMLCanvasElement> | undefined {
32
+ // custom Logic
33
+ setTimeout(() => {
34
+ const requestedLayerMeta = this.layerListInstance.find(
35
+ /* eslint-disable */
36
+ (layer: ImageryLayer): boolean => {
37
+ return (
38
+ (layer as any)._imageryProvider._resource._url ===
39
+ (this as any)._resource._url
40
+ );
41
+ }
42
+ /* eslint-enable */
43
+ )?.meta;
44
+
45
+ const layerHasTransparency =
46
+ get(requestedLayerMeta, HAS_TRANSPARENCY_META_PROP) === true;
47
+
48
+ if (
49
+ this.tileTransparencyCheckedCounter < NUMBER_OF_TILES_TO_CHECK &&
50
+ !layerHasTransparency
51
+ ) {
52
+ void imageHasTransparency(request?.url as string, this).then(
53
+ (hasTransparency) => {
54
+ this.mapViewer.layersManager?.addMetaToLayer(
55
+ { [HAS_TRANSPARENCY_META_PROP]: hasTransparency },
56
+ /* eslint-disable */
57
+ (layer: ImageryLayer): boolean => {
58
+ return (
59
+ (layer as any)._imageryProvider._resource._url ===
60
+ (this as any)._resource._url
61
+ );
62
+ }
63
+ /* eslint-enable */
64
+ );
65
+ }
66
+ );
67
+ }
68
+ }, 0);
69
+
70
+ return requestImageFn(x, y, level, request);
71
+ }
72
+
73
+ export class CustomUrlTemplateImageryProvider extends UrlTemplateImageryProvider {
74
+ public readonly layerListInstance: ICesiumImageryLayer[];
75
+ public readonly mapViewer: CesiumViewer;
76
+ public readonly maxTilesForTransparencyCheck = NUMBER_OF_TILES_TO_CHECK;
77
+
78
+ public tileTransparencyCheckedCounter = 0;
79
+
80
+ public constructor(
81
+ opts: UrlTemplateImageryProvider.ConstructorOptions,
82
+ mapViewer: CesiumViewer
83
+ ) {
84
+ super(opts);
85
+ this.layerListInstance = mapViewer.layersManager
86
+ ?.layerList as ICesiumImageryLayer[];
87
+ this.mapViewer = mapViewer;
88
+ }
89
+
90
+ public requestImage(
91
+ x: number,
92
+ y: number,
93
+ level: number,
94
+ request?: Request | undefined
95
+ ): Promise<HTMLImageElement | HTMLCanvasElement> | undefined {
96
+ return customCommonRequestImage.call(
97
+ this,
98
+ super.requestImage.bind(this),
99
+ x,
100
+ y,
101
+ level,
102
+ request
103
+ );
104
+ }
105
+ }
106
+
107
+ export class CustomWebMapServiceImageryProvider extends WebMapServiceImageryProvider {
108
+ public readonly layerListInstance: ICesiumImageryLayer[];
109
+ public readonly mapViewer: CesiumViewer;
110
+ public readonly maxTilesForTransparencyCheck = NUMBER_OF_TILES_TO_CHECK;
111
+
112
+ public tileTransparencyCheckedCounter = 0;
113
+
114
+ public constructor(
115
+ opts: WebMapServiceImageryProvider.ConstructorOptions,
116
+ mapViewer: CesiumViewer
117
+ ) {
118
+ super(opts);
119
+ this.layerListInstance = mapViewer.layersManager
120
+ ?.layerList as ICesiumImageryLayer[];
121
+ this.mapViewer = mapViewer;
122
+ }
123
+
124
+ public requestImage(
125
+ x: number,
126
+ y: number,
127
+ level: number,
128
+ request?: Request | undefined
129
+ ): Promise<HTMLImageElement | HTMLCanvasElement> | undefined {
130
+ return customCommonRequestImage.call(
131
+ this,
132
+ super.requestImage.bind(this),
133
+ x,
134
+ y,
135
+ level,
136
+ request
137
+ );
138
+ }
139
+ }
140
+
141
+ export class CustomWebMapTileServiceImageryProvider extends WebMapTileServiceImageryProvider {
142
+ public readonly layerListInstance: ICesiumImageryLayer[];
143
+ public readonly mapViewer: CesiumViewer;
144
+ public readonly maxTilesForTransparencyCheck = NUMBER_OF_TILES_TO_CHECK;
145
+
146
+ public tileTransparencyCheckedCounter = 0;
147
+
148
+ public constructor(
149
+ opts: WebMapTileServiceImageryProvider.ConstructorOptions,
150
+ mapViewer: CesiumViewer
151
+ ) {
152
+ super(opts);
153
+ this.layerListInstance = mapViewer.layersManager
154
+ ?.layerList as ICesiumImageryLayer[];
155
+ this.mapViewer = mapViewer;
156
+ }
157
+
158
+ public requestImage(
159
+ x: number,
160
+ y: number,
161
+ level: number,
162
+ request?: Request | undefined
163
+ ): Promise<HTMLImageElement | HTMLCanvasElement> | undefined {
164
+ return customCommonRequestImage.call(
165
+ this,
166
+ super.requestImage.bind(this),
167
+ x,
168
+ y,
169
+ level,
170
+ request
171
+ );
172
+ }
173
+ }
@@ -0,0 +1,135 @@
1
+ import { Rectangle } from 'cesium';
2
+ import { CustomImageryProvider } from './customImageryProviders';
3
+
4
+ const canvasElem = document.createElement('canvas');
5
+ const canvasCtx = canvasElem.getContext('2d');
6
+
7
+ /**
8
+ * @param image Image data to check
9
+ * @returns `true` if image data has at least one transparent pixel, `false` otherwise.
10
+ */
11
+ const imageDataHasTransparency = (image: ImageData | undefined): boolean => {
12
+ const ALPHA_CHANNEL_OFFSET = 4; // [R,G,B,A, R,G,B,A] => FLAT ARRAY OF THIS SHAPE; (Uint8ClampedArray)
13
+ const OPAQUE_PIXEL_ALPHA_VALUE = 255;
14
+ const imgData = image?.data ?? [];
15
+
16
+ // Iterate through alpha channels only.
17
+ for (let i = 3; i < imgData?.length; i += ALPHA_CHANNEL_OFFSET) {
18
+ if (imgData[i] < OPAQUE_PIXEL_ALPHA_VALUE) {
19
+ // Transparent pixel found.
20
+ return true;
21
+ }
22
+ }
23
+ return false;
24
+ };
25
+
26
+ /**
27
+ * An async function to detect images with transparency.
28
+ * @param image The image to resolve. if value is `string (url)` it tries to fetch the image data first.
29
+ * Could also be `HTMLImageElement` or `ImageBitmap`
30
+ * @param context `optional` `CustomImageryProvider` context in which the function will automatically increase
31
+ * the `tileTransparencyCheckedCounter`. Sets to `maxTilesForTransparencyCheck` when layer detected as transparent.
32
+ * @returns
33
+ */
34
+ export const imageHasTransparency = async (
35
+ image: string | HTMLImageElement | ImageBitmap,
36
+ context?: CustomImageryProvider
37
+ ): Promise<boolean> => {
38
+ if (context) {
39
+ context.tileTransparencyCheckedCounter++;
40
+ }
41
+
42
+ return new Promise<boolean>((resolve, reject) => {
43
+ try {
44
+ canvasCtx?.clearRect(0, 0, canvasElem.width, canvasElem.height);
45
+ let imageElement: HTMLImageElement;
46
+
47
+ // Init Image instance.
48
+ if (image instanceof HTMLImageElement) {
49
+ imageElement = image;
50
+ } else if (image instanceof ImageBitmap) {
51
+ canvasElem.width = image.width;
52
+ canvasElem.height = image.height;
53
+ canvasCtx?.drawImage(image, 0, 0);
54
+
55
+ const canvasImg = canvasCtx?.getImageData(
56
+ 0,
57
+ 0,
58
+ canvasElem.width,
59
+ canvasElem.height
60
+ );
61
+ const hasTransparency = imageDataHasTransparency(canvasImg);
62
+ if (hasTransparency) {
63
+ if (context) {
64
+ context.tileTransparencyCheckedCounter =
65
+ context.maxTilesForTransparencyCheck;
66
+ }
67
+ }
68
+
69
+ resolve(hasTransparency);
70
+ return;
71
+ } else {
72
+ imageElement = new Image();
73
+ imageElement.crossOrigin = 'anonymous'; // Disable CORS errors on canvas image load.
74
+ imageElement.src = image;
75
+ }
76
+
77
+ imageElement.onload = (): void => {
78
+ // Image loaded, set canvas size to image size.
79
+ canvasElem.width = imageElement.width;
80
+ canvasElem.height = imageElement.height;
81
+
82
+ canvasCtx?.drawImage(imageElement, 0, 0);
83
+
84
+ const canvasImg = canvasCtx?.getImageData(
85
+ 0,
86
+ 0,
87
+ canvasElem.width,
88
+ canvasElem.height
89
+ );
90
+
91
+ const hasTransparency = imageDataHasTransparency(canvasImg);
92
+
93
+ if (hasTransparency) {
94
+ if (context) {
95
+ context.tileTransparencyCheckedCounter =
96
+ context.maxTilesForTransparencyCheck;
97
+ }
98
+
99
+ resolve(true);
100
+ } else {
101
+ resolve(false);
102
+ }
103
+ };
104
+ } catch (e) {
105
+ console.error('Could not determine image transparency. Error => ', e);
106
+ reject(e);
107
+ }
108
+ });
109
+ };
110
+
111
+ /**
112
+ * Checks if `rect` is contained inside `anotherRect`
113
+ * @param rect
114
+ * @param anotherRect
115
+ */
116
+ export const cesiumRectangleContained = (
117
+ rect: Rectangle,
118
+ anotherRect: Rectangle
119
+ ): boolean => {
120
+ const { west, east, north, south } = rect;
121
+ const {
122
+ west: anotherWest,
123
+ east: anotherEast,
124
+ north: anotherNorth,
125
+ south: anotherSouth,
126
+ } = anotherRect;
127
+
128
+ const isRectInsideAnother =
129
+ west >= anotherWest &&
130
+ east <= anotherEast &&
131
+ north <= anotherNorth &&
132
+ south >= anotherSouth;
133
+
134
+ return isRectInsideAnother;
135
+ };