@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.
- package/README.md +24 -23
- package/build/postinstall.js +16 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/ol.d.ts +10 -1
- package/dist/src/featureProvider/abstractFeatureProvider.d.ts +8 -0
- package/dist/src/featureProvider/abstractFeatureProvider.js +10 -0
- package/dist/src/featureProvider/abstractFeatureProvider.js.map +1 -1
- package/dist/src/featureProvider/jsonAttributeProvider.d.ts +2 -1
- package/dist/src/featureProvider/jsonAttributeProvider.js +5 -2
- package/dist/src/featureProvider/jsonAttributeProvider.js.map +1 -1
- package/dist/src/featureProvider/mapboxFeatureProvider.d.ts +18 -0
- package/dist/src/featureProvider/mapboxFeatureProvider.js +49 -0
- package/dist/src/featureProvider/mapboxFeatureProvider.js.map +1 -0
- package/dist/src/featureProvider/wmsFeatureProvider.d.ts +11 -0
- package/dist/src/featureProvider/wmsFeatureProvider.js +19 -8
- package/dist/src/featureProvider/wmsFeatureProvider.js.map +1 -1
- package/dist/src/interaction/eventHandler.js +3 -1
- package/dist/src/interaction/eventHandler.js.map +1 -1
- package/dist/src/interaction/featureAtPixelInteraction.js +5 -0
- package/dist/src/interaction/featureAtPixelInteraction.js.map +1 -1
- package/dist/src/interaction/featureProviderInteraction.d.ts +10 -1
- package/dist/src/interaction/featureProviderInteraction.js +19 -3
- package/dist/src/interaction/featureProviderInteraction.js.map +1 -1
- package/dist/src/layer/cesium/mapboxStyleCesiumImpl.d.ts +25 -0
- package/dist/src/layer/cesium/mapboxStyleCesiumImpl.js +58 -0
- package/dist/src/layer/cesium/mapboxStyleCesiumImpl.js.map +1 -0
- package/dist/src/layer/cesium/mapboxStyleImageryProvider.d.ts +26 -0
- package/dist/src/layer/cesium/mapboxStyleImageryProvider.js +147 -0
- package/dist/src/layer/cesium/mapboxStyleImageryProvider.js.map +1 -0
- package/dist/src/layer/cesium/vectorTileImageryProvider.d.ts +1 -1
- package/dist/src/layer/cesium/vectorTileImageryProvider.js.map +1 -1
- package/dist/src/layer/i3sLayer.d.ts +2 -10
- package/dist/src/layer/i3sLayer.js +9 -8
- package/dist/src/layer/i3sLayer.js.map +1 -1
- package/dist/src/layer/mapboxStyleLayer.d.ts +55 -0
- package/dist/src/layer/mapboxStyleLayer.js +176 -0
- package/dist/src/layer/mapboxStyleLayer.js.map +1 -0
- package/dist/src/layer/openlayers/layerOpenlayersImpl.d.ts +3 -4
- package/dist/src/layer/openlayers/layerOpenlayersImpl.js +11 -3
- package/dist/src/layer/openlayers/layerOpenlayersImpl.js.map +1 -1
- package/dist/src/layer/openlayers/mapboxStyleOpenlayersImpl.d.ts +15 -0
- package/dist/src/layer/openlayers/mapboxStyleOpenlayersImpl.js +49 -0
- package/dist/src/layer/openlayers/mapboxStyleOpenlayersImpl.js.map +1 -0
- package/dist/src/map/baseCesiumMap.d.ts +17 -0
- package/dist/src/map/baseCesiumMap.js +36 -0
- package/dist/src/map/baseCesiumMap.js.map +1 -1
- package/dist/src/map/baseOLMap.d.ts +6 -4
- package/dist/src/map/baseOLMap.js +3 -2
- package/dist/src/map/baseOLMap.js.map +1 -1
- package/dist/src/map/cesiumMap.d.ts +3 -3
- package/dist/src/map/cesiumMap.js +2 -3
- package/dist/src/map/cesiumMap.js.map +1 -1
- package/dist/src/map/panoramaMap.d.ts +2 -2
- package/dist/src/map/panoramaMap.js +1 -2
- package/dist/src/map/panoramaMap.js.map +1 -1
- package/dist/src/map/vcsMap.d.ts +3 -3
- package/dist/src/map/vcsMap.js +11 -3
- package/dist/src/map/vcsMap.js.map +1 -1
- package/index.ts +2 -0
- package/package.json +4 -3
- package/src/featureProvider/abstractFeatureProvider.ts +12 -0
- package/src/featureProvider/jsonAttributeProvider.ts +10 -2
- package/src/featureProvider/mapboxFeatureProvider.ts +74 -0
- package/src/featureProvider/wmsFeatureProvider.ts +29 -8
- package/src/interaction/eventHandler.ts +3 -1
- package/src/interaction/featureAtPixelInteraction.ts +5 -0
- package/src/interaction/featureProviderInteraction.ts +37 -8
- package/src/layer/cesium/mapboxStyleCesiumImpl.ts +95 -0
- package/src/layer/cesium/mapboxStyleImageryProvider.ts +214 -0
- package/src/layer/cesium/vectorTileImageryProvider.ts +2 -1
- package/src/layer/i3sLayer.ts +12 -14
- package/src/layer/mapboxStyleLayer.ts +244 -0
- package/src/layer/openlayers/layerOpenlayersImpl.ts +21 -10
- package/src/layer/openlayers/mapboxStyleOpenlayersImpl.ts +68 -0
- package/src/map/baseCesiumMap.ts +57 -0
- package/src/map/baseOLMap.ts +16 -14
- package/src/map/cesiumMap.ts +7 -6
- package/src/map/panoramaMap.ts +3 -3
- package/src/map/vcsMap.ts +16 -10
- 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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
package/src/layer/i3sLayer.ts
CHANGED
|
@@ -33,7 +33,7 @@ export type I3SOptions = LayerOptions & {
|
|
|
33
33
|
calculateNormals?: boolean;
|
|
34
34
|
showFeatures?: boolean;
|
|
35
35
|
cesium3dTilesetOptions?: Cesium3DTileset.ConstructorOptions;
|
|
36
|
-
lightColor?:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|