@map-colonies/react-components 3.13.1 → 3.15.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.
- package/CHANGELOG.md +46 -11
- package/dist/assets/img/transparent-tile.png +0 -0
- package/dist/cesium-map/helpers/customImageryProviders.d.ts +34 -0
- package/dist/cesium-map/helpers/customImageryProviders.js +106 -0
- package/dist/cesium-map/helpers/utils.d.ts +17 -0
- package/dist/cesium-map/helpers/utils.js +142 -0
- package/dist/cesium-map/layers/wms.layer.js +7 -1
- package/dist/cesium-map/layers/wmts.layer.js +7 -1
- package/dist/cesium-map/layers/xyz.layer.js +7 -1
- package/dist/cesium-map/layers-manager.d.ts +4 -0
- package/dist/cesium-map/layers-manager.js +169 -12
- package/dist/cesium-map/map.d.ts +5 -0
- package/dist/cesium-map/map.js +40 -12
- package/dist/cesium-map/tools/scale-tracker.tool.js +4 -1
- package/dist/cesium-map/tools/zoom_level-tracker.tool.css +19 -0
- package/dist/cesium-map/tools/zoom_level-tracker.tool.d.ts +8 -0
- package/dist/cesium-map/tools/zoom_level-tracker.tool.js +131 -0
- package/package.json +2 -2
- package/public/assets/img/transparent-tile.png +0 -0
- package/src/lib/cesium-map/helpers/customImageryProviders.ts +173 -0
- package/src/lib/cesium-map/helpers/utils.ts +135 -0
- package/src/lib/cesium-map/layers/optimized-tile-requests.stories.tsx +279 -0
- package/src/lib/cesium-map/layers/wms.layer.tsx +10 -4
- package/src/lib/cesium-map/layers/wmts.layer.tsx +9 -4
- package/src/lib/cesium-map/layers/xyz.layer.tsx +9 -4
- package/src/lib/cesium-map/layers-manager.ts +223 -19
- package/src/lib/cesium-map/map.stories.tsx +1 -0
- package/src/lib/cesium-map/map.tsx +33 -0
- package/src/lib/cesium-map/settings/settings.stories.tsx +5 -1
- package/src/lib/cesium-map/tools/coordinates-tracker.tool.tsx +6 -1
- package/src/lib/cesium-map/tools/scale-tracker.tool.tsx +4 -1
- package/src/lib/cesium-map/tools/zoom_level-tracker.tool.css +19 -0
- package/src/lib/cesium-map/tools/zoom_level-tracker.tool.tsx +142 -0
- package/src/lib/date-range-picker/date-range-picker.spec.tsx +81 -81
- package/jest_html_reporters.html +0 -78
|
@@ -5,8 +5,10 @@ import {
|
|
|
5
5
|
WebMapServiceImageryProvider,
|
|
6
6
|
WebMapTileServiceImageryProvider,
|
|
7
7
|
Event,
|
|
8
|
+
Rectangle,
|
|
9
|
+
SingleTileImageryProvider,
|
|
8
10
|
} from 'cesium';
|
|
9
|
-
import { get } from 'lodash';
|
|
11
|
+
import { get, isEmpty } from 'lodash';
|
|
10
12
|
import { Feature, Point, Polygon } from 'geojson';
|
|
11
13
|
import booleanPointInPolygon from '@turf/boolean-point-in-polygon';
|
|
12
14
|
import {
|
|
@@ -19,6 +21,13 @@ import { CesiumViewer } from './map';
|
|
|
19
21
|
import { IBaseMap } from './settings/settings';
|
|
20
22
|
import { pointToGeoJSON } from './tools/geojson/point.geojson';
|
|
21
23
|
import { IMapLegend } from './map-legend';
|
|
24
|
+
import {
|
|
25
|
+
CustomUrlTemplateImageryProvider,
|
|
26
|
+
CustomWebMapServiceImageryProvider,
|
|
27
|
+
CustomWebMapTileServiceImageryProvider,
|
|
28
|
+
HAS_TRANSPARENCY_META_PROP,
|
|
29
|
+
} from './helpers/customImageryProviders';
|
|
30
|
+
import { cesiumRectangleContained } from './helpers/utils';
|
|
22
31
|
|
|
23
32
|
const INC = 1;
|
|
24
33
|
const DEC = -1;
|
|
@@ -52,6 +61,8 @@ export interface IVectorLayer {
|
|
|
52
61
|
|
|
53
62
|
export type LegendExtractor = (layers: (any & { meta: any })[]) => IMapLegend[];
|
|
54
63
|
|
|
64
|
+
const TRANSPARENT_LAYER_ID = 'TRANSPARENT_BASE_LAYER';
|
|
65
|
+
|
|
55
66
|
class LayerManager {
|
|
56
67
|
public mapViewer: CesiumViewer;
|
|
57
68
|
|
|
@@ -74,10 +85,41 @@ class LayerManager {
|
|
|
74
85
|
if (onLayersUpdate) {
|
|
75
86
|
this.layerUpdated.addEventListener(onLayersUpdate, this);
|
|
76
87
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
|
|
89
|
+
// Binding layer's relevancy check to cesium lifecycle if optimized tile requests enabled.
|
|
90
|
+
if (this.mapViewer.shouldOptimizedTileRequests) {
|
|
91
|
+
this.layerUpdated.addEventListener((meta: Record<string, unknown>) => {
|
|
92
|
+
const newMetaKeys = Object.keys(meta);
|
|
93
|
+
const shouldTriggerRelevancyCheck =
|
|
94
|
+
newMetaKeys.length === 1 &&
|
|
95
|
+
newMetaKeys[0] === HAS_TRANSPARENCY_META_PROP;
|
|
96
|
+
if (shouldTriggerRelevancyCheck) {
|
|
97
|
+
this.markRelevantLayersForExtent();
|
|
98
|
+
this.hideNonRelevantLayers();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.mapViewer.imageryLayers.layerRemoved.addEventListener(() => {
|
|
103
|
+
this.setLegends();
|
|
104
|
+
this.markRelevantLayersForExtent();
|
|
105
|
+
this.hideNonRelevantLayers();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
this.mapViewer.imageryLayers.layerMoved.addEventListener(() => {
|
|
109
|
+
this.markRelevantLayersForExtent();
|
|
110
|
+
this.hideNonRelevantLayers();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this.mapViewer.imageryLayers.layerAdded.addEventListener(() => {
|
|
114
|
+
this.markRelevantLayersForExtent();
|
|
115
|
+
this.hideNonRelevantLayers();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
this.mapViewer.camera.moveEnd.addEventListener(() => {
|
|
119
|
+
this.markRelevantLayersForExtent();
|
|
120
|
+
this.hideNonRelevantLayers();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
81
123
|
}
|
|
82
124
|
|
|
83
125
|
/* eslint-disable */
|
|
@@ -87,9 +129,9 @@ class LayerManager {
|
|
|
87
129
|
): void {
|
|
88
130
|
const layer = this.layers.find(layerPredicate);
|
|
89
131
|
if (layer) {
|
|
90
|
-
layer.meta = meta;
|
|
132
|
+
layer.meta = { ...(layer.meta ?? {}), ...meta };
|
|
91
133
|
this.setLegends();
|
|
92
|
-
this.layerUpdated.raiseEvent();
|
|
134
|
+
this.layerUpdated.raiseEvent(meta);
|
|
93
135
|
}
|
|
94
136
|
}
|
|
95
137
|
/* eslint-enable */
|
|
@@ -101,6 +143,24 @@ class LayerManager {
|
|
|
101
143
|
sortedBaseMapLayers.forEach((layer, idx) => {
|
|
102
144
|
this.addRasterLayer(layer, idx, baseMap.id);
|
|
103
145
|
});
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Set transparent layer as the first layer. if using optimized tile requests.
|
|
149
|
+
*
|
|
150
|
+
* Apparently, cesium layer's rectangle is not affective when:
|
|
151
|
+
* - There is only one active layer && The layer's rectangle contains the extent rectangle.
|
|
152
|
+
*
|
|
153
|
+
* As a result, when using optimized tile requesting and we zoom in a discrete layer,
|
|
154
|
+
* there are some visual artifacts due to tiles requesting outside of the layer's rectangle boundary.
|
|
155
|
+
*
|
|
156
|
+
* A simple workaround would be adding a transparent layer as the very first layer at all times,
|
|
157
|
+
* so that we ensure the rectangle will always be affective.
|
|
158
|
+
*/
|
|
159
|
+
|
|
160
|
+
if (this.mapViewer.shouldOptimizedTileRequests) {
|
|
161
|
+
this.removeLayer(TRANSPARENT_LAYER_ID);
|
|
162
|
+
this.addTransparentImageryProvider();
|
|
163
|
+
}
|
|
104
164
|
}
|
|
105
165
|
|
|
106
166
|
public addRasterLayer(
|
|
@@ -110,30 +170,47 @@ class LayerManager {
|
|
|
110
170
|
): void {
|
|
111
171
|
let cesiumLayer: ICesiumImageryLayer | undefined;
|
|
112
172
|
switch (layer.type) {
|
|
113
|
-
case 'XYZ_LAYER':
|
|
173
|
+
case 'XYZ_LAYER': {
|
|
174
|
+
const options = layer.options as UrlTemplateImageryProvider.ConstructorOptions;
|
|
175
|
+
|
|
176
|
+
const providerInstance = this.mapViewer.shouldOptimizedTileRequests
|
|
177
|
+
? new CustomUrlTemplateImageryProvider(options, this.mapViewer)
|
|
178
|
+
: new UrlTemplateImageryProvider(options);
|
|
179
|
+
|
|
114
180
|
cesiumLayer = this.mapViewer.imageryLayers.addImageryProvider(
|
|
115
|
-
|
|
116
|
-
layer.options as UrlTemplateImageryProvider.ConstructorOptions
|
|
117
|
-
),
|
|
181
|
+
providerInstance,
|
|
118
182
|
index
|
|
119
183
|
);
|
|
184
|
+
|
|
120
185
|
break;
|
|
121
|
-
|
|
186
|
+
}
|
|
187
|
+
case 'WMS_LAYER': {
|
|
188
|
+
const options = layer.options as WebMapServiceImageryProvider.ConstructorOptions;
|
|
189
|
+
|
|
190
|
+
const providerInstance = this.mapViewer.shouldOptimizedTileRequests
|
|
191
|
+
? new CustomWebMapServiceImageryProvider(options, this.mapViewer)
|
|
192
|
+
: new WebMapServiceImageryProvider(options);
|
|
193
|
+
|
|
122
194
|
cesiumLayer = this.mapViewer.imageryLayers.addImageryProvider(
|
|
123
|
-
|
|
124
|
-
layer.options as WebMapServiceImageryProvider.ConstructorOptions
|
|
125
|
-
),
|
|
195
|
+
providerInstance,
|
|
126
196
|
index
|
|
127
197
|
);
|
|
128
198
|
break;
|
|
129
|
-
|
|
199
|
+
}
|
|
200
|
+
case 'WMTS_LAYER': {
|
|
201
|
+
const options = layer.options as WebMapTileServiceImageryProvider.ConstructorOptions;
|
|
202
|
+
|
|
203
|
+
const providerInstance = this.mapViewer.shouldOptimizedTileRequests
|
|
204
|
+
? new CustomWebMapTileServiceImageryProvider(options, this.mapViewer)
|
|
205
|
+
: new WebMapTileServiceImageryProvider(options);
|
|
206
|
+
|
|
130
207
|
cesiumLayer = this.mapViewer.imageryLayers.addImageryProvider(
|
|
131
|
-
|
|
132
|
-
layer.options as WebMapTileServiceImageryProvider.ConstructorOptions
|
|
133
|
-
),
|
|
208
|
+
providerInstance,
|
|
134
209
|
index
|
|
135
210
|
);
|
|
211
|
+
|
|
136
212
|
break;
|
|
213
|
+
}
|
|
137
214
|
case 'OSM_LAYER':
|
|
138
215
|
break;
|
|
139
216
|
}
|
|
@@ -310,6 +387,29 @@ class LayerManager {
|
|
|
310
387
|
});
|
|
311
388
|
}
|
|
312
389
|
|
|
390
|
+
public addTransparentImageryProvider(): void {
|
|
391
|
+
// Worldwide transparent layer
|
|
392
|
+
const transparentLayer = this.mapViewer.imageryLayers.addImageryProvider(
|
|
393
|
+
new SingleTileImageryProvider({
|
|
394
|
+
url: './assets/img/transparent-tile.png',
|
|
395
|
+
/* eslint-disable @typescript-eslint/no-magic-numbers */
|
|
396
|
+
rectangle: new Rectangle(
|
|
397
|
+
-3.141592653589793,
|
|
398
|
+
-1.5707963267948966,
|
|
399
|
+
3.141592653589793,
|
|
400
|
+
1.5707963267948966
|
|
401
|
+
),
|
|
402
|
+
/* eslint-enable @typescript-eslint/no-magic-numbers */
|
|
403
|
+
}),
|
|
404
|
+
0
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
(transparentLayer as ICesiumImageryLayer).meta = {
|
|
408
|
+
id: TRANSPARENT_LAYER_ID,
|
|
409
|
+
skipRelevancyCheck: true,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
313
413
|
private setLegends(): void {
|
|
314
414
|
if (typeof this.legendsExtractor !== 'undefined') {
|
|
315
415
|
this.legendsList = this.legendsExtractor(this.layers);
|
|
@@ -349,6 +449,110 @@ class LayerManager {
|
|
|
349
449
|
}
|
|
350
450
|
});
|
|
351
451
|
}
|
|
452
|
+
|
|
453
|
+
private hideNonRelevantLayers(): void {
|
|
454
|
+
for (const layer of this.layers) {
|
|
455
|
+
if (
|
|
456
|
+
layer.meta?.relevantToExtent !== layer.show &&
|
|
457
|
+
layer.imageryProvider.ready
|
|
458
|
+
) {
|
|
459
|
+
//@ts-ignore
|
|
460
|
+
layer.show = layer.meta?.relevantToExtent ?? true;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private markRelevantLayersForExtent(): void {
|
|
466
|
+
try {
|
|
467
|
+
const extent = this.mapViewer.camera.computeViewRectangle() as Rectangle;
|
|
468
|
+
|
|
469
|
+
// Iterating in reverse order so that top layer is first.
|
|
470
|
+
for (let i = this.layers.length - 1; i >= 0; i--) {
|
|
471
|
+
const layer = this.layers[i];
|
|
472
|
+
const intersectsExtent =
|
|
473
|
+
!isEmpty(extent) &&
|
|
474
|
+
!isEmpty(layer.rectangle) &&
|
|
475
|
+
Rectangle.intersection(extent, layer.rectangle);
|
|
476
|
+
|
|
477
|
+
// Iterating from top layer until the current. (inclusive)
|
|
478
|
+
for (let j = this.layers.length - 1; j >= i; j--) {
|
|
479
|
+
if (layer.meta?.skipRelevancyCheck === true) {
|
|
480
|
+
layer.meta = { ...layer.meta, relevantToExtent: true };
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const layerAbove = this.layers[j];
|
|
485
|
+
const layerAboveHasTransparency =
|
|
486
|
+
layerAbove.meta?.[HAS_TRANSPARENCY_META_PROP] === true;
|
|
487
|
+
|
|
488
|
+
if (layer !== layerAbove) {
|
|
489
|
+
// Layer is relevant if in extent and there is no layer above it which is opaque and contains it.
|
|
490
|
+
if (intersectsExtent instanceof Rectangle) {
|
|
491
|
+
if (cesiumRectangleContained(extent, layer.rectangle)) {
|
|
492
|
+
// Layer contains the extent.
|
|
493
|
+
if (
|
|
494
|
+
cesiumRectangleContained(extent, layerAbove.rectangle) &&
|
|
495
|
+
!layerAboveHasTransparency
|
|
496
|
+
) {
|
|
497
|
+
layer.meta = {
|
|
498
|
+
...(layer.meta ?? {}),
|
|
499
|
+
relevantToExtent: false,
|
|
500
|
+
};
|
|
501
|
+
break;
|
|
502
|
+
} else {
|
|
503
|
+
layer.meta = {
|
|
504
|
+
...(layer.meta ?? {}),
|
|
505
|
+
relevantToExtent: true,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (
|
|
511
|
+
cesiumRectangleContained(extent, layerAbove.rectangle) &&
|
|
512
|
+
!layerAboveHasTransparency
|
|
513
|
+
) {
|
|
514
|
+
layer.meta = { ...(layer.meta ?? {}), relevantToExtent: false };
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (
|
|
519
|
+
cesiumRectangleContained(layer.rectangle, layerAbove.rectangle)
|
|
520
|
+
) {
|
|
521
|
+
layer.meta = {
|
|
522
|
+
...(layer.meta ?? {}),
|
|
523
|
+
relevantToExtent: layerAboveHasTransparency,
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// Once there is layer above that hides it, no need to continue to check.
|
|
527
|
+
if (!layerAboveHasTransparency) {
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
} else {
|
|
531
|
+
// Not contained by layer above it, and inside the extent.
|
|
532
|
+
layer.meta = { ...(layer.meta ?? {}), relevantToExtent: true };
|
|
533
|
+
}
|
|
534
|
+
} else {
|
|
535
|
+
layer.meta = { ...(layer.meta ?? {}), relevantToExtent: false };
|
|
536
|
+
}
|
|
537
|
+
} else {
|
|
538
|
+
// Handle top layer
|
|
539
|
+
if (i === this.layers.length - 1) {
|
|
540
|
+
layer.meta = {
|
|
541
|
+
...(layer.meta ?? {}),
|
|
542
|
+
relevantToExtent: intersectsExtent instanceof Rectangle,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
} catch (e) {
|
|
549
|
+
console.error(e);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
public get layerList(): ICesiumImageryLayer[] {
|
|
554
|
+
return this.layers;
|
|
555
|
+
}
|
|
352
556
|
}
|
|
353
557
|
|
|
354
558
|
export default LayerManager;
|
|
@@ -121,6 +121,7 @@ LocalizedMap.argTypes = {
|
|
|
121
121
|
MAP_SETTINGS_SCENE_MODE_TITLE: 'תצורה',
|
|
122
122
|
MAP_SETTINGS_BASE_MAP_TITLE: 'מפות בסיס',
|
|
123
123
|
MAP_SETTINGS_OK_BUTTON_TEXT: 'אישור',
|
|
124
|
+
ZOOM_LABEL: 'זום',
|
|
124
125
|
},
|
|
125
126
|
/* eslint-enable @typescript-eslint/naming-convention */
|
|
126
127
|
},
|
|
@@ -27,6 +27,7 @@ import { Box } from '../box';
|
|
|
27
27
|
import { Proj } from '../utils/projections';
|
|
28
28
|
import { CoordinatesTrackerTool } from './tools/coordinates-tracker.tool';
|
|
29
29
|
import { ScaleTrackerTool } from './tools/scale-tracker.tool';
|
|
30
|
+
import { ZoomLevelTrackerTool } from './tools/zoom_level-tracker.tool';
|
|
30
31
|
import { CesiumSettings, IBaseMap, IBaseMaps } from './settings/settings';
|
|
31
32
|
import { IMapLegend, MapLegendSidebar, MapLegendToggle } from './map-legend';
|
|
32
33
|
import LayerManager, { LegendExtractor } from './layers-manager';
|
|
@@ -58,6 +59,7 @@ interface ICameraState {
|
|
|
58
59
|
}
|
|
59
60
|
export class CesiumViewer extends CesiumViewerCls {
|
|
60
61
|
public layersManager?: LayerManager;
|
|
62
|
+
private useOptimizedTileRequests?: boolean;
|
|
61
63
|
|
|
62
64
|
public constructor(
|
|
63
65
|
container: string | Element,
|
|
@@ -65,6 +67,14 @@ export class CesiumViewer extends CesiumViewerCls {
|
|
|
65
67
|
) {
|
|
66
68
|
super(container, options);
|
|
67
69
|
}
|
|
70
|
+
|
|
71
|
+
public get shouldOptimizedTileRequests(): boolean {
|
|
72
|
+
return this.useOptimizedTileRequests ?? false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public set shouldOptimizedTileRequests(useOptimizedTileRequests: boolean) {
|
|
76
|
+
this.useOptimizedTileRequests = useOptimizedTileRequests;
|
|
77
|
+
}
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
const mapContext = createContext<CesiumViewer | null>(null);
|
|
@@ -96,6 +106,7 @@ interface ILegends {
|
|
|
96
106
|
|
|
97
107
|
export interface CesiumMapProps extends ViewerProps {
|
|
98
108
|
showMousePosition?: boolean;
|
|
109
|
+
showZoomLevel?: boolean;
|
|
99
110
|
showScale?: boolean;
|
|
100
111
|
projection?: Proj;
|
|
101
112
|
center?: [number, number];
|
|
@@ -103,6 +114,7 @@ export interface CesiumMapProps extends ViewerProps {
|
|
|
103
114
|
locale?: { [key: string]: string };
|
|
104
115
|
sceneModes?: CesiumSceneModeEnum[];
|
|
105
116
|
baseMaps?: IBaseMaps;
|
|
117
|
+
useOptimizedTileRequests?: boolean;
|
|
106
118
|
terrainProvider?: TerrainProvider;
|
|
107
119
|
imageryContextMenu?: React.ReactElement<IContextMenuData>;
|
|
108
120
|
imageryContextMenuSize?: {
|
|
@@ -128,6 +140,7 @@ export const CesiumMap: React.FC<CesiumMapProps> = (props) => {
|
|
|
128
140
|
const [mapViewRef, setMapViewRef] = useState<CesiumViewer>();
|
|
129
141
|
const [projection, setProjection] = useState<Proj>();
|
|
130
142
|
const [showMousePosition, setShowMousePosition] = useState<boolean>();
|
|
143
|
+
const [showZoomLevel, setShowZoomLevel] = useState<boolean>();
|
|
131
144
|
const [showScale, setShowScale] = useState<boolean>();
|
|
132
145
|
const [locale, setLocale] = useState<{ [key: string]: string }>();
|
|
133
146
|
const [cameraState, setCameraState] = useState<ICameraState | undefined>();
|
|
@@ -209,6 +222,9 @@ export const CesiumMap: React.FC<CesiumMapProps> = (props) => {
|
|
|
209
222
|
|
|
210
223
|
useEffect(() => {
|
|
211
224
|
if (mapViewRef) {
|
|
225
|
+
mapViewRef.shouldOptimizedTileRequests =
|
|
226
|
+
props.useOptimizedTileRequests ?? false;
|
|
227
|
+
|
|
212
228
|
mapViewRef.layersManager = new LayerManager(
|
|
213
229
|
mapViewRef,
|
|
214
230
|
props.legends?.mapLegendsExtractor,
|
|
@@ -219,8 +235,16 @@ export const CesiumMap: React.FC<CesiumMapProps> = (props) => {
|
|
|
219
235
|
}
|
|
220
236
|
);
|
|
221
237
|
}
|
|
238
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
222
239
|
}, [mapViewRef]);
|
|
223
240
|
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
if (mapViewRef) {
|
|
243
|
+
mapViewRef.shouldOptimizedTileRequests =
|
|
244
|
+
props.useOptimizedTileRequests ?? false;
|
|
245
|
+
}
|
|
246
|
+
}, [props.useOptimizedTileRequests, mapViewRef]);
|
|
247
|
+
|
|
224
248
|
useEffect(() => {
|
|
225
249
|
setSceneModes(
|
|
226
250
|
props.sceneModes ?? [
|
|
@@ -254,6 +278,10 @@ export const CesiumMap: React.FC<CesiumMapProps> = (props) => {
|
|
|
254
278
|
setShowMousePosition(props.showMousePosition ?? true);
|
|
255
279
|
}, [props.showMousePosition]);
|
|
256
280
|
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
setShowZoomLevel(props.showZoomLevel ?? true);
|
|
283
|
+
}, [props.showZoomLevel]);
|
|
284
|
+
|
|
257
285
|
useEffect(() => {
|
|
258
286
|
setShowScale(props.showScale ?? true);
|
|
259
287
|
}, [props.showScale]);
|
|
@@ -391,6 +419,11 @@ export const CesiumMap: React.FC<CesiumMapProps> = (props) => {
|
|
|
391
419
|
) : (
|
|
392
420
|
<></>
|
|
393
421
|
)}
|
|
422
|
+
{showZoomLevel === true ? (
|
|
423
|
+
<ZoomLevelTrackerTool locale={locale} />
|
|
424
|
+
) : (
|
|
425
|
+
<></>
|
|
426
|
+
)}
|
|
394
427
|
{showScale === true ? <ScaleTrackerTool locale={locale} /> : <></>}
|
|
395
428
|
</Box>
|
|
396
429
|
</>,
|
|
@@ -171,7 +171,11 @@ export const MapWithSettings: Story = () => {
|
|
|
171
171
|
center={center}
|
|
172
172
|
zoom={14}
|
|
173
173
|
imageryProvider={false}
|
|
174
|
-
sceneModes={[
|
|
174
|
+
sceneModes={[
|
|
175
|
+
CesiumSceneMode.SCENE3D,
|
|
176
|
+
CesiumSceneMode.SCENE2D,
|
|
177
|
+
CesiumSceneMode.COLUMBUS_VIEW,
|
|
178
|
+
]}
|
|
175
179
|
baseMaps={BASE_MAPS}
|
|
176
180
|
>
|
|
177
181
|
<CesiumXYZLayer options={optionsXYZSanDiego} />
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Cartesian3,
|
|
4
|
+
Math as CesiumMath,
|
|
5
|
+
WebMercatorProjection,
|
|
6
|
+
ScreenSpaceEventType,
|
|
7
|
+
} from 'cesium';
|
|
3
8
|
import { CesiumViewer, useCesiumMap } from '../map';
|
|
4
9
|
|
|
5
10
|
import './coordinates-tracker.tool.css';
|
|
@@ -160,7 +160,10 @@ export const ScaleTrackerTool: React.FC<RScaleTrackerToolProps> = (props) => {
|
|
|
160
160
|
|
|
161
161
|
return (): void => {
|
|
162
162
|
try {
|
|
163
|
-
|
|
163
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition*/
|
|
164
|
+
if (get(mapViewer, '_cesiumWidget') != undefined) {
|
|
165
|
+
mapViewer.camera.moveEnd.removeEventListener(setFromEvent);
|
|
166
|
+
}
|
|
164
167
|
} catch (e) {
|
|
165
168
|
console.log('CESIUM camera "moveEnd" remove listener failed', e);
|
|
166
169
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.zoomLevel {
|
|
2
|
+
z-index: 2000;
|
|
3
|
+
background-color: var(--mdc-theme-surface);
|
|
4
|
+
border-radius: 2px;
|
|
5
|
+
padding: 2px;
|
|
6
|
+
height: 24px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.zoomLevelValue {
|
|
10
|
+
margin: 2px;
|
|
11
|
+
font-size: 14px;
|
|
12
|
+
line-height: 10px;
|
|
13
|
+
text-align: center;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.zoomLevelLabel {
|
|
17
|
+
font-size: 8px;
|
|
18
|
+
line-height: 8px;
|
|
19
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { get } from 'lodash';
|
|
3
|
+
import { PerspectiveOffCenterFrustum } from 'cesium';
|
|
4
|
+
import { CesiumViewer, useCesiumMap } from '../map';
|
|
5
|
+
import { CesiumSceneMode } from '../map.types';
|
|
6
|
+
|
|
7
|
+
import './zoom_level-tracker.tool.css';
|
|
8
|
+
|
|
9
|
+
export interface RZoomLevelTrackerToolProps {
|
|
10
|
+
locale?: { [key: string]: string };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* eslint-disable @typescript-eslint/no-magic-numbers, @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
|
|
14
|
+
const detectZoomLevel = (distance: number, viewer: CesiumViewer) => {
|
|
15
|
+
const MAX_ZOOM_LEVEL = 19;
|
|
16
|
+
const tileProvider = get(viewer.scene.globe, '_surface.tileProvider') as any;
|
|
17
|
+
const quadtree = tileProvider._quadtree;
|
|
18
|
+
const drawingBufferHeight = viewer.canvas.height;
|
|
19
|
+
const sseDenominator = get(viewer.camera.frustum, 'sseDenominator');
|
|
20
|
+
|
|
21
|
+
for (let level = 0; level <= MAX_ZOOM_LEVEL; level++) {
|
|
22
|
+
const maxGeometricError = tileProvider.getLevelMaximumGeometricError(level);
|
|
23
|
+
const error =
|
|
24
|
+
(maxGeometricError * drawingBufferHeight) / (distance * sseDenominator);
|
|
25
|
+
if (error < quadtree.maximumScreenSpaceError) {
|
|
26
|
+
return level;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const getZoomLevelHeights = (precision: number, viewer: CesiumViewer) => {
|
|
34
|
+
precision = precision || 10;
|
|
35
|
+
|
|
36
|
+
const result = [];
|
|
37
|
+
let step = 100000.0;
|
|
38
|
+
let currentZoomLevel = 0;
|
|
39
|
+
for (let height = 100000000.0; height > step; height = height - step) {
|
|
40
|
+
const level = detectZoomLevel(height, viewer);
|
|
41
|
+
if (level === null) {
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (level !== currentZoomLevel) {
|
|
46
|
+
let minHeight = height;
|
|
47
|
+
let maxHeight = height + step;
|
|
48
|
+
while (maxHeight - minHeight > precision) {
|
|
49
|
+
height = minHeight + (maxHeight - minHeight) / 2;
|
|
50
|
+
if (detectZoomLevel(height, viewer) === level) {
|
|
51
|
+
minHeight = height;
|
|
52
|
+
} else {
|
|
53
|
+
maxHeight = height;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
result.push({
|
|
58
|
+
level: level,
|
|
59
|
+
height: Math.round(height),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
currentZoomLevel = level;
|
|
63
|
+
|
|
64
|
+
if (result.length >= 2) {
|
|
65
|
+
step = (result[result.length - 2].height - height) / 1000.0;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return result;
|
|
71
|
+
};
|
|
72
|
+
/* eslint-enable @typescript-eslint/no-magic-numbers */
|
|
73
|
+
|
|
74
|
+
export const ZoomLevelTrackerTool: React.FC<RZoomLevelTrackerToolProps> = (
|
|
75
|
+
props
|
|
76
|
+
) => {
|
|
77
|
+
const mapViewer: CesiumViewer = useCesiumMap();
|
|
78
|
+
const [zoomLevel, setZoomLevel] = useState(1);
|
|
79
|
+
const zoomLevelHeights = getZoomLevelHeights(1, mapViewer);
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const calculateZoomLevel = () => {
|
|
83
|
+
const camera = mapViewer.camera;
|
|
84
|
+
const ORTHOPHOTO_HEIGHT_FRUSTRUM_FACTOR = 0.5;
|
|
85
|
+
let cameraHeight = 0;
|
|
86
|
+
|
|
87
|
+
switch (mapViewer.scene.mode) {
|
|
88
|
+
case CesiumSceneMode.SCENE3D:
|
|
89
|
+
cameraHeight = mapViewer.scene.mapProjection.ellipsoid.cartesianToCartographic(
|
|
90
|
+
camera.positionWC
|
|
91
|
+
).height;
|
|
92
|
+
break;
|
|
93
|
+
case CesiumSceneMode.SCENE2D:
|
|
94
|
+
cameraHeight =
|
|
95
|
+
((camera.frustum as PerspectiveOffCenterFrustum).right -
|
|
96
|
+
(camera.frustum as PerspectiveOffCenterFrustum).left) *
|
|
97
|
+
ORTHOPHOTO_HEIGHT_FRUSTRUM_FACTOR;
|
|
98
|
+
break;
|
|
99
|
+
case CesiumSceneMode.COLUMBUS_VIEW:
|
|
100
|
+
cameraHeight = camera.position.z;
|
|
101
|
+
break;
|
|
102
|
+
default:
|
|
103
|
+
cameraHeight = 0;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const closestZoom = zoomLevelHeights.reduce((a, b) => {
|
|
108
|
+
return Math.abs(b.height - cameraHeight) <
|
|
109
|
+
Math.abs(a.height - cameraHeight)
|
|
110
|
+
? b
|
|
111
|
+
: a;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
setZoomLevel(closestZoom.level);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
mapViewer.camera.moveEnd.addEventListener(calculateZoomLevel);
|
|
118
|
+
|
|
119
|
+
return (): void => {
|
|
120
|
+
try {
|
|
121
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition*/
|
|
122
|
+
if (get(mapViewer, '_cesiumWidget') != undefined) {
|
|
123
|
+
mapViewer.camera.moveEnd.removeEventListener(calculateZoomLevel);
|
|
124
|
+
}
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.log(
|
|
127
|
+
'CESIUM camera "moveEnd"(from zoom tracker) remove listener failed',
|
|
128
|
+
e
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}, [mapViewer]);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div className="zoomLevel">
|
|
136
|
+
<div className="zoomLevelValue">{zoomLevel}</div>
|
|
137
|
+
<div className="zoomLevelLabel">
|
|
138
|
+
{get(props.locale, 'ZOOM_LABEL') ?? 'zoom'}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
};
|