@vcmap/core 5.0.0-rc.1 → 5.0.0-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +3 -2
  3. package/index.d.ts +2208 -2072
  4. package/index.js +137 -131
  5. package/package.json +10 -22
  6. package/src/category/appBackedCategory.js +76 -0
  7. package/src/category/category.js +401 -0
  8. package/src/category/categoryCollection.js +145 -0
  9. package/src/cesium/cesium3DTileFeature.js +1 -1
  10. package/src/classRegistry.js +168 -0
  11. package/src/context.js +73 -0
  12. package/src/{vcs/vcm/util/featureProvider → featureProvider}/abstractFeatureProvider.js +8 -8
  13. package/src/{vcs/vcm/util/featureProvider → featureProvider}/featureProviderHelpers.js +4 -5
  14. package/src/{vcs/vcm/util/featureProvider → featureProvider}/featureProviderSymbols.js +0 -0
  15. package/src/{vcs/vcm/util/featureProvider → featureProvider}/tileProviderFeatureProvider.js +4 -2
  16. package/src/{vcs/vcm/util/featureProvider → featureProvider}/wmsFeatureProvider.js +25 -17
  17. package/src/{vcs/vcm/interaction → interaction}/abstractInteraction.js +19 -17
  18. package/src/{vcs/vcm/interaction → interaction}/coordinateAtPixel.js +6 -9
  19. package/src/{vcs/vcm/interaction → interaction}/eventHandler.js +3 -3
  20. package/src/{vcs/vcm/interaction → interaction}/featureAtPixelInteraction.js +6 -19
  21. package/src/{vcs/vcm/interaction → interaction}/featureProviderInteraction.js +2 -13
  22. package/src/{vcs/vcm/interaction → interaction}/interactionChain.js +11 -31
  23. package/src/{vcs/vcm/interaction → interaction}/interactionType.js +0 -0
  24. package/src/{vcs/vcm/layer/cesium/cesiumTilesetCesium.js → layer/cesium/cesiumTilesetCesiumImpl.js} +8 -10
  25. package/src/{vcs/vcm/layer → layer}/cesium/clusterContext.js +0 -0
  26. package/src/{vcs/vcm/layer/cesium/dataSourceCesium.js → layer/cesium/dataSourceCesiumImpl.js} +5 -2
  27. package/src/{vcs/vcm/layer/cesium/openStreetMapCesium.js → layer/cesium/openStreetMapCesiumImpl.js} +6 -6
  28. package/src/{vcs/vcm/layer/cesium/rasterLayerCesium.js → layer/cesium/rasterLayerCesiumImpl.js} +5 -5
  29. package/src/{vcs/vcm/layer/cesium/singleImageCesium.js → layer/cesium/singleImageCesiumImpl.js} +5 -5
  30. package/src/{vcs/vcm/layer/cesium/terrainCesium.js → layer/cesium/terrainCesiumImpl.js} +4 -4
  31. package/src/{vcs/vcm/layer/cesium/tmsCesium.js → layer/cesium/tmsCesiumImpl.js} +6 -6
  32. package/src/{vcs/vcm/layer/cesium/vectorCesium.js → layer/cesium/vectorCesiumImpl.js} +3 -3
  33. package/src/{vcs/vcm/layer → layer}/cesium/vectorContext.js +0 -0
  34. package/src/{vcs/vcm/layer/cesium/vectorRasterTileCesium.js → layer/cesium/vectorRasterTileCesiumImpl.js} +5 -5
  35. package/src/{vcs/vcm/layer → layer}/cesium/vectorTileImageryProvider.js +1 -1
  36. package/src/{vcs/vcm/layer/cesium/wmsCesium.js → layer/cesium/wmsCesiumImpl.js} +6 -6
  37. package/src/{vcs/vcm/layer/cesium/wmtsCesium.js → layer/cesium/wmtsCesiumImpl.js} +6 -6
  38. package/src/{vcs/vcm/layer → layer}/cesium/x3dmHelper.js +0 -0
  39. package/src/{vcs/vcm/layer/cesiumTileset.js → layer/cesiumTilesetLayer.js} +38 -36
  40. package/src/{vcs/vcm/layer/czml.js → layer/czmlLayer.js} +14 -16
  41. package/src/{vcs/vcm/layer/dataSource.js → layer/dataSourceLayer.js} +13 -13
  42. package/src/{vcs/vcm/layer → layer}/featureLayer.js +17 -30
  43. package/src/{vcs/vcm/layer/featureStore.js → layer/featureStoreLayer.js} +53 -53
  44. package/src/{vcs/vcm/layer/featureStoreChanges.js → layer/featureStoreLayerChanges.js} +98 -82
  45. package/src/{vcs/vcm/layer/featureStoreState.js → layer/featureStoreLayerState.js} +1 -1
  46. package/src/{vcs/vcm/layer → layer}/featureVisibility.js +6 -5
  47. package/src/{vcs/vcm/layer → layer}/geojsonHelpers.js +15 -37
  48. package/src/{vcs/vcm/layer/geojson.js → layer/geojsonLayer.js} +19 -21
  49. package/src/{vcs/vcm/layer → layer}/globalHider.js +1 -11
  50. package/src/{vcs/vcm/layer → layer}/layer.js +11 -11
  51. package/src/{vcs/vcm/layer → layer}/layerImplementation.js +2 -2
  52. package/src/{vcs/vcm/layer → layer}/layerState.js +0 -0
  53. package/src/{vcs/vcm/layer → layer}/layerSymbols.js +0 -0
  54. package/src/{vcs/vcm/layer/oblique/layerOblique.js → layer/oblique/layerObliqueImpl.js} +4 -4
  55. package/src/{vcs/vcm/layer → layer}/oblique/obliqueHelpers.js +2 -2
  56. package/src/{vcs/vcm/layer/oblique/vectorOblique.js → layer/oblique/vectorObliqueImpl.js} +6 -6
  57. package/src/{vcs/vcm/layer/openStreetMap.js → layer/openStreetMapLayer.js} +32 -32
  58. package/src/{vcs/vcm/layer/openlayers/layerOpenlayers.js → layer/openlayers/layerOpenlayersImpl.js} +5 -5
  59. package/src/layer/openlayers/openStreetMapOpenlayersImpl.js +27 -0
  60. package/src/{vcs/vcm/layer/openlayers/rasterLayerOpenlayers.js → layer/openlayers/rasterLayerOpenlayersImpl.js} +15 -14
  61. package/src/{vcs/vcm/layer/openlayers/singleImageOpenlayers.js → layer/openlayers/singleImageOpenlayersImpl.js} +6 -6
  62. package/src/{vcs/vcm/layer/openlayers/tileDebugOpenlayers.js → layer/openlayers/tileDebugOpenlayersImpl.js} +5 -5
  63. package/src/{vcs/vcm/layer/openlayers/tmsOpenlayers.js → layer/openlayers/tmsOpenlayersImpl.js} +7 -7
  64. package/src/{vcs/vcm/layer/openlayers/vectorOpenlayers.js → layer/openlayers/vectorOpenlayersImpl.js} +6 -6
  65. package/src/{vcs/vcm/layer/openlayers/vectorTileOpenlayers.js → layer/openlayers/vectorTileOpenlayersImpl.js} +6 -6
  66. package/src/{vcs/vcm/layer/openlayers/wmsOpenlayers.js → layer/openlayers/wmsOpenlayersImpl.js} +7 -7
  67. package/src/{vcs/vcm/layer/openlayers/wmtsOpenlayers.js → layer/openlayers/wmtsOpenlayersImpl.js} +7 -7
  68. package/src/{vcs/vcm/layer/pointCloud.js → layer/pointCloudLayer.js} +26 -33
  69. package/src/{vcs/vcm/layer → layer}/rasterLayer.js +18 -18
  70. package/src/{vcs/vcm/layer/singleImage.js → layer/singleImageLayer.js} +20 -19
  71. package/src/{vcs/vcm/layer → layer}/terrainHelpers.js +13 -49
  72. package/src/{vcs/vcm/layer/terrain.js → layer/terrainLayer.js} +13 -13
  73. package/src/{vcs/vcm/layer → layer}/tileLoadedHelper.js +5 -5
  74. package/src/{vcs/vcm/layer → layer}/tileProvider/mvtTileProvider.js +22 -4
  75. package/src/layer/tileProvider/staticGeojsonTileProvider.js +82 -0
  76. package/src/{vcs/vcm/layer → layer}/tileProvider/tileProvider.js +16 -7
  77. package/src/{vcs/vcm/layer → layer}/tileProvider/urlTemplateTileProvider.js +20 -5
  78. package/src/{vcs/vcm/layer/tms.js → layer/tmsLayer.js} +19 -19
  79. package/src/{vcs/vcm/layer → layer}/vectorHelpers.js +5 -5
  80. package/src/{vcs/vcm/layer/vector.js → layer/vectorLayer.js} +38 -47
  81. package/src/{vcs/vcm/layer → layer}/vectorProperties.js +3 -3
  82. package/src/{vcs/vcm/layer → layer}/vectorSymbols.js +0 -0
  83. package/src/{vcs/vcm/layer/vectorTile.js → layer/vectorTileLayer.js} +32 -41
  84. package/src/{vcs/vcm/layer/wfs.js → layer/wfsLayer.js} +19 -19
  85. package/src/{vcs/vcm/layer → layer}/wmsHelpers.js +1 -1
  86. package/src/{vcs/vcm/layer/wms.js → layer/wmsLayer.js} +22 -22
  87. package/src/{vcs/vcm/layer/wmts.js → layer/wmtsLayer.js} +20 -20
  88. package/src/{vcs/vcm/maps → map}/baseOLMap.js +5 -5
  89. package/src/{vcs/vcm/maps → map}/cameraLimiter.js +11 -16
  90. package/src/{vcs/vcm/maps/cesium.js → map/cesiumMap.js} +21 -33
  91. package/src/{vcs/vcm/maps → map}/mapState.js +0 -0
  92. package/src/{vcs/vcm/maps/oblique.js → map/obliqueMap.js} +42 -56
  93. package/src/{vcs/vcm/maps/openlayers.js → map/openlayersMap.js} +17 -14
  94. package/src/{vcs/vcm/maps/map.js → map/vcsMap.js} +35 -20
  95. package/src/oblique/defaultObliqueCollection.js +62 -0
  96. package/src/{vcs/vcm/oblique → oblique}/helpers.js +13 -41
  97. package/src/{vcs/vcm/oblique/ObliqueCollection.js → oblique/obliqueCollection.js} +117 -36
  98. package/src/{vcs/vcm/oblique/ObliqueDataSet.js → oblique/obliqueDataSet.js} +67 -24
  99. package/src/{vcs/vcm/oblique/ObliqueImage.js → oblique/obliqueImage.js} +1 -1
  100. package/src/{vcs/vcm/oblique/ObliqueImageMeta.js → oblique/obliqueImageMeta.js} +4 -4
  101. package/src/{vcs/vcm/oblique/ObliqueProvider.js → oblique/obliqueProvider.js} +17 -11
  102. package/src/{vcs/vcm/oblique/ObliqueView.js → oblique/obliqueView.js} +31 -1
  103. package/src/{vcs/vcm/oblique/ObliqueViewDirection.js → oblique/obliqueViewDirection.js} +0 -0
  104. package/src/{vcs/vcm/oblique → oblique}/parseImageJson.js +20 -12
  105. package/src/ol/geom/circle.js +1 -1
  106. package/src/overrideClassRegistry.js +204 -0
  107. package/src/{vcs/vcm/util/style → style}/declarativeStyleItem.js +43 -19
  108. package/src/{vcs/vcm/util/style → style}/shapesCategory.js +0 -0
  109. package/src/style/styleFactory.js +29 -0
  110. package/src/{vcs/vcm/util/style → style}/styleHelpers.js +3 -3
  111. package/src/{vcs/vcm/util/style → style}/styleItem.js +23 -85
  112. package/src/{vcs/vcm/util/style → style}/vectorStyleItem.js +73 -81
  113. package/src/{vcs/vcm/util/style → style}/writeStyle.js +4 -7
  114. package/src/{vcs/vcm/util → util}/clipping/clippingObject.js +10 -10
  115. package/src/{vcs/vcm/util → util}/clipping/clippingObjectManager.js +2 -2
  116. package/src/{vcs/vcm/util → util}/clipping/clippingPlaneHelper.js +4 -4
  117. package/src/{vcs/vcm/util → util}/collection.js +1 -1
  118. package/src/{vcs/vcm/util → util}/dateTime.js +0 -0
  119. package/src/{vcs/vcm/util → util}/exclusiveManager.js +0 -0
  120. package/src/{vcs/vcm/util → util}/extent.js +18 -11
  121. package/src/{vcs/vcm/util → util}/featureconverter/circleToCesium.js +0 -0
  122. package/src/{vcs/vcm/util → util}/featureconverter/convert.js +0 -0
  123. package/src/util/featureconverter/extent3D.js +181 -0
  124. package/src/{vcs/vcm/util → util}/featureconverter/featureconverterHelper.js +1 -1
  125. package/src/{vcs/vcm/util → util}/featureconverter/lineStringToCesium.js +0 -0
  126. package/src/{vcs/vcm/util → util}/featureconverter/pointToCesium.js +3 -3
  127. package/src/{vcs/vcm/util → util}/featureconverter/polygonToCesium.js +1 -1
  128. package/src/util/fetch.js +32 -0
  129. package/src/{vcs/vcm/util → util}/geometryHelpers.js +0 -0
  130. package/src/{vcs/vcm/util → util}/indexedCollection.js +24 -1
  131. package/src/{vcs/vcm/util → util}/isMobile.js +0 -0
  132. package/src/{vcs/vcm/util → util}/layerCollection.js +11 -6
  133. package/src/{vcs/vcm/util → util}/locale.js +1 -1
  134. package/src/{vcs/vcm/util → util}/mapCollection.js +63 -21
  135. package/src/{vcs/vcm/util → util}/math.js +0 -0
  136. package/src/util/overrideCollection.js +224 -0
  137. package/src/{vcs/vcm/util → util}/projection.js +39 -24
  138. package/src/{vcs/vcm/util → util}/splitScreen.js +10 -10
  139. package/src/{vcs/vcm/util → util}/urlHelpers.js +0 -0
  140. package/src/{vcs/vcm/util → util}/viewpoint.js +5 -5
  141. package/src/vcsApp.js +471 -0
  142. package/src/vcsAppContextHelpers.js +121 -0
  143. package/src/{vcs/vcm/event/vcsEvent.js → vcsEvent.js} +2 -2
  144. package/src/{vcs/vcm/object.js → vcsObject.js} +2 -10
  145. package/src/vcs/vcm/classRegistry.js +0 -106
  146. package/src/vcs/vcm/globalCollections.js +0 -11
  147. package/src/vcs/vcm/layer/buildings.js +0 -17
  148. package/src/vcs/vcm/layer/cesium/pointCloudCesium.js +0 -58
  149. package/src/vcs/vcm/layer/openlayers/openStreetMapOpenlayers.js +0 -27
  150. package/src/vcs/vcm/layer/tileProvider/staticGeojsonTileProvider.js +0 -67
  151. package/src/vcs/vcm/layer/tileProvider/tileProviderFactory.js +0 -28
  152. package/src/vcs/vcm/util/featureconverter/extent3d.js +0 -154
  153. package/src/vcs/vcm/util/style/styleFactory.js +0 -48
@@ -0,0 +1,401 @@
1
+ import { check } from '@vcsuite/check';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { Feature } from 'ol';
4
+ import { contextIdSymbol, destroyCollection } from '../vcsAppContextHelpers.js';
5
+ import makeOverrideCollection, { isOverrideCollection } from '../util/overrideCollection.js';
6
+ import VcsObject from '../vcsObject.js';
7
+ import VectorLayer from '../layer/vectorLayer.js';
8
+ import IndexedCollection from '../util/indexedCollection.js';
9
+ import { parseGeoJSON, writeGeoJSONFeature } from '../layer/geojsonHelpers.js';
10
+ import Collection from '../util/collection.js';
11
+ import { getStyleOrDefaultStyle } from '../style/styleFactory.js';
12
+ import { categoryClassRegistry, getObjectFromClassRegistry } from '../classRegistry.js';
13
+ import OverrideClassRegistry from '../overrideClassRegistry.js';
14
+
15
+ /**
16
+ * @typedef {VcsObjectOptions} CategoryOptions
17
+ * @property {string|Object<string, string>} [title]
18
+ * @property {string} [classRegistryName=''] - the class registry name on the current app to provide classes for this category. if provided, parseItems will deserialize using this class registry. See: {@link getObjectFromClassRegistry}.
19
+ * @property {string|undefined} [featureProperty]
20
+ * @property {VectorOptions} [layerOptions={}]
21
+ * @property {Array<Object>} [items] - items are not evaluated by the constructor but passed to parseItem during deserialization.
22
+ * @property {string} [keyProperty=name]
23
+ */
24
+
25
+ /**
26
+ * @type {*|string}
27
+ */
28
+ const categoryContextId = uuidv4();
29
+
30
+ /**
31
+ * @param {import("@vcmap/core").VectorLayer} layer
32
+ * @param {VectorOptions} options
33
+ * @private
34
+ */
35
+ function assignLayerOptions(layer, options) {
36
+ if (options.style) {
37
+ layer.setStyle(getStyleOrDefaultStyle(options.style, layer.defaultStyle));
38
+ }
39
+
40
+ if (options.highlightStyle) {
41
+ const highlightStyle = getStyleOrDefaultStyle(options.highlightStyle, layer.highlightStyle);
42
+ layer.setHighlightStyle(/** @type {import("@vcmap/core").VectorStyleItem} */ (highlightStyle));
43
+ }
44
+
45
+ if (options.vectorProperties) {
46
+ layer.vectorProperties.setValues(options.vectorProperties);
47
+ }
48
+
49
+ if (options.zIndex != null) {
50
+ layer.zIndex = options.zIndex;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * @param {string} key
56
+ * @param {T} value
57
+ * @param {T} defaultOption
58
+ * @param {T=} option
59
+ * @template {number|boolean|string} T
60
+ * @returns {void}
61
+ */
62
+ function checkMergeOptionOverride(key, value, defaultOption, option) {
63
+ const isOverride = option == null ? value !== defaultOption : option !== value;
64
+ if (isOverride) {
65
+ throw new Error(`Cannot merge options, values of ${key} do not match`);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * A category contains user based items and is a special container. The container should not be created directly, but via
71
+ * the requestCategory API on the categories collection. Do not use toJSON to retrieve the state of a category, since
72
+ * categories outlive contexts and may be changed with mergeOptions to no longer reflect your initial state. Requestors
73
+ * should keep track of the requested options themselves.
74
+ * @class
75
+ * @extends {VcsObject}
76
+ * @template {Object|VcsObject} T
77
+ */
78
+ class Category extends VcsObject {
79
+ static get className() { return 'Category'; }
80
+
81
+ /**
82
+ * @returns {CategoryOptions}
83
+ */
84
+ static getDefaultConfig() {
85
+ return {
86
+ title: '',
87
+ featureProperty: undefined,
88
+ classRegistryName: undefined,
89
+ layerOptions: {},
90
+ keyProperty: 'name',
91
+ items: [],
92
+ };
93
+ }
94
+
95
+ /**
96
+ * @param {CategoryOptions} options
97
+ */
98
+ constructor(options) {
99
+ super(options);
100
+ const defaultOptions = Category.getDefaultConfig();
101
+ /**
102
+ * @type {string|Object<string, string>}
103
+ */
104
+ this.title = options.title || this.name;
105
+ /**
106
+ * @type {import("@vcmap/core").VcsApp}
107
+ * @protected
108
+ */
109
+ this._app = null;
110
+ /**
111
+ * @type {string}
112
+ * @private
113
+ */
114
+ this._featureProperty = options.featureProperty || defaultOptions.featureProperty;
115
+ /**
116
+ * @type {string}
117
+ * @private
118
+ */
119
+ this._classRegistryName = options.classRegistryName;
120
+ /**
121
+ * @type {VectorOptions}
122
+ * @private
123
+ */
124
+ this._layerOptions = options.layerOptions || defaultOptions.layerOptions;
125
+ /**
126
+ * @type {import("@vcmap/core").VectorLayer}
127
+ * @protected
128
+ */
129
+ this._layer = null;
130
+ if (this._featureProperty) {
131
+ this._layer = new VectorLayer(this._layerOptions);
132
+ this._layer[contextIdSymbol] = categoryContextId;
133
+ }
134
+ /**
135
+ * @type {string}
136
+ * @private
137
+ */
138
+ this._keyProperty = options.keyProperty || defaultOptions.keyProperty;
139
+ /**
140
+ * @type {Array<function():void>}
141
+ * @private
142
+ */
143
+ this._collectionListeners = [];
144
+ /**
145
+ * @type {OverrideCollection<T>}
146
+ * @private
147
+ */
148
+ this._collection = null;
149
+ this.setCollection(new IndexedCollection(this._keyProperty));
150
+ /**
151
+ * @type {function():void}
152
+ * @private
153
+ */
154
+ this._contextRemovedListener = () => {};
155
+ }
156
+
157
+ /**
158
+ * @type {string}
159
+ * @readonly
160
+ */
161
+ get classRegistryName() { return this._classRegistryName; }
162
+
163
+ /**
164
+ * The collection of this category.
165
+ * @type {OverrideCollection<T>}
166
+ * @readonly
167
+ */
168
+ get collection() {
169
+ return this._collection;
170
+ }
171
+
172
+ /**
173
+ * Returns the layer of this collection. Caution, do not use the layer API to add or remove items.
174
+ * When adding items to the collection, the features are added to the layer async (timeout of 0), since there is weird behavior
175
+ * when removing and adding a feature with the same id in the same sync call.
176
+ * @type {import("@vcmap/core").VectorLayer|null}
177
+ */
178
+ get layer() {
179
+ return this._layer;
180
+ }
181
+
182
+ /**
183
+ * @param {T} item
184
+ * @protected
185
+ */
186
+ _itemAdded(item) {
187
+ if (this._featureProperty) {
188
+ const id = item[this._keyProperty];
189
+ this._layer.removeFeaturesById([id]); // this may be a replacement.
190
+
191
+ const geoJsonFeature = item[this._featureProperty];
192
+ let feature;
193
+ if (geoJsonFeature instanceof Feature) {
194
+ feature = geoJsonFeature;
195
+ } else if (typeof geoJsonFeature === 'object') {
196
+ const { features } = parseGeoJSON(geoJsonFeature);
197
+ if (features[0]) { // XXX do we warn on feature collection?
198
+ feature = features[0];
199
+ }
200
+ }
201
+
202
+ if (feature) {
203
+ feature.setId(id);
204
+ setTimeout(() => { this._layer.addFeatures([feature]); }, 0); // We need to set a timeout, since removing and adding the feature in the same sync call leads to undefined behavior in OL TODO recheck in ol 6.11
205
+ }
206
+ }
207
+ }
208
+
209
+ /**
210
+ * @param {T} item
211
+ * @protected
212
+ */
213
+ _itemRemoved(item) {
214
+ if (this._featureProperty) {
215
+ this._layer.removeFeaturesById([item[this._keyProperty]]);
216
+ }
217
+ }
218
+
219
+ /**
220
+ * @param {T} item
221
+ * @protected
222
+ */
223
+ // eslint-disable-next-line class-methods-use-this,no-unused-vars
224
+ _itemReplaced(item) {}
225
+
226
+ /**
227
+ * @param {T} item
228
+ * @protected
229
+ */
230
+ // eslint-disable-next-line class-methods-use-this,no-unused-vars
231
+ _itemMoved(item) {}
232
+
233
+ /**
234
+ * @returns {string}
235
+ * @private
236
+ */
237
+ _getDynamicContextId() {
238
+ if (!this._app) {
239
+ throw new Error('Cannot get dynamic context id, before setting the vcApp');
240
+ }
241
+ return this._app.dynamicContextId;
242
+ }
243
+
244
+ /**
245
+ * Throws if typed, featureProperty and keyProperty do not match. Merges other options.
246
+ * Only merges: style, highlightStyle, zIndex & vectorProperties from layerOptions.
247
+ * @param {CategoryOptions} options
248
+ */
249
+ mergeOptions(options) {
250
+ const defaultOptions = Category.getDefaultConfig();
251
+ checkMergeOptionOverride(
252
+ 'classRegistryName',
253
+ this._classRegistryName,
254
+ defaultOptions.classRegistryName,
255
+ options.classRegistryName,
256
+ );
257
+ checkMergeOptionOverride(
258
+ 'featureProperty',
259
+ this._featureProperty,
260
+ defaultOptions.featureProperty,
261
+ options.featureProperty,
262
+ );
263
+ checkMergeOptionOverride('keyProperty', this._keyProperty, defaultOptions.keyProperty, options.keyProperty);
264
+ this.title = options.title || this.title;
265
+ if (options.layerOptions && this._layer) {
266
+ assignLayerOptions(this._layer, options.layerOptions);
267
+ }
268
+ }
269
+
270
+ /**
271
+ * When setting the category, it MUST use the same unqiueKey as the previous collection (default is "name").
272
+ * All items in the current collection _will be destroyed_ and the current collection will be destroyed. The category will take
273
+ * complete ownership of the collection and destroy it once the category is destroyed. The collection will
274
+ * be turned into an {@see OverrideCollection}.
275
+ * @param {import("@vcmap/core").Collection<T>} collection
276
+ */
277
+ setCollection(collection) {
278
+ check(collection, Collection);
279
+
280
+ if (this._keyProperty !== collection.uniqueKey) {
281
+ throw new Error('The collections key property does not match the categories key property');
282
+ }
283
+
284
+ this._collectionListeners.forEach((cb) => { cb(); });
285
+ if (this._collection) {
286
+ destroyCollection(this._collection);
287
+ }
288
+ if (this._layer) {
289
+ this._layer.removeAllFeatures(); // XXX should we call `itemRemoved` instead?
290
+ }
291
+
292
+ this._collection = collection[isOverrideCollection] ?
293
+ /** @type {OverrideCollection} */ (collection) :
294
+ makeOverrideCollection(
295
+ collection,
296
+ this._getDynamicContextId.bind(this),
297
+ this._serializeItem.bind(this),
298
+ this._deserializeItem.bind(this),
299
+ );
300
+
301
+ [...this.collection].forEach((item) => { this._itemAdded(item); });
302
+ /**
303
+ * @type {Array<function():void>}
304
+ * @private
305
+ */
306
+ this._collectionListeners = [
307
+ this._collection.added.addEventListener(this._itemAdded.bind(this)),
308
+ this._collection.removed.addEventListener(this._itemRemoved.bind(this)),
309
+ this._collection.replaced.addEventListener(this._itemReplaced.bind(this)),
310
+ ];
311
+
312
+ // @ts-ignore
313
+ if (this._collection.moved) {
314
+ // @ts-ignore
315
+ this._collectionListeners.push(this._collection.moved.addEventListener(this._itemMoved.bind(this)));
316
+ }
317
+ }
318
+
319
+ /**
320
+ * @param {import("@vcmap/core").VcsApp} app
321
+ */
322
+ setApp(app) {
323
+ if (this._app) {
324
+ throw new Error('Cannot switch apps');
325
+ }
326
+ this._app = app;
327
+ this._contextRemovedListener = this._app.contextRemoved.addEventListener((context) => {
328
+ this._collection.removeContext(context.id);
329
+ });
330
+ if (this._layer) {
331
+ this._app.layers.add(this._layer);
332
+ }
333
+ }
334
+
335
+ /**
336
+ * @protected
337
+ * @param {*} config
338
+ * @returns {Promise<T>} // XXX should this still be async?
339
+ */
340
+ async _deserializeItem(config) {
341
+ if (!this._app) {
342
+ throw new Error('Cannot deserialize item before setting the vcApp');
343
+ }
344
+ const classRegistry = this._classRegistryName ? this._app[this._classRegistryName] : null;
345
+ if (classRegistry && classRegistry instanceof OverrideClassRegistry) {
346
+ return getObjectFromClassRegistry(classRegistry, config);
347
+ }
348
+ return config;
349
+ }
350
+
351
+ /**
352
+ * @protected
353
+ * @param {T} item
354
+ * @returns {Array<Object>}
355
+ */
356
+ _serializeItem(item) {
357
+ const config = JSON.parse(JSON.stringify(item));
358
+ if (this._featureProperty) {
359
+ const feature = this._layer.getFeatureById(item[this._keyProperty]);
360
+ if (feature) {
361
+ config[this._featureProperty] = writeGeoJSONFeature(feature);
362
+ }
363
+ }
364
+ return config;
365
+ }
366
+
367
+ /**
368
+ * @param {string} contextId
369
+ * @returns {CategoryOptions|null}
370
+ */
371
+ serializeForContext(contextId) {
372
+ if (this._collection.size === 0) {
373
+ return null;
374
+ }
375
+
376
+ return {
377
+ name: this.name,
378
+ items: this.collection.serializeContext(contextId),
379
+ };
380
+ }
381
+
382
+ destroy() {
383
+ super.destroy();
384
+ if (this._app && this._layer) {
385
+ this._app.layers.remove(this._layer);
386
+ }
387
+ if (this._layer) {
388
+ this._layer.destroy();
389
+ }
390
+
391
+ this._collectionListeners.forEach((cb) => { cb(); });
392
+ this._collectionListeners.splice(0);
393
+ this._contextRemovedListener();
394
+ this._contextRemovedListener = () => {};
395
+ destroyCollection(this._collection);
396
+ this._app = null;
397
+ }
398
+ }
399
+
400
+ export default Category;
401
+ categoryClassRegistry.registerClass(Category.className, Category);
@@ -0,0 +1,145 @@
1
+ import { getLogger as getLoggerByName } from '@vcsuite/logger';
2
+ import Category from './category.js';
3
+ import './appBackedCategory.js';
4
+ import IndexedCollection from '../util/indexedCollection.js';
5
+ import { getObjectFromClassRegistry } from '../classRegistry.js';
6
+
7
+ /**
8
+ * @returns {import("@vcsuite/logger").Logger}
9
+ */
10
+ function getLogger() {
11
+ return getLoggerByName('CategoryCollection');
12
+ }
13
+
14
+ /**
15
+ * @class
16
+ * @extends {IndexedCollection<Category<Object|import("@vcmap/core").VcsObject>>}
17
+ */
18
+ class CategoryCollection extends IndexedCollection {
19
+ /**
20
+ * @param {import("@vcmap/core").VcsApp} app
21
+ */
22
+ constructor(app) {
23
+ super();
24
+ /**
25
+ * @type {import("@vcmap/core").VcsApp}
26
+ * @private
27
+ */
28
+ this._app = app;
29
+ /**
30
+ * Map of category names, where the value is a map of contextId and items.
31
+ * @type {Map<string, Map<string, Array<Object>>>}
32
+ * @private
33
+ */
34
+ this._cache = new Map();
35
+ /**
36
+ * @type {Function}
37
+ * @private
38
+ */
39
+ this._contextRemovedListener = this._app.contextRemoved.addEventListener((context) => {
40
+ this._cache.forEach((contextMap, name) => {
41
+ contextMap.delete(context.id);
42
+ if (contextMap.size === 0) {
43
+ this._cache.delete(name);
44
+ }
45
+ });
46
+ });
47
+ }
48
+
49
+ /**
50
+ * Do not call add directly. Use request category for adding categories.
51
+ * @param {Category<Object|import("@vcmap/core").VcsObject>} category
52
+ * @returns {number|null}
53
+ */
54
+ add(category) { // XXX use a symbol to enforce using request over add?
55
+ if (this.hasKey(category.name)) {
56
+ return null;
57
+ }
58
+
59
+ category.setApp(this._app);
60
+ const added = super.add(category);
61
+ if (added != null && this._cache.has(category.name)) {
62
+ this._cache
63
+ .get(category.name)
64
+ .forEach((items, contextId) => {
65
+ this.parseCategoryItems(category.name, items, contextId);
66
+ });
67
+
68
+ this._cache.delete(category.name);
69
+ }
70
+ return added;
71
+ }
72
+
73
+ /**
74
+ * Categories should be static. Removing them can lead to undefined behavior.
75
+ * @param {Category<Object|import("@vcmap/core").VcsObject>} category
76
+ */
77
+ remove(category) { // XXX add logger warning?
78
+ super.remove(category);
79
+ this._cache.delete(category.name);
80
+ }
81
+
82
+ /**
83
+ * Parses the category items. Items will only be parsed, if a category with said name exists. Otherwise,
84
+ * they will be cached, until such a category is requested.
85
+ * @param {string} name
86
+ * @param {Array<Object>} items
87
+ * @param {string} contextId
88
+ * @returns {Promise<void>}
89
+ */
90
+ async parseCategoryItems(name, items, contextId) {
91
+ const category = this.getByKey(name);
92
+
93
+ if (category) {
94
+ await category.collection.parseItems(items, contextId);
95
+ } else if (this._cache.has(name)) {
96
+ this._cache.get(name).set(contextId, items);
97
+ } else {
98
+ this._cache.set(name, new Map([[contextId, items]]));
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Add categories with this API.
104
+ * @param {CategoryOptions} options
105
+ * @returns {Promise<Category<Object|import("@vcmap/core").VcsObject>>}
106
+ */
107
+ async requestCategory(options) {
108
+ if (!options.name) {
109
+ getLogger().error('Cannot request a category without a name');
110
+ return null;
111
+ }
112
+
113
+ if (!options.type) {
114
+ getLogger().warning(`Implicitly typing category ${options.name} as ${Category.className}`);
115
+ options.type = Category.className;
116
+ }
117
+
118
+ let category;
119
+ if (this.hasKey(options.name)) {
120
+ category = this.getByKey(options.name);
121
+ category.mergeOptions(options);
122
+ } else {
123
+ category = await getObjectFromClassRegistry(this._app.categoryClassRegistry, options);
124
+ if (category) {
125
+ if (this.add(category) == null) {
126
+ return null;
127
+ }
128
+ }
129
+ }
130
+
131
+ if (!category) {
132
+ throw new Error(`Category ${options.name} with type ${options.type} could not be created`);
133
+ }
134
+ return category;
135
+ }
136
+
137
+ destroy() {
138
+ super.destroy();
139
+ this._contextRemovedListener();
140
+ this._cache.clear();
141
+ this._app = null;
142
+ }
143
+ }
144
+
145
+ export default CategoryCollection;
@@ -5,5 +5,5 @@ import { Cesium3DTileFeature } from '@vcmap/cesium';
5
5
  * @returns {string|number}
6
6
  */
7
7
  Cesium3DTileFeature.prototype.getId = function getId() {
8
- return this.getProperty('id') || `${this.content.url}${this._batchId}`;
8
+ return this.getProperty('id') || `${this.content.url}${this._batchId}`; // XXX there is a new property `featureId` on the Cesium3DTileset. this may cause issues when picking b3dm.
9
9
  };