@vcmap/core 5.0.0-rc.2 → 5.0.0-rc.5
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/index.d.ts +287 -95
- package/index.js +11 -3
- package/package.json +5 -10
- package/src/vcs/vcm/category/appBackedCategory.js +41 -0
- package/src/vcs/vcm/category/category.js +374 -0
- package/src/vcs/vcm/category/categoryCollection.js +145 -0
- package/src/vcs/vcm/context.js +73 -0
- package/src/vcs/vcm/interaction/coordinateAtPixel.js +1 -1
- package/src/vcs/vcm/layer/cesiumTileset.js +1 -1
- package/src/vcs/vcm/layer/featureStore.js +3 -3
- package/src/vcs/vcm/layer/featureStoreChanges.js +89 -73
- package/src/vcs/vcm/layer/geojson.js +3 -5
- package/src/vcs/vcm/layer/geojsonHelpers.js +3 -3
- package/src/vcs/vcm/layer/oblique/obliqueHelpers.js +2 -2
- package/src/vcs/vcm/layer/singleImage.js +2 -1
- package/src/vcs/vcm/layer/terrainHelpers.js +4 -8
- package/src/vcs/vcm/layer/tileProvider/mvtTileProvider.js +3 -3
- package/src/vcs/vcm/layer/tileProvider/staticGeojsonTileProvider.js +3 -3
- package/src/vcs/vcm/layer/tileProvider/urlTemplateTileProvider.js +3 -3
- package/src/vcs/vcm/layer/vector.js +1 -1
- package/src/vcs/vcm/layer/vectorHelpers.js +4 -4
- package/src/vcs/vcm/layer/wfs.js +5 -5
- package/src/vcs/vcm/maps/cameraLimiter.js +5 -5
- package/src/vcs/vcm/maps/map.js +26 -11
- package/src/vcs/vcm/maps/oblique.js +8 -8
- package/src/vcs/vcm/object.js +1 -1
- package/src/vcs/vcm/oblique/ObliqueCollection.js +83 -31
- package/src/vcs/vcm/oblique/ObliqueDataSet.js +67 -24
- package/src/vcs/vcm/oblique/ObliqueImage.js +1 -1
- package/src/vcs/vcm/oblique/ObliqueImageMeta.js +2 -2
- package/src/vcs/vcm/oblique/ObliqueProvider.js +10 -7
- package/src/vcs/vcm/oblique/helpers.js +12 -40
- package/src/vcs/vcm/oblique/parseImageJson.js +17 -9
- package/src/vcs/vcm/util/clipping/clippingPlaneHelper.js +4 -4
- package/src/vcs/vcm/util/extent.js +16 -9
- package/src/vcs/vcm/util/featureProvider/featureProviderHelpers.js +3 -4
- package/src/vcs/vcm/util/featureProvider/wmsFeatureProvider.js +11 -6
- package/src/vcs/vcm/util/featureconverter/extent3D.js +181 -0
- package/src/vcs/vcm/util/fetch.js +32 -0
- package/src/vcs/vcm/util/indexedCollection.js +23 -0
- package/src/vcs/vcm/util/layerCollection.js +10 -5
- package/src/vcs/vcm/util/overrideCollection.js +224 -0
- package/src/vcs/vcm/util/projection.js +37 -3
- package/src/vcs/vcm/util/style/declarativeStyleItem.js +2 -0
- package/src/vcs/vcm/util/style/styleFactory.js +1 -1
- package/src/vcs/vcm/util/style/styleItem.js +2 -0
- package/src/vcs/vcm/util/style/vectorStyleItem.js +2 -0
- package/src/vcs/vcm/util/viewpoint.js +3 -0
- package/src/vcs/vcm/vcsApp.js +360 -0
- package/src/vcs/vcm/vcsAppContextHelpers.js +108 -0
- package/src/vcs/vcm/util/featureconverter/extent3d.js +0 -154
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import Projection from './projection.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @typedef {
|
|
5
|
-
* @property {
|
|
4
|
+
* @typedef {Object} ExtentOptions
|
|
5
|
+
* @property {string} [type]
|
|
6
|
+
* @property {import("ol/extent").Extent|undefined} [coordinates] - if not specified, the extent of the projection is used
|
|
7
|
+
* @property {ProjectionOptions} [projection] - if not specified the default projection is assumed
|
|
6
8
|
* @api
|
|
7
9
|
*/
|
|
8
10
|
|
|
@@ -35,16 +37,17 @@ function checkExtentValidity(extent) {
|
|
|
35
37
|
* @api
|
|
36
38
|
*/
|
|
37
39
|
class Extent {
|
|
40
|
+
/**
|
|
41
|
+
* @type {string}
|
|
42
|
+
*/
|
|
43
|
+
static get className() { return 'vcs.vcm.util.Extent'; }
|
|
44
|
+
|
|
38
45
|
/**
|
|
39
46
|
* @param {ExtentOptions=} options object
|
|
40
47
|
*/
|
|
41
48
|
constructor(options = {}) {
|
|
42
49
|
/** @type {Projection} */
|
|
43
|
-
this.projection = new Projection(
|
|
44
|
-
epsg: options.epsg,
|
|
45
|
-
proj4: options.proj4,
|
|
46
|
-
alias: options.alias,
|
|
47
|
-
});
|
|
50
|
+
this.projection = new Projection(options.projection);
|
|
48
51
|
|
|
49
52
|
/** @type {import("ol/extent").Extent|null} */
|
|
50
53
|
this.extent = options.coordinates || this.projection.proj.getExtent();
|
|
@@ -79,7 +82,11 @@ class Extent {
|
|
|
79
82
|
* @returns {ExtentOptions}
|
|
80
83
|
*/
|
|
81
84
|
toJSON() {
|
|
82
|
-
return {
|
|
85
|
+
return {
|
|
86
|
+
coordinates: this.extent.slice(),
|
|
87
|
+
projection: this.projection.toJSON(),
|
|
88
|
+
type: Extent.className,
|
|
89
|
+
};
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
/**
|
|
@@ -112,7 +119,7 @@ class Extent {
|
|
|
112
119
|
* @api
|
|
113
120
|
*/
|
|
114
121
|
static validateOptions(options) {
|
|
115
|
-
return Projection.validateOptions(options) && checkExtentValidity(options.coordinates);
|
|
122
|
+
return Projection.validateOptions(options.projection || {}) && checkExtentValidity(options.coordinates);
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getCenter } from 'ol/extent.js';
|
|
2
2
|
import Projection from '../projection.js';
|
|
3
|
-
import
|
|
3
|
+
import Extent3D from '../featureconverter/extent3D.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @param {VectorClickedObject} feature
|
|
@@ -32,9 +32,8 @@ export function getGenericFeatureFromProvidedFeature(feature, layer) {
|
|
|
32
32
|
|
|
33
33
|
let heightOffset = clickedPosition.height;
|
|
34
34
|
if (!isModel) {
|
|
35
|
-
const extent =
|
|
36
|
-
|
|
37
|
-
heightOffset = max;
|
|
35
|
+
const extent = Extent3D.fromGeometry(geometry);
|
|
36
|
+
heightOffset = Number.isFinite(extent.maxZ) ? extent.maxZ : 0;
|
|
38
37
|
}
|
|
39
38
|
const relativeToGround = !isModel && feature.get('olcs_altitudeMode') === 'relativeToGround';
|
|
40
39
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
1
|
import GML2 from 'ol/format/GML2.js';
|
|
3
2
|
import WFS from 'ol/format/WFS.js';
|
|
4
3
|
import GeoJSON from 'ol/format/GeoJSON.js';
|
|
@@ -10,6 +9,7 @@ import AbstractFeatureProvider from './abstractFeatureProvider.js';
|
|
|
10
9
|
import Projection, { mercatorProjection } from '../projection.js';
|
|
11
10
|
import { getWMSSource } from '../../layer/wmsHelpers.js';
|
|
12
11
|
import Extent from '../extent.js';
|
|
12
|
+
import { requestJson } from '../fetch.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @typedef {AbstractFeatureProviderOptions} WMSFeatureProviderOptions
|
|
@@ -155,12 +155,11 @@ class WMSFeatureProvider extends AbstractFeatureProvider {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
|
-
* @param {import("
|
|
158
|
+
* @param {import("ol/format/GeoJSON").GeoJSONObject} data
|
|
159
159
|
* @param {import("ol/coordinate").Coordinate} coordinate
|
|
160
160
|
* @returns {Array<import("ol").Feature<import("ol/geom/Geometry").default>>}
|
|
161
161
|
*/
|
|
162
|
-
featureResponseCallback(
|
|
163
|
-
const { data } = response;
|
|
162
|
+
featureResponseCallback(data, coordinate) {
|
|
164
163
|
/** @type {Array<import("ol").Feature<import("ol/geom/Geometry").default>>} */
|
|
165
164
|
let features;
|
|
166
165
|
|
|
@@ -211,8 +210,14 @@ class WMSFeatureProvider extends AbstractFeatureProvider {
|
|
|
211
210
|
);
|
|
212
211
|
|
|
213
212
|
if (url) {
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
let data;
|
|
214
|
+
try {
|
|
215
|
+
data = await requestJson(url);
|
|
216
|
+
} catch (ex) {
|
|
217
|
+
this.getLogger().error(`Failed fetching WMS FeatureInfo ${url}`);
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
return this.featureResponseCallback(data, coordinate)
|
|
216
221
|
.map(f => this.getProviderFeature(f));
|
|
217
222
|
}
|
|
218
223
|
return [];
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import GeometryType from 'ol/geom/GeometryType.js';
|
|
2
|
+
import { check } from '@vcsuite/check';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Extent3D {
|
|
6
|
+
/**
|
|
7
|
+
* @param {Array<number>} array
|
|
8
|
+
* @returns {Extent3D}
|
|
9
|
+
*/
|
|
10
|
+
static fromArray(array) {
|
|
11
|
+
check(array, [Number]);
|
|
12
|
+
check(array.length, 6);
|
|
13
|
+
return new Extent3D(array[0], array[1], array[2], array[3], array[4], array[5]);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {import("ol/geom").Geometry} geometry
|
|
18
|
+
* @returns {Extent3D}
|
|
19
|
+
*/
|
|
20
|
+
static fromGeometry(geometry) {
|
|
21
|
+
const extent = new Extent3D();
|
|
22
|
+
extent.extendWithGeometry(geometry);
|
|
23
|
+
return extent;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {VectorHeightInfo} heightInfo
|
|
28
|
+
* @returns {Extent3D}
|
|
29
|
+
*/
|
|
30
|
+
static fromHeightInfo(heightInfo) {
|
|
31
|
+
const extent = new Extent3D();
|
|
32
|
+
extent.extendWithHeightInfo(heightInfo);
|
|
33
|
+
return extent;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {number} minX
|
|
38
|
+
* @param {number} minY
|
|
39
|
+
* @param {number} minZ
|
|
40
|
+
* @param {number} maxX
|
|
41
|
+
* @param {number} maxY
|
|
42
|
+
* @param {number} maxZ
|
|
43
|
+
*/
|
|
44
|
+
constructor(minX = Infinity, minY = Infinity, minZ = Infinity, maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity) {
|
|
45
|
+
/**
|
|
46
|
+
* @type {number}
|
|
47
|
+
*/
|
|
48
|
+
this.minX = minX;
|
|
49
|
+
/**
|
|
50
|
+
* @type {number}
|
|
51
|
+
*/
|
|
52
|
+
this.minY = minY;
|
|
53
|
+
/**
|
|
54
|
+
* @type {number}
|
|
55
|
+
*/
|
|
56
|
+
this.minZ = minZ;
|
|
57
|
+
/**
|
|
58
|
+
* @type {number}
|
|
59
|
+
*/
|
|
60
|
+
this.maxX = maxX;
|
|
61
|
+
/**
|
|
62
|
+
* @type {number}
|
|
63
|
+
*/
|
|
64
|
+
this.maxY = maxY;
|
|
65
|
+
/**
|
|
66
|
+
* @type {number}
|
|
67
|
+
*/
|
|
68
|
+
this.maxZ = maxZ;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {import("ol/geom").Geometry} geometry
|
|
73
|
+
*/
|
|
74
|
+
extendWithGeometry(geometry) {
|
|
75
|
+
if (geometry.getType() === GeometryType.GEOMETRY_COLLECTION) {
|
|
76
|
+
/** @type {import("ol/geom/GeometryCollection").default} */ (geometry)
|
|
77
|
+
.getGeometriesArray().forEach((geom) => { this.extendWithGeometry(geom); });
|
|
78
|
+
} else if (geometry.getType() === GeometryType.CIRCLE) {
|
|
79
|
+
const flatCoordinates = /** @type {import("ol/geom/Circle").default} */ (geometry).getFlatCoordinates();
|
|
80
|
+
const stride = /** @type {import("ol/geom/Circle").default} */ (geometry).getStride();
|
|
81
|
+
const radius = flatCoordinates[stride] - flatCoordinates[0];
|
|
82
|
+
this.extendXY(
|
|
83
|
+
flatCoordinates[0] - radius,
|
|
84
|
+
flatCoordinates[1] - radius,
|
|
85
|
+
);
|
|
86
|
+
this.extendXY(
|
|
87
|
+
flatCoordinates[0] + radius,
|
|
88
|
+
flatCoordinates[1] + radius,
|
|
89
|
+
);
|
|
90
|
+
if (stride > 2) {
|
|
91
|
+
this.extendZ(flatCoordinates[2]);
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
const flatCoordinates = /** @type {import("ol/geom/SimpleGeometry").default} */ (geometry).getFlatCoordinates();
|
|
95
|
+
const stride = /** @type {import("ol/geom/SimpleGeometry").default} */ (geometry).getStride();
|
|
96
|
+
this.extendFlatCoordinates(flatCoordinates, stride);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {VectorHeightInfo} heightInfo
|
|
102
|
+
*/
|
|
103
|
+
extendWithHeightInfo(heightInfo) {
|
|
104
|
+
if (heightInfo.extruded) {
|
|
105
|
+
const calculatedFeatureMaxHeight =
|
|
106
|
+
heightInfo.groundLevel + heightInfo.storeyHeightsAboveGround.reduce((accumulator, currentValue) => {
|
|
107
|
+
return accumulator + currentValue;
|
|
108
|
+
}, 0);
|
|
109
|
+
this.extendZ(calculatedFeatureMaxHeight);
|
|
110
|
+
const calculatedFeatureMinHeight =
|
|
111
|
+
heightInfo.groundLevel - heightInfo.storeyHeightsBelowGround.reduce((accumulator, currentValue) => {
|
|
112
|
+
return accumulator + currentValue;
|
|
113
|
+
}, 0);
|
|
114
|
+
this.extendZ(calculatedFeatureMinHeight);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {number} x
|
|
120
|
+
* @param {number} y
|
|
121
|
+
* @param {number} z
|
|
122
|
+
*/
|
|
123
|
+
extendXYZ(x, y, z) {
|
|
124
|
+
this.minX = Math.min(this.minX, x);
|
|
125
|
+
this.minY = Math.min(this.minY, y);
|
|
126
|
+
this.minZ = Math.min(this.minZ, z);
|
|
127
|
+
this.maxX = Math.max(this.maxX, x);
|
|
128
|
+
this.maxY = Math.max(this.maxY, y);
|
|
129
|
+
this.maxZ = Math.max(this.maxZ, z);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {number} x
|
|
134
|
+
* @param {number} y
|
|
135
|
+
*/
|
|
136
|
+
extendXY(x, y) {
|
|
137
|
+
this.minX = Math.min(this.minX, x);
|
|
138
|
+
this.minY = Math.min(this.minY, y);
|
|
139
|
+
this.maxX = Math.max(this.maxX, x);
|
|
140
|
+
this.maxY = Math.max(this.maxY, y);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @param {number} z
|
|
145
|
+
*/
|
|
146
|
+
extendZ(z) {
|
|
147
|
+
this.minZ = Math.min(this.minZ, z);
|
|
148
|
+
this.maxZ = Math.max(this.maxZ, z);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {Array<number>} flatCoordinates
|
|
153
|
+
* @param {number} stride
|
|
154
|
+
*/
|
|
155
|
+
extendFlatCoordinates(flatCoordinates, stride) {
|
|
156
|
+
const { length } = flatCoordinates;
|
|
157
|
+
for (let offset = 0; offset < length; offset += stride) {
|
|
158
|
+
if (stride > 2) {
|
|
159
|
+
this.extendXYZ(flatCoordinates[offset], flatCoordinates[offset + 1], flatCoordinates[offset + 2]);
|
|
160
|
+
} else {
|
|
161
|
+
this.extendXY(flatCoordinates[offset], flatCoordinates[offset + 1]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @returns {import("ol/extent").Extent}
|
|
168
|
+
*/
|
|
169
|
+
to2D() {
|
|
170
|
+
return [this.minX, this.minY, this.maxX, this.maxY];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @returns {Array<number>}
|
|
175
|
+
*/
|
|
176
|
+
toArray() {
|
|
177
|
+
return [this.minX, this.minY, this.minZ, this.maxX, this.maxY, this.maxZ];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export default Extent3D;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} url
|
|
3
|
+
* @param {RequestInit=} init
|
|
4
|
+
* @returns {Promise<Response>}
|
|
5
|
+
*/
|
|
6
|
+
export async function requestUrl(url, init) {
|
|
7
|
+
const response = await fetch(url, init);
|
|
8
|
+
if (!response.ok) {
|
|
9
|
+
throw new Error(`Failed fetching url ${url} with status: ${response.status}`);
|
|
10
|
+
}
|
|
11
|
+
return response;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} url
|
|
16
|
+
* @param {RequestInit=} init
|
|
17
|
+
* @returns {Promise<any>}
|
|
18
|
+
*/
|
|
19
|
+
export async function requestJson(url, init) {
|
|
20
|
+
const response = await requestUrl(url, init);
|
|
21
|
+
return response.json();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} url
|
|
26
|
+
* @param {RequestInit=} init
|
|
27
|
+
* @returns {Promise<ArrayBuffer>}
|
|
28
|
+
*/
|
|
29
|
+
export async function requestArrayBuffer(url, init) {
|
|
30
|
+
const response = await requestUrl(url, init);
|
|
31
|
+
return response.arrayBuffer();
|
|
32
|
+
}
|
|
@@ -42,8 +42,22 @@ class IndexedCollection extends Collection {
|
|
|
42
42
|
* @api
|
|
43
43
|
*/
|
|
44
44
|
this.moved = new VcsEvent();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @type {symbol}
|
|
48
|
+
* @private
|
|
49
|
+
*/
|
|
50
|
+
this._previousIndexSymbol = Symbol('previousIndex');
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Get the symbol which is attached to an item prior to its removal. If an item is removed, the current index of the item
|
|
55
|
+
* is set on the item with this symbol.
|
|
56
|
+
* @type {symbol}
|
|
57
|
+
* @readonly
|
|
58
|
+
*/
|
|
59
|
+
get previousIndexSymbol() { return this._previousIndexSymbol; }
|
|
60
|
+
|
|
47
61
|
/**
|
|
48
62
|
* Returns an item at index.
|
|
49
63
|
* @param {number} index
|
|
@@ -76,6 +90,15 @@ class IndexedCollection extends Collection {
|
|
|
76
90
|
return null;
|
|
77
91
|
}
|
|
78
92
|
|
|
93
|
+
/**
|
|
94
|
+
* @inheritDoc
|
|
95
|
+
* @param {T} item
|
|
96
|
+
*/
|
|
97
|
+
remove(item) {
|
|
98
|
+
item[this._previousIndexSymbol] = this._array.indexOf(item);
|
|
99
|
+
super.remove(item);
|
|
100
|
+
}
|
|
101
|
+
|
|
79
102
|
/**
|
|
80
103
|
* @param {T} item
|
|
81
104
|
* @param {number} itemIndex
|
|
@@ -44,9 +44,6 @@ class LayerCollection extends IndexedCollection {
|
|
|
44
44
|
this._layerEventListeners = {};
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* A symbol to describe the local z index of a layer. The local z index must not equal the layers z index, but is
|
|
48
|
-
* always consistent in comparison to the neighbouring layers. If a layer is moved other then by z index, the collection
|
|
49
|
-
* ensures consistency by setting a new local z index if needed.
|
|
50
47
|
* @type {symbol}
|
|
51
48
|
* @private
|
|
52
49
|
*/
|
|
@@ -67,6 +64,15 @@ class LayerCollection extends IndexedCollection {
|
|
|
67
64
|
this.exclusiveManager = new ExclusiveManager();
|
|
68
65
|
}
|
|
69
66
|
|
|
67
|
+
/**
|
|
68
|
+
* A symbol to describe the local z index of a layer. The local z index must not equal the layers z index, but is
|
|
69
|
+
* always consistent in comparison to the neighbouring layers. If a layer is moved other then by z index, the collection
|
|
70
|
+
* ensures consistency by setting a new local z index if needed.
|
|
71
|
+
* @type {symbol}
|
|
72
|
+
* @readonly
|
|
73
|
+
*/
|
|
74
|
+
get zIndexSymbol() { return this._zIndexSymbol; }
|
|
75
|
+
|
|
70
76
|
/**
|
|
71
77
|
* @param {import("@vcmap/core").Layer} layer
|
|
72
78
|
* @private
|
|
@@ -116,10 +122,9 @@ class LayerCollection extends IndexedCollection {
|
|
|
116
122
|
_zIndexChanged(layer) {
|
|
117
123
|
const currentIndex = this.indexOf(layer);
|
|
118
124
|
if (currentIndex > -1) {
|
|
119
|
-
const increased = layer[this._zIndexSymbol] < layer.zIndex;
|
|
120
125
|
layer[this._zIndexSymbol] = layer.zIndex;
|
|
121
126
|
let zIndexPosition = this._findZIndexPosition(layer.zIndex);
|
|
122
|
-
if (
|
|
127
|
+
if (zIndexPosition > 0 && zIndexPosition > currentIndex) {
|
|
123
128
|
zIndexPosition -= 1; // remove self from count
|
|
124
129
|
}
|
|
125
130
|
zIndexPosition = zIndexPosition != null ? zIndexPosition : this._array.length - 1;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/* eslint no-underscore-dangle: ["error", { "allow": ["_array"] }] */
|
|
2
|
+
// eslint-disable-next-line max-classes-per-file
|
|
3
|
+
import { check } from '@vcsuite/check';
|
|
4
|
+
import { getLogger as getLoggerByName } from '@vcsuite/logger';
|
|
5
|
+
import { contextIdSymbol } from '../vcsAppContextHelpers.js';
|
|
6
|
+
import Collection from './collection.js';
|
|
7
|
+
import VcsEvent from '../event/vcsEvent.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @returns {import("@vcsuite/logger").Logger}
|
|
11
|
+
*/
|
|
12
|
+
function getLogger() {
|
|
13
|
+
return getLoggerByName('OverrideCollection');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The override collection adds the ability to override a unique item and re-creating it, should the override
|
|
18
|
+
* be removed. This does change some flow of called events. 1) if you override an item, removed is not called for the
|
|
19
|
+
* removed current item. 2) added can be called more the once for the same unique id. 3) replaced is called for items
|
|
20
|
+
* which where replaced. replaced is called after added has been called for the item.
|
|
21
|
+
* @interface OverrideCollectionInterface
|
|
22
|
+
* @property {import("@vcmap/core").VcsEvent<T>} replaced - replaced is called after added
|
|
23
|
+
* @property {function(T):T|null} override - returns the overriden item or null if the item could not be inserted (this would be the result of a race condition)
|
|
24
|
+
* @property {Map<string, Array<Object>>} shadowMap
|
|
25
|
+
* @property {function(Array<Object>, string):Promise<void>} parseItems
|
|
26
|
+
* @property {function(string):Promise<void>} removeContext
|
|
27
|
+
* @property {function(string):Array<Object>} serializeContext
|
|
28
|
+
* @template {*} T
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A symbol added to override collections.
|
|
33
|
+
* @type {symbol}
|
|
34
|
+
*/
|
|
35
|
+
export const isOverrideCollection = Symbol('OverrideCollection');
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {Collection<T>} collection
|
|
39
|
+
* @param {function():string} getDynamicContextId - function to get the current dynamic context id
|
|
40
|
+
* @param {(function(T):Object)=} serializeItem - optional function to serialize an item, defaults to returning item.toJSON or item: i => (i.toJSON || i)
|
|
41
|
+
* @param {(function(Object):(T|Promise<T>))=} deserializeItem - optional desirialization function. defaults to returning the passed object: i => i
|
|
42
|
+
* @param {*=} ctor - optional constructor to validate deserialized items against. if passed, deserializeItem must be an instance of ctor.
|
|
43
|
+
* @param {(function(T, (T|null|undefined), (number|undefined)):number|null)=} determineShadowIndex - return the index where a shadow should be inserted. only has relevance, if the collection is indexed. previous and current index may be null.
|
|
44
|
+
* @template {*} T
|
|
45
|
+
* @returns {OverrideCollection<T>}
|
|
46
|
+
*/
|
|
47
|
+
function makeOverrideCollection(
|
|
48
|
+
collection,
|
|
49
|
+
getDynamicContextId,
|
|
50
|
+
serializeItem,
|
|
51
|
+
deserializeItem,
|
|
52
|
+
ctor,
|
|
53
|
+
determineShadowIndex,
|
|
54
|
+
) {
|
|
55
|
+
check(collection, Collection);
|
|
56
|
+
|
|
57
|
+
const overrideCollection = /** @type {OverrideCollection<T>} */ (collection);
|
|
58
|
+
if (overrideCollection[isOverrideCollection]) {
|
|
59
|
+
throw new Error('Cannot transform collection, collection already is an OverrideCollection');
|
|
60
|
+
}
|
|
61
|
+
overrideCollection[isOverrideCollection] = true;
|
|
62
|
+
|
|
63
|
+
const deserialize = deserializeItem || (i => i);
|
|
64
|
+
// @ts-ignore
|
|
65
|
+
const serialize = serializeItem || (i => (i.toJSON ? i.toJSON() : i));
|
|
66
|
+
const getShadowIndex = determineShadowIndex || ((item, shadow, currentIndex) => currentIndex);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @type {Map<string, Array<Object>>}
|
|
70
|
+
*/
|
|
71
|
+
overrideCollection.shadowMap = new Map();
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {T} item
|
|
75
|
+
* @returns {T|null}
|
|
76
|
+
*/
|
|
77
|
+
overrideCollection.override = function override(item) {
|
|
78
|
+
let shadow;
|
|
79
|
+
let index;
|
|
80
|
+
const itemId = item[overrideCollection.uniqueKey];
|
|
81
|
+
|
|
82
|
+
if (overrideCollection.hasKey(itemId)) {
|
|
83
|
+
shadow = overrideCollection.getByKey(itemId);
|
|
84
|
+
// @ts-ignore
|
|
85
|
+
index = overrideCollection._array.indexOf(shadow); // faking remove to not call removed
|
|
86
|
+
if (index > -1) {
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
overrideCollection._array.splice(index, 1);
|
|
89
|
+
}
|
|
90
|
+
if (!overrideCollection.shadowMap.has(itemId)) {
|
|
91
|
+
overrideCollection.shadowMap.set(itemId, []);
|
|
92
|
+
}
|
|
93
|
+
const shadowsArray = overrideCollection.shadowMap.get(itemId);
|
|
94
|
+
const serializedShadow = serialize(shadow);
|
|
95
|
+
// @ts-ignore
|
|
96
|
+
if (shadow.destroy) {
|
|
97
|
+
// @ts-ignore
|
|
98
|
+
shadow.destroy();
|
|
99
|
+
}
|
|
100
|
+
serializedShadow[contextIdSymbol] = shadow[contextIdSymbol];
|
|
101
|
+
shadowsArray.push(serializedShadow);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const usedIndex = shadow ? getShadowIndex(shadow, item, index) : null;
|
|
105
|
+
// @ts-ignore
|
|
106
|
+
if (overrideCollection.add(item, usedIndex) >= 0) {
|
|
107
|
+
if (shadow) {
|
|
108
|
+
overrideCollection.replaced.raiseEvent(item);
|
|
109
|
+
}
|
|
110
|
+
return item;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {Array<Object>} configArray
|
|
117
|
+
* @param {string} contextId
|
|
118
|
+
* @returns {Promise<void>}
|
|
119
|
+
*/
|
|
120
|
+
overrideCollection.parseItems = async function parseItems(configArray, contextId) {
|
|
121
|
+
if (Array.isArray(configArray)) {
|
|
122
|
+
const instanceArray = await Promise.all(configArray.map(async (config) => {
|
|
123
|
+
const item = await deserialize(config);
|
|
124
|
+
if (!item || (ctor && !(item instanceof ctor))) {
|
|
125
|
+
getLogger().warning(`Could not load item ${config[overrideCollection.uniqueKey]} of type ${config.type}`);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
item[contextIdSymbol] = contextId;
|
|
129
|
+
return item;
|
|
130
|
+
}));
|
|
131
|
+
instanceArray
|
|
132
|
+
.filter(i => i)
|
|
133
|
+
.forEach((i) => { overrideCollection.override(i); });
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
overrideCollection.removed.addEventListener(async (item) => {
|
|
138
|
+
const itemId = item[overrideCollection.uniqueKey];
|
|
139
|
+
|
|
140
|
+
if (overrideCollection.shadowMap.has(itemId)) {
|
|
141
|
+
const serializedShadow = overrideCollection.shadowMap.get(itemId).pop();
|
|
142
|
+
if (serializedShadow) {
|
|
143
|
+
const reincarnation = await deserialize(serializedShadow);
|
|
144
|
+
reincarnation[contextIdSymbol] = serializedShadow[contextIdSymbol];
|
|
145
|
+
// @ts-ignore
|
|
146
|
+
const index = getShadowIndex(reincarnation, item, item[overrideCollection.previousIndexSymbol]);
|
|
147
|
+
// @ts-ignore
|
|
148
|
+
overrideCollection.add(reincarnation, index);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (overrideCollection.shadowMap.get(itemId).length === 0) {
|
|
152
|
+
overrideCollection.shadowMap.delete(itemId);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
overrideCollection.added.addEventListener((item) => {
|
|
158
|
+
if (!item[contextIdSymbol]) {
|
|
159
|
+
item[contextIdSymbol] = getDynamicContextId();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @param {string} contextId
|
|
165
|
+
* @returns {Promise<void>}
|
|
166
|
+
*/
|
|
167
|
+
overrideCollection.removeContext = async function removeContext(contextId) {
|
|
168
|
+
overrideCollection.shadowMap.forEach((shadowsArray, name) => {
|
|
169
|
+
const newShadowsArray = shadowsArray.filter(c => c[contextIdSymbol] !== contextId);
|
|
170
|
+
if (newShadowsArray.length === 0) {
|
|
171
|
+
overrideCollection.shadowMap.delete(name);
|
|
172
|
+
} else if (newShadowsArray.length !== shadowsArray.length) {
|
|
173
|
+
overrideCollection.shadowMap.set(name, newShadowsArray);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await Promise.all([...overrideCollection]
|
|
178
|
+
.filter(item => item[contextIdSymbol] === contextId)
|
|
179
|
+
.map(async (item) => {
|
|
180
|
+
overrideCollection.remove(item);
|
|
181
|
+
// @ts-ignore
|
|
182
|
+
if (item.destroy) {
|
|
183
|
+
// @ts-ignore
|
|
184
|
+
item.destroy();
|
|
185
|
+
}
|
|
186
|
+
}));
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @type {VcsEvent<T>}
|
|
191
|
+
*/
|
|
192
|
+
overrideCollection.replaced = new VcsEvent();
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* @param {string} contextId
|
|
196
|
+
* @returns {Array<Object>}
|
|
197
|
+
*/
|
|
198
|
+
overrideCollection.serializeContext = function serializeContext(contextId) {
|
|
199
|
+
return [...overrideCollection]
|
|
200
|
+
.map((item) => {
|
|
201
|
+
if (item[contextIdSymbol] === contextId) {
|
|
202
|
+
return serialize(item);
|
|
203
|
+
}
|
|
204
|
+
if (overrideCollection.shadowMap.has(item[overrideCollection.uniqueKey])) {
|
|
205
|
+
return overrideCollection.shadowMap.get(item[overrideCollection.uniqueKey])
|
|
206
|
+
.find(i => i[contextIdSymbol] === contextId);
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
})
|
|
210
|
+
.filter(i => i);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const originalDestroy = overrideCollection.destroy.bind(overrideCollection);
|
|
214
|
+
|
|
215
|
+
overrideCollection.destroy = function destroy() {
|
|
216
|
+
originalDestroy();
|
|
217
|
+
overrideCollection.shadowMap.clear();
|
|
218
|
+
overrideCollection.replaced.destroy();
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return overrideCollection;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export default makeOverrideCollection;
|