@vcmap/core 6.0.7 → 6.1.0-rc.2

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 (146) hide show
  1. package/dist/cesium.d.ts +3 -0
  2. package/dist/index.d.ts +16 -1
  3. package/dist/index.js +16 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/ol.d.ts +8 -1
  6. package/dist/src/featureProvider/featureProviderSymbols.d.ts +5 -0
  7. package/dist/src/featureProvider/featureProviderSymbols.js +5 -1
  8. package/dist/src/featureProvider/featureProviderSymbols.js.map +1 -1
  9. package/dist/src/interaction/featureAtPixelInteraction.js +58 -62
  10. package/dist/src/interaction/featureAtPixelInteraction.js.map +1 -1
  11. package/dist/src/interaction/featureProviderInteraction.js +25 -13
  12. package/dist/src/interaction/featureProviderInteraction.js.map +1 -1
  13. package/dist/src/layer/cesium/sourceVectorContextSync.d.ts +27 -0
  14. package/dist/src/layer/cesium/sourceVectorContextSync.js +94 -0
  15. package/dist/src/layer/cesium/sourceVectorContextSync.js.map +1 -0
  16. package/dist/src/layer/cesium/vectorCesiumImpl.d.ts +4 -27
  17. package/dist/src/layer/cesium/vectorCesiumImpl.js +15 -107
  18. package/dist/src/layer/cesium/vectorCesiumImpl.js.map +1 -1
  19. package/dist/src/layer/cesium/vectorContext.d.ts +12 -1
  20. package/dist/src/layer/cesium/vectorContext.js +6 -0
  21. package/dist/src/layer/cesium/vectorContext.js.map +1 -1
  22. package/dist/src/layer/layerSymbols.js +1 -1
  23. package/dist/src/layer/layerSymbols.js.map +1 -1
  24. package/dist/src/layer/oblique/sourceObliqueSync.d.ts +18 -0
  25. package/dist/src/layer/oblique/sourceObliqueSync.js +319 -0
  26. package/dist/src/layer/oblique/sourceObliqueSync.js.map +1 -0
  27. package/dist/src/layer/oblique/vectorObliqueImpl.d.ts +2 -40
  28. package/dist/src/layer/oblique/vectorObliqueImpl.js +8 -283
  29. package/dist/src/layer/oblique/vectorObliqueImpl.js.map +1 -1
  30. package/dist/src/layer/vectorLayer.d.ts +10 -1
  31. package/dist/src/layer/vectorLayer.js +23 -1
  32. package/dist/src/layer/vectorLayer.js.map +1 -1
  33. package/dist/src/map/baseOLMap.js +8 -1
  34. package/dist/src/map/baseOLMap.js.map +1 -1
  35. package/dist/src/map/cesiumMap.d.ts +2 -0
  36. package/dist/src/map/cesiumMap.js +26 -1
  37. package/dist/src/map/cesiumMap.js.map +1 -1
  38. package/dist/src/map/vcsMap.d.ts +24 -12
  39. package/dist/src/map/vcsMap.js +92 -38
  40. package/dist/src/map/vcsMap.js.map +1 -1
  41. package/dist/src/ol/source/ClusterEnhancedVectorSource.d.ts +6 -4
  42. package/dist/src/ol/source/ClusterEnhancedVectorSource.js +4 -9
  43. package/dist/src/ol/source/ClusterEnhancedVectorSource.js.map +1 -1
  44. package/dist/src/ol/source/VcsCluster.d.ts +10 -10
  45. package/dist/src/ol/source/VcsCluster.js +23 -7
  46. package/dist/src/ol/source/VcsCluster.js.map +1 -1
  47. package/dist/src/util/clipping/clippingPolygonHelper.d.ts +7 -0
  48. package/dist/src/util/clipping/clippingPolygonHelper.js +53 -0
  49. package/dist/src/util/clipping/clippingPolygonHelper.js.map +1 -0
  50. package/dist/src/util/clipping/clippingPolygonObject.d.ts +59 -0
  51. package/dist/src/util/clipping/clippingPolygonObject.js +158 -0
  52. package/dist/src/util/clipping/clippingPolygonObject.js.map +1 -0
  53. package/dist/src/util/clipping/clippingPolygonObjectCollection.d.ts +18 -0
  54. package/dist/src/util/clipping/clippingPolygonObjectCollection.js +167 -0
  55. package/dist/src/util/clipping/clippingPolygonObjectCollection.js.map +1 -0
  56. package/dist/src/util/layerCollection.d.ts +11 -1
  57. package/dist/src/util/layerCollection.js +67 -12
  58. package/dist/src/util/layerCollection.js.map +1 -1
  59. package/dist/src/util/mapCollection.d.ts +16 -1
  60. package/dist/src/util/mapCollection.js +37 -3
  61. package/dist/src/util/mapCollection.js.map +1 -1
  62. package/dist/src/util/renderScreenshot.d.ts +9 -0
  63. package/dist/src/util/renderScreenshot.js +162 -0
  64. package/dist/src/util/renderScreenshot.js.map +1 -0
  65. package/dist/src/util/rotation.d.ts +30 -0
  66. package/dist/src/util/rotation.js +145 -0
  67. package/dist/src/util/rotation.js.map +1 -0
  68. package/dist/src/util/vcsTemplate.d.ts +7 -0
  69. package/dist/src/util/vcsTemplate.js +248 -0
  70. package/dist/src/util/vcsTemplate.js.map +1 -0
  71. package/dist/src/vcsApp.d.ts +7 -0
  72. package/dist/src/vcsApp.js +29 -0
  73. package/dist/src/vcsApp.js.map +1 -1
  74. package/dist/src/vcsModule.d.ts +6 -2
  75. package/dist/src/vcsModule.js.map +1 -1
  76. package/dist/src/vectorCluster/vectorClusterCesiumContext.d.ts +18 -0
  77. package/dist/src/{layer/cesium/clusterContext.js → vectorCluster/vectorClusterCesiumContext.js} +28 -42
  78. package/dist/src/vectorCluster/vectorClusterCesiumContext.js.map +1 -0
  79. package/dist/src/vectorCluster/vectorClusterGroup.d.ts +96 -0
  80. package/dist/src/vectorCluster/vectorClusterGroup.js +320 -0
  81. package/dist/src/vectorCluster/vectorClusterGroup.js.map +1 -0
  82. package/dist/src/vectorCluster/vectorClusterGroupCesiumImpl.d.ts +20 -0
  83. package/dist/src/vectorCluster/vectorClusterGroupCesiumImpl.js +115 -0
  84. package/dist/src/vectorCluster/vectorClusterGroupCesiumImpl.js.map +1 -0
  85. package/dist/src/vectorCluster/vectorClusterGroupCollection.d.ts +19 -0
  86. package/dist/src/vectorCluster/vectorClusterGroupCollection.js +37 -0
  87. package/dist/src/vectorCluster/vectorClusterGroupCollection.js.map +1 -0
  88. package/dist/src/vectorCluster/vectorClusterGroupImpl.d.ts +31 -0
  89. package/dist/src/vectorCluster/vectorClusterGroupImpl.js +76 -0
  90. package/dist/src/vectorCluster/vectorClusterGroupImpl.js.map +1 -0
  91. package/dist/src/vectorCluster/vectorClusterGroupObliqueImpl.d.ts +17 -0
  92. package/dist/src/vectorCluster/vectorClusterGroupObliqueImpl.js +62 -0
  93. package/dist/src/vectorCluster/vectorClusterGroupObliqueImpl.js.map +1 -0
  94. package/dist/src/vectorCluster/vectorClusterGroupOpenlayersImpl.d.ts +17 -0
  95. package/dist/src/vectorCluster/vectorClusterGroupOpenlayersImpl.js +62 -0
  96. package/dist/src/vectorCluster/vectorClusterGroupOpenlayersImpl.js.map +1 -0
  97. package/dist/src/vectorCluster/vectorClusterStyleItem.d.ts +110 -0
  98. package/dist/src/vectorCluster/vectorClusterStyleItem.js +374 -0
  99. package/dist/src/vectorCluster/vectorClusterStyleItem.js.map +1 -0
  100. package/dist/src/vectorCluster/vectorClusterSymbols.d.ts +1 -0
  101. package/dist/src/vectorCluster/vectorClusterSymbols.js +3 -0
  102. package/dist/src/vectorCluster/vectorClusterSymbols.js.map +1 -0
  103. package/index.ts +42 -1
  104. package/package.json +3 -1
  105. package/src/cesium/cesium.d.ts +3 -0
  106. package/src/featureProvider/featureProviderSymbols.ts +6 -1
  107. package/src/interaction/featureAtPixelInteraction.ts +109 -84
  108. package/src/interaction/featureProviderInteraction.ts +42 -28
  109. package/src/layer/cesium/sourceVectorContextSync.ts +134 -0
  110. package/src/layer/cesium/vcsTile/vcsDebugTile.ts +1 -1
  111. package/src/layer/cesium/vcsTile/vcsVectorTile.ts +1 -1
  112. package/src/layer/cesium/vectorCesiumImpl.ts +30 -144
  113. package/src/layer/cesium/vectorContext.ts +17 -1
  114. package/src/layer/layerSymbols.ts +1 -1
  115. package/src/layer/oblique/sourceObliqueSync.ts +436 -0
  116. package/src/layer/oblique/vectorObliqueImpl.ts +11 -397
  117. package/src/layer/vectorLayer.ts +35 -2
  118. package/src/map/baseOLMap.ts +8 -1
  119. package/src/map/cesiumMap.ts +36 -3
  120. package/src/map/vcsMap.ts +121 -47
  121. package/src/ol/ol.d.ts +8 -1
  122. package/src/ol/source/{ClusterEnhancedVectorSource.js → ClusterEnhancedVectorSource.ts} +7 -10
  123. package/src/ol/source/VcsCluster.ts +58 -0
  124. package/src/util/clipping/clippingPolygonHelper.ts +86 -0
  125. package/src/util/clipping/clippingPolygonObject.ts +223 -0
  126. package/src/util/clipping/clippingPolygonObjectCollection.ts +249 -0
  127. package/src/util/layerCollection.ts +90 -12
  128. package/src/util/mapCollection.ts +53 -2
  129. package/src/util/renderScreenshot.ts +193 -0
  130. package/src/util/rotation.ts +215 -0
  131. package/src/util/vcsTemplate.ts +373 -0
  132. package/src/vcsApp.ts +65 -0
  133. package/src/vcsModule.ts +6 -2
  134. package/src/vectorCluster/vectorClusterCesiumContext.ts +123 -0
  135. package/src/vectorCluster/vectorClusterGroup.ts +463 -0
  136. package/src/vectorCluster/vectorClusterGroupCesiumImpl.ts +176 -0
  137. package/src/vectorCluster/vectorClusterGroupCollection.ts +43 -0
  138. package/src/vectorCluster/vectorClusterGroupImpl.ts +107 -0
  139. package/src/vectorCluster/vectorClusterGroupObliqueImpl.ts +84 -0
  140. package/src/vectorCluster/vectorClusterGroupOpenlayersImpl.ts +81 -0
  141. package/src/vectorCluster/vectorClusterStyleItem.ts +490 -0
  142. package/src/vectorCluster/vectorClusterSymbols.ts +2 -0
  143. package/dist/src/layer/cesium/clusterContext.d.ts +0 -20
  144. package/dist/src/layer/cesium/clusterContext.js.map +0 -1
  145. package/src/layer/cesium/clusterContext.ts +0 -140
  146. package/src/ol/source/VcsCluster.js +0 -37
@@ -0,0 +1,373 @@
1
+ import {
2
+ BooleanType,
3
+ newParsingContext,
4
+ StringType,
5
+ NoneType,
6
+ LiteralValue,
7
+ } from 'ol/expr/expression.js';
8
+ import { buildExpression, newEvaluationContext } from 'ol/expr/cpu.js';
9
+ import { is } from '@vcsuite/check';
10
+
11
+ type Block = {
12
+ opening: RegExpExecArray;
13
+ closing: RegExpExecArray;
14
+ };
15
+
16
+ type ConditionalBlock = Block & {
17
+ elseStatement?: RegExpExecArray;
18
+ elseIfs: RegExpExecArray[];
19
+ };
20
+
21
+ /**
22
+ * @param {string} expressionString
23
+ * @param {Record<string, unknown>} data
24
+ * @param {number} evaluationType
25
+ * @returns {*}
26
+ */
27
+ function evaluateExpression(
28
+ expressionString: string,
29
+ data: Record<string, unknown>,
30
+ evaluationType: number,
31
+ ): LiteralValue {
32
+ const parsed = expressionString.startsWith('[')
33
+ ? (JSON.parse(expressionString) as any[])
34
+ : [
35
+ 'get',
36
+ ...expressionString
37
+ .replace(/\[([^\]]+)]/g, '.$1')
38
+ .split('.')
39
+ .filter((f) => f),
40
+ ];
41
+
42
+ const compiledExpression = buildExpression(
43
+ parsed,
44
+ evaluationType,
45
+ newParsingContext(),
46
+ );
47
+ const evaluationContext = newEvaluationContext();
48
+ evaluationContext.properties = data;
49
+ return compiledExpression(evaluationContext);
50
+ }
51
+
52
+ /**
53
+ * Replaces template strings by provided attributes, e.g. {{myAttribute}}
54
+ */
55
+ function replaceAttributes(
56
+ template: string,
57
+ data: Record<string, unknown>,
58
+ ): string {
59
+ const pattern = /\{\{([^}]+)}}/g;
60
+ return template.replace(pattern, (_p, value) => {
61
+ return (
62
+ (evaluateExpression(
63
+ (value as string).trim(),
64
+ data,
65
+ StringType,
66
+ ) as string) ?? ''
67
+ );
68
+ });
69
+ }
70
+
71
+ function regexHits(regexp: RegExp, string: string): RegExpExecArray[] {
72
+ const hits = [];
73
+ let hit;
74
+ // eslint-disable-next-line no-cond-assign
75
+ while ((hit = regexp.exec(string))) {
76
+ hits.push(hit);
77
+ }
78
+
79
+ return hits;
80
+ }
81
+
82
+ function findTopLevelBlock(
83
+ openings: RegExpExecArray[],
84
+ closings: RegExpExecArray[],
85
+ accepted: (b: Block) => void,
86
+ rejected: (b: Block) => void,
87
+ ): void {
88
+ const localOpenings = openings.slice();
89
+ const localClosings = closings.slice();
90
+ while (localOpenings.length > 0) {
91
+ let matchingClosing;
92
+ let matchingOpening;
93
+ while (!matchingClosing && localClosings.length > 0) {
94
+ const currentClosing = localClosings.shift()!;
95
+ const openingDistances = localOpenings.map(
96
+ (o) => currentClosing.index - o.index,
97
+ );
98
+ const minDistance = openingDistances.reduce((min, currentDistance) => {
99
+ if (currentDistance > 0 && currentDistance < min) {
100
+ return currentDistance;
101
+ }
102
+ return min;
103
+ }, Infinity);
104
+ const matchingOpeningIndex = openingDistances.indexOf(minDistance);
105
+ matchingOpening = localOpenings[matchingOpeningIndex];
106
+
107
+ if (matchingOpeningIndex === 0) {
108
+ matchingClosing = currentClosing;
109
+ } else {
110
+ rejected({ opening: matchingOpening, closing: currentClosing });
111
+ }
112
+ localOpenings.splice(matchingOpeningIndex, 1);
113
+ }
114
+
115
+ if (matchingOpening && matchingClosing) {
116
+ accepted({ opening: matchingOpening, closing: matchingClosing });
117
+ }
118
+ }
119
+ }
120
+
121
+ function tagWithinBlock(tag: RegExpExecArray, block: Block): boolean {
122
+ return tag.index > block.opening.index && tag.index < block.closing.index;
123
+ }
124
+
125
+ function getForEachBlocks(template: string): Block[] {
126
+ const forEachBlocks = [] as Block[];
127
+ const forEachOpenings = regexHits(
128
+ /\s*{{#each\s\(([^.)]+)\)\sin\s([^}]+)}}\s*/g,
129
+ template,
130
+ );
131
+ const forEachClosings = regexHits(/\s*{{\/each}}\s*/g, template);
132
+
133
+ if (forEachClosings.length > forEachOpenings.length) {
134
+ throw new Error(
135
+ 'Template failed to render, missing opening tag for each statements',
136
+ );
137
+ } else if (forEachClosings.length < forEachOpenings.length) {
138
+ throw new Error(
139
+ 'Template failed to render, missing closing tag for each statements',
140
+ );
141
+ }
142
+
143
+ findTopLevelBlock(
144
+ forEachOpenings,
145
+ forEachClosings,
146
+ (block) => {
147
+ forEachBlocks.push(block);
148
+ },
149
+ () => {},
150
+ );
151
+
152
+ return forEachBlocks;
153
+ }
154
+
155
+ function getConditionalBlocks(
156
+ template: string,
157
+ forEachBlocks: Block[],
158
+ ): ConditionalBlock[] {
159
+ const conditionalBlocks = [] as ConditionalBlock[];
160
+ let conditionalOpenings = regexHits(/\s*{{#if\s([^}]*)}}\s*/g, template);
161
+ let conditionalClosings = regexHits(/\s*{{\/if}}\s*/g, template);
162
+ let elseIfs = regexHits(/\s*{{elseif\s([^}]*)}}\s*/g, template);
163
+ let elses = regexHits(/\s*{{else}}\s*/g, template);
164
+
165
+ const withinForEachBlock = (tag: RegExpExecArray): Block | undefined =>
166
+ forEachBlocks.find((block) => tagWithinBlock(tag, block));
167
+
168
+ // conditionals within a for each blocks are rendered with the for each block, ignore
169
+ conditionalOpenings = conditionalOpenings.filter(
170
+ (t) => !withinForEachBlock(t),
171
+ );
172
+ conditionalClosings = conditionalClosings.filter(
173
+ (t) => !withinForEachBlock(t),
174
+ );
175
+
176
+ if (conditionalClosings.length > conditionalOpenings.length) {
177
+ throw new Error(
178
+ 'Template failed to render, missing closing tag for if statements',
179
+ );
180
+ } else if (conditionalClosings.length < conditionalOpenings.length) {
181
+ throw new Error(
182
+ 'Template failed to render, missing opening tag for if statements',
183
+ );
184
+ }
185
+
186
+ const filterElseIfElse = (block: Block): void => {
187
+ elseIfs = elseIfs.filter((tag) => !tagWithinBlock(tag, block));
188
+ elses = elses.filter((tag) => !tagWithinBlock(tag, block));
189
+ };
190
+
191
+ findTopLevelBlock(
192
+ conditionalOpenings,
193
+ conditionalClosings,
194
+ (block) => {
195
+ const blockElseIfs = elseIfs.filter((tag) => tagWithinBlock(tag, block));
196
+ const elseStatement = elses.find((tag) => tagWithinBlock(tag, block));
197
+ if (
198
+ elseStatement &&
199
+ blockElseIfs.length > 0 &&
200
+ elseStatement.index < blockElseIfs.at(-1)!.index
201
+ ) {
202
+ throw new Error('{{else}} must be the last entry in a block');
203
+ }
204
+ conditionalBlocks.push({
205
+ ...block,
206
+ elseStatement,
207
+ elseIfs: blockElseIfs,
208
+ });
209
+ },
210
+ (block) => {
211
+ filterElseIfElse(block);
212
+ },
213
+ );
214
+
215
+ return conditionalBlocks;
216
+ }
217
+
218
+ function shouldRemoveWhiteSpace(openingTag: string): boolean {
219
+ return /\n\s*\{/.test(openingTag) && /}\s*\n/.test(openingTag);
220
+ }
221
+
222
+ /**
223
+ * This will extract the block to render separately. This will depend on the white space handling. If the
224
+ * opening is placed on its own line, whitespace after the opening and before the closing blocks will be removed
225
+ * from the sub template, up to the first new line feed.
226
+ */
227
+ function getSubTemplateForBlock(template: string, block: Block): string {
228
+ const removeWhiteSpace = shouldRemoveWhiteSpace(block.opening[0]);
229
+ let startIndex = block.opening.index + block.opening[0].indexOf('}') + 2;
230
+ let endIndex = block.closing.index + block.closing[0].indexOf('{');
231
+ if (removeWhiteSpace) {
232
+ startIndex += (/}\s*\n/.exec(block.opening[0])?.[0].length ?? 1) - 1;
233
+ endIndex -= (/\n\s*\{/.exec(block.closing[0])?.[0].length ?? 2) - 2;
234
+ }
235
+
236
+ return template.substring(startIndex, endIndex);
237
+ }
238
+
239
+ /**
240
+ * This will replace a block with a previously extracted blocks rendered template.
241
+ * This will depend on the white space handling. If the opening is placed on its own line,
242
+ * whitespace before the opening and after the closing blocks will be removed up to the first new line feed,
243
+ * from the new template string all together.
244
+ */
245
+ function replaceBlock(
246
+ template: string,
247
+ block: Block,
248
+ replacement: string,
249
+ ): string {
250
+ const removeWhiteSpace = shouldRemoveWhiteSpace(block.opening[0]);
251
+ let startIndex = block.opening.index + block.opening[0].indexOf('{');
252
+ let endIndex = block.closing.index + block.closing[0].indexOf('}') + 2;
253
+ if (removeWhiteSpace) {
254
+ startIndex -= (/\n\s*\{/.exec(block.opening[0])?.[0].length ?? 2) - 2;
255
+ endIndex += (/}\s*\n/.exec(block.closing[0])?.[0].length ?? 1) - 1;
256
+ }
257
+
258
+ return `${template.substring(
259
+ 0,
260
+ startIndex,
261
+ )}${replacement}${template.substring(endIndex)}`;
262
+ }
263
+
264
+ /**
265
+ * Replaces {{#if }} blocks
266
+ */
267
+ function expandConditionalsAndLoops(
268
+ template: string,
269
+ data: Record<string, unknown>,
270
+ ): string {
271
+ let renderedTemplate = template;
272
+ const forEachBlocks = getForEachBlocks(template);
273
+
274
+ getConditionalBlocks(template, forEachBlocks)
275
+ .reverse()
276
+ .forEach(
277
+ /** @param {ConditionalBlock} block */ (block) => {
278
+ const partialBlocks = [block.opening];
279
+ if (block.elseIfs) {
280
+ partialBlocks.push(...block.elseIfs);
281
+ }
282
+ let trueStatementIndex = partialBlocks.findIndex((s) =>
283
+ evaluateExpression(s[1].trim(), data, BooleanType),
284
+ );
285
+ if (trueStatementIndex === -1 && block.elseStatement) {
286
+ trueStatementIndex = partialBlocks.length;
287
+ }
288
+
289
+ let renderedBlock = '';
290
+ if (trueStatementIndex > -1) {
291
+ if (block.elseStatement) {
292
+ partialBlocks.push(block.elseStatement);
293
+ }
294
+ partialBlocks.push(block.closing);
295
+
296
+ const blockTemplate = getSubTemplateForBlock(template, {
297
+ opening: partialBlocks[trueStatementIndex],
298
+ closing: partialBlocks[trueStatementIndex + 1],
299
+ });
300
+
301
+ renderedBlock = expandConditionalsAndLoops(blockTemplate, data);
302
+ }
303
+
304
+ renderedTemplate = replaceBlock(renderedTemplate, block, renderedBlock);
305
+ },
306
+ );
307
+
308
+ // only iterate over blocks not removed by conditionals
309
+ getForEachBlocks(renderedTemplate)
310
+ .reverse()
311
+ .forEach((block) => {
312
+ const obj = evaluateExpression(block.opening[2].trim(), data, NoneType);
313
+ let keyValuePairs;
314
+ if (is(obj, Object)) {
315
+ keyValuePairs = Object.entries(obj);
316
+ } else if (Array.isArray(obj)) {
317
+ keyValuePairs = obj.entries();
318
+ }
319
+ const renderedBlocks = [];
320
+ if (keyValuePairs) {
321
+ let index = 0;
322
+ const [valueName, keyName, indexName] = block.opening[1]
323
+ .split(',')
324
+ .map((e) => e.trim())
325
+ .slice(0, 3);
326
+
327
+ const blockTemplate = getSubTemplateForBlock(renderedTemplate, block);
328
+ // eslint-disable-next-line no-restricted-syntax
329
+ for (const args of keyValuePairs) {
330
+ const forEachData = structuredClone(data);
331
+ forEachData[valueName] = args[1];
332
+ if (keyName) {
333
+ forEachData[keyName] = args[0];
334
+ }
335
+ if (indexName) {
336
+ forEachData[indexName] = index;
337
+ }
338
+ const currentBlock = expandConditionalsAndLoops(
339
+ blockTemplate,
340
+ forEachData,
341
+ );
342
+ renderedBlocks.push(replaceAttributes(currentBlock, forEachData));
343
+ index += 1;
344
+ }
345
+ }
346
+
347
+ renderedTemplate = replaceBlock(
348
+ renderedTemplate,
349
+ block,
350
+ renderedBlocks.join(''),
351
+ );
352
+ });
353
+
354
+ return renderedTemplate;
355
+ }
356
+
357
+ /**
358
+ * Renders a template in these steps. See {@link documentation/vcsTemplate.md} for more information.
359
+ * 1. expand conditional blocks. this will remove any blocks that do not match their expressions and choose from if / elseif / else block which of them to render
360
+ * 2. expand iterations. this will create new templates for each iteration and re-run the rendering for those blocks
361
+ * 3. render attributes. this will add the attributes to all the blocks not within each blocks
362
+ */
363
+ // eslint-disable-next-line import/prefer-default-export
364
+ export function renderTemplate(
365
+ template: string | string[],
366
+ data: Record<string, unknown>,
367
+ ): string {
368
+ const templateString = Array.isArray(template)
369
+ ? template.join('\n')
370
+ : template;
371
+ const conditionalTemplate = expandConditionalsAndLoops(templateString, data);
372
+ return replaceAttributes(conditionalTemplate, data);
373
+ }
package/src/vcsApp.ts CHANGED
@@ -52,6 +52,12 @@ import FlightInstance, {
52
52
  } from './util/flight/flightInstance.js';
53
53
  import FlightCollection from './util/flight/flightCollection.js';
54
54
  import DisplayQuality from './util/displayQuality/displayQuality.js';
55
+ import VectorClusterGroup from './vectorCluster/vectorClusterGroup.js';
56
+ import VectorClusterGroupCollection from './vectorCluster/vectorClusterGroupCollection.js';
57
+ import ClippingPolygonObject, {
58
+ ClippingPolygonObjectOptions,
59
+ } from './util/clipping/clippingPolygonObject.js';
60
+ import ClippingPolygonObjectCollection from './util/clipping/clippingPolygonObjectCollection.js';
55
61
 
56
62
  function getLogger(): Logger {
57
63
  return getLoggerByName('init');
@@ -104,6 +110,11 @@ class VcsApp {
104
110
 
105
111
  private _layers: OverrideCollection<Layer, LayerCollection>;
106
112
 
113
+ private _vectorClusterGroups: OverrideCollection<
114
+ VectorClusterGroup,
115
+ VectorClusterGroupCollection
116
+ >;
117
+
107
118
  private _obliqueCollections: OverrideCollection<ObliqueCollection>;
108
119
 
109
120
  private _viewpoints: OverrideCollection<Viewpoint>;
@@ -116,6 +127,8 @@ class VcsApp {
116
127
 
117
128
  private _hiddenObjects: OverrideCollection<HiddenObject>;
118
129
 
130
+ private _clippingPolygons: OverrideCollection<ClippingPolygonObject>;
131
+
119
132
  private _flights: OverrideCollection<FlightInstance, FlightCollection>;
120
133
 
121
134
  private _categoryClassRegistry: OverrideClassRegistry<
@@ -183,6 +196,14 @@ class VcsApp {
183
196
  );
184
197
  this._layers.locale = this.locale;
185
198
 
199
+ this._vectorClusterGroups = makeOverrideCollection(
200
+ this._layers.vectorClusterGroups,
201
+ getDynamicModuleId,
202
+ undefined,
203
+ (config) => new VectorClusterGroup(config),
204
+ VectorClusterGroup,
205
+ );
206
+
186
207
  this._obliqueCollections = makeOverrideCollection(
187
208
  new Collection(),
188
209
  getDynamicModuleId,
@@ -217,6 +238,14 @@ class VcsApp {
217
238
  this._layers.globalHider,
218
239
  );
219
240
 
241
+ this._clippingPolygons = makeOverrideCollection(
242
+ new ClippingPolygonObjectCollection(this),
243
+ getDynamicModuleId,
244
+ undefined,
245
+ (clippingPolygonOptions: ClippingPolygonObjectOptions) =>
246
+ new ClippingPolygonObject(clippingPolygonOptions),
247
+ );
248
+
220
249
  this._flights = makeOverrideCollection(
221
250
  new FlightCollection(this),
222
251
  getDynamicModuleId,
@@ -286,6 +315,13 @@ class VcsApp {
286
315
  return this._layers;
287
316
  }
288
317
 
318
+ get vectorClusterGroups(): OverrideCollection<
319
+ VectorClusterGroup,
320
+ VectorClusterGroupCollection
321
+ > {
322
+ return this._vectorClusterGroups;
323
+ }
324
+
289
325
  get obliqueCollections(): OverrideCollection<ObliqueCollection> {
290
326
  return this._obliqueCollections;
291
327
  }
@@ -306,6 +342,10 @@ class VcsApp {
306
342
  return this._hiddenObjects;
307
343
  }
308
344
 
345
+ get clippingPolygons(): OverrideCollection<ClippingPolygonObject> {
346
+ return this._clippingPolygons;
347
+ }
348
+
309
349
  get flights(): OverrideCollection<FlightInstance, FlightCollection> {
310
350
  return this._flights;
311
351
  }
@@ -383,6 +423,10 @@ class VcsApp {
383
423
 
384
424
  await this._styles.parseItems(config.styles, module._id);
385
425
  await this._layers.parseItems(config.layers, module._id);
426
+ await this._vectorClusterGroups.parseItems(
427
+ config.vectorClusterGroups,
428
+ module._id,
429
+ );
386
430
  // TODO add ade here
387
431
 
388
432
  await this._obliqueCollections.parseItems(
@@ -392,6 +436,10 @@ class VcsApp {
392
436
  await this._viewpoints.parseItems(config.viewpoints, module._id);
393
437
  await this._maps.parseItems(config.maps, module._id);
394
438
  await this._hiddenObjects.parseItems(config.hiddenObjects, module._id);
439
+ await this._clippingPolygons.parseItems(
440
+ config.clippingPolygons,
441
+ module._id,
442
+ );
395
443
  await this._flights.parseItems(config.flights, module._id);
396
444
 
397
445
  if (Array.isArray(config.categories)) {
@@ -418,6 +466,14 @@ class VcsApp {
418
466
  }
419
467
  });
420
468
 
469
+ [...this._clippingPolygons]
470
+ .filter((c) => c[moduleIdSymbol] === module._id)
471
+ .forEach((c) => {
472
+ if (c.activeOnStartup) {
473
+ c.activate();
474
+ }
475
+ });
476
+
421
477
  if (config.startingObliqueCollectionName) {
422
478
  const startingObliqueCollection = this._obliqueCollections.getByKey(
423
479
  config.startingObliqueCollectionName,
@@ -516,6 +572,8 @@ class VcsApp {
516
572
  const config = this._modules.getByKey(moduleId)!.toJSON();
517
573
  config.maps = this._maps.serializeModule(moduleId);
518
574
  config.layers = this._layers.serializeModule(moduleId);
575
+ config.vectorClusterGroups =
576
+ this._vectorClusterGroups.serializeModule(moduleId);
519
577
  config.obliqueCollections =
520
578
  this._obliqueCollections.serializeModule(moduleId);
521
579
  config.viewpoints = this._viewpoints.serializeModule(moduleId);
@@ -523,6 +581,9 @@ class VcsApp {
523
581
  config.hiddenObjects = this._hiddenObjects.serializeModule(
524
582
  moduleId,
525
583
  ) as HiddenObject[];
584
+ config.clippingPolygons = this._clippingPolygons.serializeModule(
585
+ moduleId,
586
+ ) as ClippingPolygonObjectOptions[];
526
587
  config.flights = this._flights.serializeModule(moduleId);
527
588
  config.categories = [...this._categories]
528
589
  .map((c) => c.serializeModule(moduleId))
@@ -562,7 +623,9 @@ class VcsApp {
562
623
  this._styles.removeModule(moduleId),
563
624
  this._obliqueCollections.removeModule(moduleId),
564
625
  this._hiddenObjects.removeModule(moduleId),
626
+ this._clippingPolygons.removeModule(moduleId),
565
627
  this._flights.removeModule(moduleId),
628
+ this._vectorClusterGroups.removeModule(moduleId),
566
629
  ]);
567
630
  }
568
631
 
@@ -603,8 +666,10 @@ class VcsApp {
603
666
  destroyCollection(this._layers);
604
667
  destroyCollection(this._obliqueCollections);
605
668
  destroyCollection(this._viewpoints);
669
+ destroyCollection(this._flights);
606
670
  destroyCollection(this._styles);
607
671
  destroyCollection(this._categories);
672
+ destroyCollection(this._clippingPolygons);
608
673
  this._modules.destroy();
609
674
  this._hiddenObjects.destroy();
610
675
  this._mapClassRegistry.destroy();
package/src/vcsModule.ts CHANGED
@@ -7,8 +7,10 @@ import type { ViewpointOptions } from './util/viewpoint.js';
7
7
  import type { ObliqueCollectionOptions } from './oblique/obliqueCollection.js';
8
8
  import type VcsApp from './vcsApp.js';
9
9
  import { moduleIdSymbol } from './moduleIdSymbol.js';
10
- import { HiddenObject } from './util/hiddenObjects.js';
11
- import { FlightInstanceOptions } from './util/flight/flightInstance.js';
10
+ import type { HiddenObject } from './util/hiddenObjects.js';
11
+ import type { FlightInstanceOptions } from './util/flight/flightInstance.js';
12
+ import type { VectorClusterGroupOptions } from './vectorCluster/vectorClusterGroup.js';
13
+ import type { ClippingPolygonObjectOptions } from './util/clipping/clippingPolygonObject.js';
12
14
 
13
15
  export type VcsModuleConfig = {
14
16
  _id?: string;
@@ -16,6 +18,7 @@ export type VcsModuleConfig = {
16
18
  description?: string | null;
17
19
  properties?: Record<string, unknown>;
18
20
  layers?: LayerOptions[];
21
+ vectorClusterGroups?: VectorClusterGroupOptions[];
19
22
  maps?: VcsMapOptions[];
20
23
  styles?: StyleItemOptions[];
21
24
  viewpoints?: ViewpointOptions[];
@@ -26,6 +29,7 @@ export type VcsModuleConfig = {
26
29
  obliqueCollections?: ObliqueCollectionOptions[];
27
30
  categories?: { name: string; items: object[] }[];
28
31
  hiddenObjects?: HiddenObject[];
32
+ clippingPolygons?: ClippingPolygonObjectOptions[];
29
33
  flights?: FlightInstanceOptions[];
30
34
  };
31
35
 
@@ -0,0 +1,123 @@
1
+ import {
2
+ CustomDataSource,
3
+ EntityCollection,
4
+ Entity,
5
+ Scene,
6
+ } from '@vcmap-cesium/engine';
7
+ import { StyleLike } from 'ol/style/Style.js';
8
+ import type { Feature } from 'ol/index.js';
9
+ import {
10
+ CesiumVectorContext,
11
+ setReferenceForPicking,
12
+ } from '../layer/cesium/vectorContext.js';
13
+ import VectorProperties from '../layer/vectorProperties.js';
14
+ import convert, { ConvertedItem } from '../util/featureconverter/convert.js';
15
+
16
+ class VectorClusterCesiumContext implements CesiumVectorContext {
17
+ entities: EntityCollection;
18
+
19
+ private _featureItems = new Map<Feature, () => void>();
20
+
21
+ private _convertingFeatures: Map<Feature, () => void> = new Map();
22
+
23
+ constructor(dataSource: CustomDataSource) {
24
+ this.entities = dataSource.entities;
25
+ }
26
+
27
+ private _addConvertedItems(
28
+ feature: Feature,
29
+ allowPicking: boolean,
30
+ items: ConvertedItem[],
31
+ ): void {
32
+ let entityOptions: Entity.ConstructorOptions | undefined;
33
+ items.forEach((item) => {
34
+ if (item.type === 'billboard') {
35
+ entityOptions = entityOptions ?? {};
36
+ entityOptions.billboard = {
37
+ ...item.item,
38
+ };
39
+ entityOptions.position = entityOptions.position ?? item.item.position;
40
+ } else if (item.type === 'label') {
41
+ entityOptions = entityOptions ?? {};
42
+ entityOptions.label = item.item;
43
+ entityOptions.position = entityOptions.position ?? item.item.position;
44
+ }
45
+ });
46
+
47
+ if (entityOptions) {
48
+ const instance = this.entities.add(entityOptions);
49
+ if (instance) {
50
+ if (allowPicking) {
51
+ setReferenceForPicking(feature, instance);
52
+ }
53
+ this._featureItems.set(feature, (): void => {
54
+ this.entities.remove(instance);
55
+ });
56
+ }
57
+ }
58
+ }
59
+
60
+ async addFeature(
61
+ feature: Feature,
62
+ style: StyleLike,
63
+ vectorProperties: VectorProperties,
64
+ scene: Scene,
65
+ ): Promise<void> {
66
+ this._convertingFeatures.get(feature)?.();
67
+ let deleted = false;
68
+ this._convertingFeatures.set(feature, () => {
69
+ deleted = true;
70
+ });
71
+
72
+ const convertedItems = await convert(
73
+ feature,
74
+ style,
75
+ vectorProperties,
76
+ scene,
77
+ );
78
+
79
+ this._featureItems.get(feature)?.();
80
+
81
+ if (deleted) {
82
+ convertedItems.forEach((item) => {
83
+ if (item.type === 'primitive') {
84
+ item.item.destroy();
85
+ }
86
+ });
87
+ } else {
88
+ this._addConvertedItems(
89
+ feature,
90
+ vectorProperties.getAllowPicking(feature),
91
+ convertedItems,
92
+ );
93
+ }
94
+ }
95
+
96
+ hasFeature(feature: Feature): boolean {
97
+ return (
98
+ this._featureItems.has(feature) || this._convertingFeatures.has(feature)
99
+ );
100
+ }
101
+
102
+ removeFeature(feature: Feature): void {
103
+ this._convertingFeatures.get(feature)?.();
104
+ this._convertingFeatures.delete(feature);
105
+ this._featureItems.get(feature)?.();
106
+ this._featureItems.delete(feature);
107
+ }
108
+
109
+ clear(): void {
110
+ this.entities.removeAll();
111
+ this._featureItems.clear();
112
+ this._convertingFeatures.forEach((destroy) => {
113
+ destroy();
114
+ });
115
+ this._convertingFeatures.clear();
116
+ }
117
+
118
+ destroy(): void {
119
+ this.clear();
120
+ }
121
+ }
122
+
123
+ export default VectorClusterCesiumContext;