@mapwhit/tilerenderer 1.3.0 → 1.5.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/build/min/package.json +1 -1
- package/package.json +1 -1
- package/src/data/array_types.js +115 -64
- package/src/data/bucket/circle_bucket.js +42 -5
- package/src/data/bucket/fill_bucket.js +31 -13
- package/src/data/bucket/fill_extrusion_bucket.js +8 -6
- package/src/data/bucket/line_bucket.js +38 -14
- package/src/data/bucket/symbol_attributes.js +13 -5
- package/src/data/bucket/symbol_bucket.js +112 -40
- package/src/data/bucket/symbol_collision_buffers.js +1 -1
- package/src/data/bucket.js +3 -1
- package/src/data/feature_index.js +24 -11
- package/src/data/segment.js +15 -7
- package/src/index.js +23 -23
- package/src/render/draw_circle.js +45 -4
- package/src/render/draw_symbol.js +190 -22
- package/src/render/painter.js +1 -1
- package/src/source/geojson_source.js +118 -21
- package/src/source/geojson_source_diff.js +148 -0
- package/src/source/geojson_tiler.js +89 -0
- package/src/source/rtl_text_plugin.js +133 -66
- package/src/source/source.js +16 -5
- package/src/source/source_cache.js +6 -6
- package/src/source/source_state.js +4 -2
- package/src/source/tile.js +5 -3
- package/src/source/vector_tile_source.js +2 -0
- package/src/source/worker_tile.js +6 -2
- package/src/style/evaluation_parameters.js +2 -3
- package/src/style/pauseable_placement.js +39 -7
- package/src/style/style.js +34 -23
- package/src/style/style_layer/circle_style_layer_properties.js +8 -1
- package/src/style/style_layer/fill_style_layer_properties.js +8 -1
- package/src/style/style_layer/line_style_layer_properties.js +4 -0
- package/src/style/style_layer/symbol_style_layer_properties.js +17 -2
- package/src/style-spec/reference/v8.json +161 -4
- package/src/symbol/one_em.js +4 -0
- package/src/symbol/placement.js +406 -173
- package/src/symbol/projection.js +3 -3
- package/src/symbol/quads.js +1 -6
- package/src/symbol/shaping.js +18 -29
- package/src/symbol/symbol_layout.js +243 -81
- package/src/symbol/transform_text.js +3 -4
- package/src/util/config.js +1 -9
- package/src/util/script_detection.js +19 -7
- package/src/util/vectortile_to_geojson.js +3 -4
- package/src/source/geojson_worker_source.js +0 -97
|
@@ -9,7 +9,10 @@ export const dynamicLayoutAttributes = createLayout([{ name: 'a_projected_pos',
|
|
|
9
9
|
|
|
10
10
|
export const placementOpacityAttributes = createLayout([{ name: 'a_fade_opacity', components: 1, type: 'Uint32' }], 4);
|
|
11
11
|
|
|
12
|
-
export const collisionVertexAttributes = createLayout([
|
|
12
|
+
export const collisionVertexAttributes = createLayout([
|
|
13
|
+
{ name: 'a_placed', components: 2, type: 'Uint8' },
|
|
14
|
+
{ name: 'a_shift', components: 2, type: 'Float32' }
|
|
15
|
+
]);
|
|
13
16
|
|
|
14
17
|
export const collisionBox = createLayout([
|
|
15
18
|
// the box is centered around the anchor point
|
|
@@ -70,13 +73,16 @@ export const placement = createLayout([
|
|
|
70
73
|
{ type: 'Float32', name: 'lineOffsetX' },
|
|
71
74
|
{ type: 'Float32', name: 'lineOffsetY' },
|
|
72
75
|
{ type: 'Uint8', name: 'writingMode' },
|
|
73
|
-
{ type: 'Uint8', name: 'hidden' }
|
|
76
|
+
{ type: 'Uint8', name: 'hidden' },
|
|
77
|
+
{ type: 'Uint32', name: 'crossTileID' }
|
|
74
78
|
]);
|
|
75
79
|
|
|
76
80
|
export const symbolInstance = createLayout([
|
|
77
81
|
{ type: 'Int16', name: 'anchorX' },
|
|
78
82
|
{ type: 'Int16', name: 'anchorY' },
|
|
79
|
-
{ type: 'Int16', name: '
|
|
83
|
+
{ type: 'Int16', name: 'rightJustifiedTextSymbolIndex' },
|
|
84
|
+
{ type: 'Int16', name: 'centerJustifiedTextSymbolIndex' },
|
|
85
|
+
{ type: 'Int16', name: 'leftJustifiedTextSymbolIndex' },
|
|
80
86
|
{ type: 'Int16', name: 'verticalPlacedTextSymbolIndex' },
|
|
81
87
|
{ type: 'Uint16', name: 'key' },
|
|
82
88
|
{ type: 'Uint16', name: 'textBoxStartIndex' },
|
|
@@ -84,10 +90,12 @@ export const symbolInstance = createLayout([
|
|
|
84
90
|
{ type: 'Uint16', name: 'iconBoxStartIndex' },
|
|
85
91
|
{ type: 'Uint16', name: 'iconBoxEndIndex' },
|
|
86
92
|
{ type: 'Uint16', name: 'featureIndex' },
|
|
87
|
-
{ type: 'Uint16', name: '
|
|
93
|
+
{ type: 'Uint16', name: 'numHorizontalGlyphVertices' },
|
|
88
94
|
{ type: 'Uint16', name: 'numVerticalGlyphVertices' },
|
|
89
95
|
{ type: 'Uint16', name: 'numIconVertices' },
|
|
90
|
-
{ type: 'Uint32', name: 'crossTileID' }
|
|
96
|
+
{ type: 'Uint32', name: 'crossTileID' },
|
|
97
|
+
{ type: 'Float32', name: 'textBoxScale' },
|
|
98
|
+
{ type: 'Float32', name: 'radialTextOffset' }
|
|
91
99
|
]);
|
|
92
100
|
|
|
93
101
|
export const glyphOffset = createLayout([{ type: 'Float32', name: 'offsetX' }]);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { dist } from '@mapwhit/point-geometry';
|
|
2
2
|
import { Formatted } from '@mapwhit/style-expressions';
|
|
3
3
|
import { VectorTileFeature } from '@mapwhit/vector-tile';
|
|
4
|
+
import { rtlPlugin } from '../../source/rtl_text_plugin.js';
|
|
4
5
|
import EvaluationParameters from '../../style/evaluation_parameters.js';
|
|
5
6
|
import mergeLines from '../../symbol/mergelines.js';
|
|
6
7
|
import { getSizeData } from '../../symbol/symbol_size.js';
|
|
7
8
|
import transformText from '../../symbol/transform_text.js';
|
|
8
|
-
import { allowsVerticalWritingMode } from '../../util/script_detection.js';
|
|
9
|
+
import { allowsVerticalWritingMode, stringContainsRTLText } from '../../util/script_detection.js';
|
|
9
10
|
import { verticalizedCharacterMap } from '../../util/verticalize_punctuation.js';
|
|
10
11
|
import {
|
|
11
12
|
CollisionBoxLayoutArray,
|
|
@@ -44,6 +45,15 @@ export function addDynamicAttributes(dynamicLayoutVertexArray, p, angle) {
|
|
|
44
45
|
dynamicLayoutVertexArray.emplaceBack(p.x, p.y, angle);
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
function containsRTLText(formattedText) {
|
|
49
|
+
for (const section of formattedText.sections) {
|
|
50
|
+
if (stringContainsRTLText(section.text)) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
/**
|
|
48
58
|
* Unlike other buckets, which simply implement #addFeature with type-specific
|
|
49
59
|
* logic for (essentially) triangulating feature geometries, SymbolBucket
|
|
@@ -86,6 +96,8 @@ export default class SymbolBucket {
|
|
|
86
96
|
this.pixelRatio = options.pixelRatio;
|
|
87
97
|
this.sourceLayerIndex = options.sourceLayerIndex;
|
|
88
98
|
this.hasPattern = false;
|
|
99
|
+
this.hasRTLText = false;
|
|
100
|
+
this.sortKeyRanges = [];
|
|
89
101
|
|
|
90
102
|
const layer = this.layers[0];
|
|
91
103
|
const unevaluatedLayoutValues = layer._unevaluatedLayout._values;
|
|
@@ -94,7 +106,10 @@ export default class SymbolBucket {
|
|
|
94
106
|
this.iconSizeData = getSizeData(this.zoom, unevaluatedLayoutValues['icon-size']);
|
|
95
107
|
|
|
96
108
|
const layout = this.layers[0]._layout;
|
|
97
|
-
const
|
|
109
|
+
const sortKey = layout.get('symbol-sort-key');
|
|
110
|
+
const zOrder = layout.get('symbol-z-order');
|
|
111
|
+
this.sortFeaturesByKey = zOrder !== 'viewport-y' && sortKey.constantOr(1) !== undefined;
|
|
112
|
+
const zOrderByViewportY = zOrder === 'viewport-y' || (zOrder === 'auto' && !this.sortFeaturesByKey);
|
|
98
113
|
this.sortFeaturesByY =
|
|
99
114
|
zOrderByViewportY &&
|
|
100
115
|
(layout.get('text-allow-overlap') ||
|
|
@@ -102,6 +117,8 @@ export default class SymbolBucket {
|
|
|
102
117
|
layout.get('text-ignore-placement') ||
|
|
103
118
|
layout.get('icon-ignore-placement'));
|
|
104
119
|
|
|
120
|
+
this.stateDependentLayerIds = this.layers.filter(l => l.isStateDependent()).map(l => l.id);
|
|
121
|
+
|
|
105
122
|
this.sourceID = options.sourceID;
|
|
106
123
|
}
|
|
107
124
|
|
|
@@ -140,6 +157,7 @@ export default class SymbolBucket {
|
|
|
140
157
|
(textField.value.kind !== 'constant' || textField.value.value.toString().length > 0) &&
|
|
141
158
|
(textFont.value.kind !== 'constant' || textFont.value.value.length > 0);
|
|
142
159
|
const hasIcon = iconImage.value.kind !== 'constant' || iconImage.value.value?.length > 0;
|
|
160
|
+
const symbolSortKey = layout.get('symbol-sort-key');
|
|
143
161
|
|
|
144
162
|
this.features = [];
|
|
145
163
|
|
|
@@ -151,7 +169,7 @@ export default class SymbolBucket {
|
|
|
151
169
|
const stacks = options.glyphDependencies;
|
|
152
170
|
const globalProperties = new EvaluationParameters(this.zoom);
|
|
153
171
|
|
|
154
|
-
for (const { feature, index, sourceLayerIndex } of features) {
|
|
172
|
+
for (const { feature, id, index, sourceLayerIndex } of features) {
|
|
155
173
|
if (!layer._featureFilter(globalProperties, feature)) {
|
|
156
174
|
continue;
|
|
157
175
|
}
|
|
@@ -162,11 +180,16 @@ export default class SymbolBucket {
|
|
|
162
180
|
// but plain string token evaluation skips that pathway so do the
|
|
163
181
|
// conversion here.
|
|
164
182
|
const resolvedTokens = layer.getValueAndResolveTokens('text-field', feature);
|
|
165
|
-
|
|
166
|
-
resolvedTokens instanceof Formatted ? resolvedTokens : Formatted.fromString(resolvedTokens)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
183
|
+
const formattedText =
|
|
184
|
+
resolvedTokens instanceof Formatted ? resolvedTokens : Formatted.fromString(resolvedTokens);
|
|
185
|
+
// on this instance: if hasRTLText is already true, all future calls to containsRTLText can be skipped.
|
|
186
|
+
const bucketHasRTLText = (this.hasRTLText = this.hasRTLText || containsRTLText(formattedText));
|
|
187
|
+
if (
|
|
188
|
+
!bucketHasRTLText || // non-rtl text so can proceed safely
|
|
189
|
+
rtlPlugin.isRTLSupported(true) // Use the rtlText plugin to shape text if available or We don't intend to lazy-load the rtl text plugin, so proceed with incorrect shaping
|
|
190
|
+
) {
|
|
191
|
+
text = transformText(formattedText, layer, feature);
|
|
192
|
+
}
|
|
170
193
|
}
|
|
171
194
|
|
|
172
195
|
let icon;
|
|
@@ -178,18 +201,19 @@ export default class SymbolBucket {
|
|
|
178
201
|
continue;
|
|
179
202
|
}
|
|
180
203
|
|
|
204
|
+
const sortKey = this.sortFeaturesByKey ? symbolSortKey.evaluate(feature, {}) : undefined;
|
|
205
|
+
|
|
181
206
|
const symbolFeature = {
|
|
207
|
+
id,
|
|
182
208
|
text,
|
|
183
209
|
icon,
|
|
184
210
|
index,
|
|
185
211
|
sourceLayerIndex,
|
|
186
212
|
geometry: loadGeometry(feature),
|
|
187
213
|
properties: feature.properties,
|
|
188
|
-
type: VectorTileFeature.types[feature.type]
|
|
214
|
+
type: VectorTileFeature.types[feature.type],
|
|
215
|
+
sortKey
|
|
189
216
|
};
|
|
190
|
-
if (typeof feature.id !== 'undefined') {
|
|
191
|
-
symbolFeature.id = feature.id;
|
|
192
|
-
}
|
|
193
217
|
this.features.push(symbolFeature);
|
|
194
218
|
|
|
195
219
|
if (icon) {
|
|
@@ -214,6 +238,13 @@ export default class SymbolBucket {
|
|
|
214
238
|
// It's better to place labels on one long line than on many short segments.
|
|
215
239
|
this.features = mergeLines(this.features);
|
|
216
240
|
}
|
|
241
|
+
|
|
242
|
+
if (this.sortFeaturesByKey) {
|
|
243
|
+
this.features.sort((a, b) => {
|
|
244
|
+
// a.sortKey is always a number when sortFeaturesByKey is true
|
|
245
|
+
return a.sortKey - b.sortKey;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
217
248
|
}
|
|
218
249
|
|
|
219
250
|
update(states, vtLayer, imagePositions) {
|
|
@@ -229,7 +260,9 @@ export default class SymbolBucket {
|
|
|
229
260
|
}
|
|
230
261
|
|
|
231
262
|
isEmpty() {
|
|
232
|
-
|
|
263
|
+
// When the bucket encounters only rtl-text but the plugin isnt loaded, no symbol instances will be created.
|
|
264
|
+
// In order for the bucket to be serialized, and not discarded as an empty bucket both checks are necessary.
|
|
265
|
+
return this.symbolInstances.length === 0 && !this.hasRTLText;
|
|
233
266
|
}
|
|
234
267
|
|
|
235
268
|
uploadPending() {
|
|
@@ -295,7 +328,7 @@ export default class SymbolBucket {
|
|
|
295
328
|
lineLength
|
|
296
329
|
) {
|
|
297
330
|
const { indexArray, layoutVertexArray, dynamicLayoutVertexArray, segments } = arrays;
|
|
298
|
-
const segment = segments.prepareSegment(4 * quads.length, layoutVertexArray, indexArray);
|
|
331
|
+
const segment = segments.prepareSegment(4 * quads.length, layoutVertexArray, indexArray, feature.sortKey);
|
|
299
332
|
const glyphOffsetArrayStart = this.glyphOffsetArray.length;
|
|
300
333
|
const vertexStartIndex = segment.vertexLength;
|
|
301
334
|
const { x: lax, y: lay } = labelAnchor;
|
|
@@ -335,7 +368,9 @@ export default class SymbolBucket {
|
|
|
335
368
|
lineOffset[0],
|
|
336
369
|
lineOffset[1],
|
|
337
370
|
writingMode,
|
|
338
|
-
false
|
|
371
|
+
false,
|
|
372
|
+
// The crossTileID is only filled/used on the foreground for dynamic text anchors
|
|
373
|
+
0
|
|
339
374
|
);
|
|
340
375
|
|
|
341
376
|
arrays.programConfigurations.populatePaintArrays(arrays.layoutVertexArray.length, feature, feature.index, {
|
|
@@ -439,6 +474,43 @@ export default class SymbolBucket {
|
|
|
439
474
|
}
|
|
440
475
|
}
|
|
441
476
|
|
|
477
|
+
getSortedSymbolIndexes(angle) {
|
|
478
|
+
if (this.sortedAngle === angle && this.symbolInstanceIndexes !== undefined) {
|
|
479
|
+
return this.symbolInstanceIndexes;
|
|
480
|
+
}
|
|
481
|
+
const sin = Math.sin(angle);
|
|
482
|
+
const cos = Math.cos(angle);
|
|
483
|
+
const rotatedYs = [];
|
|
484
|
+
const featureIndexes = [];
|
|
485
|
+
const result = [];
|
|
486
|
+
|
|
487
|
+
for (let i = 0; i < this.symbolInstances.length; ++i) {
|
|
488
|
+
result.push(i);
|
|
489
|
+
const symbolInstance = this.symbolInstances.get(i);
|
|
490
|
+
rotatedYs.push(Math.round(sin * symbolInstance.anchorX + cos * symbolInstance.anchorY) | 0);
|
|
491
|
+
featureIndexes.push(symbolInstance.featureIndex);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
result.sort((aIndex, bIndex) => {
|
|
495
|
+
return rotatedYs[aIndex] - rotatedYs[bIndex] || featureIndexes[bIndex] - featureIndexes[aIndex];
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
return result;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
addToSortKeyRanges(symbolInstanceIndex, sortKey) {
|
|
502
|
+
const last = this.sortKeyRanges[this.sortKeyRanges.length - 1];
|
|
503
|
+
if (last && last.sortKey === sortKey) {
|
|
504
|
+
last.symbolInstanceEnd = symbolInstanceIndex + 1;
|
|
505
|
+
} else {
|
|
506
|
+
this.sortKeyRanges.push({
|
|
507
|
+
sortKey,
|
|
508
|
+
symbolInstanceStart: symbolInstanceIndex,
|
|
509
|
+
symbolInstanceEnd: symbolInstanceIndex + 1
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
442
514
|
sortFeatures(angle) {
|
|
443
515
|
if (!this.sortFeaturesByY) {
|
|
444
516
|
return;
|
|
@@ -447,7 +519,6 @@ export default class SymbolBucket {
|
|
|
447
519
|
if (this.sortedAngle === angle) {
|
|
448
520
|
return;
|
|
449
521
|
}
|
|
450
|
-
this.sortedAngle = angle;
|
|
451
522
|
|
|
452
523
|
// The current approach to sorting doesn't sort across segments so don't try.
|
|
453
524
|
// Sorting within segments separately seemed not to be worth the complexity.
|
|
@@ -460,35 +531,36 @@ export default class SymbolBucket {
|
|
|
460
531
|
// sorted order.
|
|
461
532
|
|
|
462
533
|
// To avoid sorting the actual symbolInstance array we sort an array of indexes.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const rotatedYs = new Array(slen);
|
|
466
|
-
const featureIndexes = new Array(slen);
|
|
467
|
-
const sin = Math.sin(angle);
|
|
468
|
-
const cos = Math.cos(angle);
|
|
469
|
-
for (let i = 0; i < slen; i++) {
|
|
470
|
-
symbolInstanceIndexes[i] = i;
|
|
471
|
-
const { anchorX, anchorY, featureIndex } = this.symbolInstances.get(i);
|
|
472
|
-
rotatedYs[i] = Math.round(sin * anchorX + cos * anchorY) | 0;
|
|
473
|
-
featureIndexes[i] = featureIndex;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
symbolInstanceIndexes.sort((a, b) => rotatedYs[a] - rotatedYs[b] || featureIndexes[b] - featureIndexes[a]);
|
|
534
|
+
this.symbolInstanceIndexes = this.getSortedSymbolIndexes(angle);
|
|
535
|
+
this.sortedAngle = angle;
|
|
477
536
|
|
|
478
537
|
this.text.indexArray.clear();
|
|
479
538
|
this.icon.indexArray.clear();
|
|
480
539
|
|
|
481
|
-
this.featureSortOrder = new Array(
|
|
482
|
-
|
|
483
|
-
for (let i = 0; i <
|
|
484
|
-
const index = symbolInstanceIndexes[i];
|
|
485
|
-
const {
|
|
486
|
-
|
|
487
|
-
|
|
540
|
+
this.featureSortOrder = new Array(this.symbolInstanceIndexes.length);
|
|
541
|
+
|
|
542
|
+
for (let i = 0; i < this.symbolInstanceIndexes.length; i++) {
|
|
543
|
+
const index = this.symbolInstanceIndexes[i];
|
|
544
|
+
const {
|
|
545
|
+
centerJustifiedTextSymbolIndex,
|
|
546
|
+
featureIndex,
|
|
547
|
+
leftJustifiedTextSymbolIndex,
|
|
548
|
+
rightJustifiedTextSymbolIndex,
|
|
549
|
+
verticalPlacedTextSymbolIndex
|
|
550
|
+
} = this.symbolInstances.get(index);
|
|
551
|
+
this.featureSortOrder.push(featureIndex);
|
|
552
|
+
|
|
553
|
+
[rightJustifiedTextSymbolIndex, centerJustifiedTextSymbolIndex, leftJustifiedTextSymbolIndex].forEach(
|
|
554
|
+
(index, i, array) => {
|
|
555
|
+
// Only add a given index the first time it shows up,
|
|
556
|
+
// to avoid duplicate opacity entries when multiple justifications
|
|
557
|
+
// share the same glyphs.
|
|
558
|
+
if (index >= 0 && array.indexOf(index) === i) {
|
|
559
|
+
this.addIndicesForPlacedTextSymbol(index);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
);
|
|
488
563
|
|
|
489
|
-
if (horizontalPlacedTextSymbolIndex >= 0) {
|
|
490
|
-
this.addIndicesForPlacedTextSymbol(horizontalPlacedTextSymbolIndex);
|
|
491
|
-
}
|
|
492
564
|
if (verticalPlacedTextSymbolIndex >= 0) {
|
|
493
565
|
this.addIndicesForPlacedTextSymbol(verticalPlacedTextSymbolIndex);
|
|
494
566
|
}
|
package/src/data/bucket.js
CHANGED
|
@@ -49,6 +49,8 @@ export function updateBuckets(buckets, style) {
|
|
|
49
49
|
|
|
50
50
|
// swap out the layers in the bucket with the current style layers
|
|
51
51
|
bucket.layers = layers;
|
|
52
|
-
|
|
52
|
+
if (bucket.stateDependentLayerIds?.length) {
|
|
53
|
+
bucket.stateDependentLayers = layers.filter(layer => layer.isStateDependent());
|
|
54
|
+
}
|
|
53
55
|
}
|
|
54
56
|
}
|
|
@@ -9,12 +9,13 @@ import { FeatureIndexArray } from './array_types.js';
|
|
|
9
9
|
import EXTENT from './extent.js';
|
|
10
10
|
import loadGeometry from './load_geometry.js';
|
|
11
11
|
|
|
12
|
-
class FeatureIndex {
|
|
13
|
-
constructor(tileID,
|
|
12
|
+
export default class FeatureIndex {
|
|
13
|
+
constructor(tileID, promoteId) {
|
|
14
14
|
this.tileID = tileID;
|
|
15
|
-
this.grid =
|
|
15
|
+
this.grid = new Grid(EXTENT, 16, 0);
|
|
16
16
|
this.grid3D = new Grid(EXTENT, 16, 0);
|
|
17
|
-
this.featureIndexArray =
|
|
17
|
+
this.featureIndexArray = new FeatureIndexArray();
|
|
18
|
+
this.promoteId = promoteId;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
insert(feature, geometry, featureIndex, sourceLayerIndex, bucketIndex, is3D) {
|
|
@@ -91,14 +92,14 @@ class FeatureIndex {
|
|
|
91
92
|
|
|
92
93
|
const match = this.featureIndexArray.get(index);
|
|
93
94
|
let featureGeometry = null;
|
|
94
|
-
const intersectionTest = (feature, styleLayer) => {
|
|
95
|
+
const intersectionTest = (feature, styleLayer, id) => {
|
|
95
96
|
if (!featureGeometry) {
|
|
96
97
|
featureGeometry = loadGeometry(feature);
|
|
97
98
|
}
|
|
98
99
|
let featureState = {};
|
|
99
|
-
if (
|
|
100
|
+
if (id !== undefined) {
|
|
100
101
|
// `feature-state` expression evaluation requires feature state to be available
|
|
101
|
-
featureState = sourceFeatureState.getState(styleLayer.sourceLayer || '_geojsonTileLayer',
|
|
102
|
+
featureState = sourceFeatureState.getState(styleLayer.sourceLayer || '_geojsonTileLayer', id);
|
|
102
103
|
}
|
|
103
104
|
return styleLayer.queryIntersectsFeature(
|
|
104
105
|
queryGeometry,
|
|
@@ -149,6 +150,8 @@ class FeatureIndex {
|
|
|
149
150
|
return;
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
const id = this.getId(feature, sourceLayerName);
|
|
154
|
+
|
|
152
155
|
const { x, y, z } = this.tileID.canonical;
|
|
153
156
|
for (const layerID of layerIDs) {
|
|
154
157
|
if (filterLayerIDs && !filterLayerIDs.includes(layerID)) {
|
|
@@ -160,13 +163,13 @@ class FeatureIndex {
|
|
|
160
163
|
continue;
|
|
161
164
|
}
|
|
162
165
|
|
|
163
|
-
const intersectionZ = !intersectionTest || intersectionTest(feature, styleLayer);
|
|
166
|
+
const intersectionZ = !intersectionTest || intersectionTest(feature, styleLayer, id);
|
|
164
167
|
if (!intersectionZ) {
|
|
165
168
|
// Only applied for non-symbol features
|
|
166
169
|
continue;
|
|
167
170
|
}
|
|
168
171
|
|
|
169
|
-
const geojsonFeature = new GeoJSONFeature(feature, z, x, y);
|
|
172
|
+
const geojsonFeature = new GeoJSONFeature(feature, z, x, y, id);
|
|
170
173
|
geojsonFeature.layer = styleLayer;
|
|
171
174
|
const layerResult = (result[layerID] ??= []);
|
|
172
175
|
layerResult.push({ featureIndex, feature: geojsonFeature, intersectionZ });
|
|
@@ -201,9 +204,19 @@ class FeatureIndex {
|
|
|
201
204
|
}
|
|
202
205
|
return result;
|
|
203
206
|
}
|
|
204
|
-
}
|
|
205
207
|
|
|
206
|
-
|
|
208
|
+
getId(feature, sourceLayerId) {
|
|
209
|
+
let id = feature.id;
|
|
210
|
+
if (this.promoteId) {
|
|
211
|
+
const propName = typeof this.promoteId === 'string' ? this.promoteId : this.promoteId[sourceLayerId];
|
|
212
|
+
id = feature.properties[propName];
|
|
213
|
+
if (typeof id === 'boolean') {
|
|
214
|
+
id = Number(id);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return id;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
207
220
|
|
|
208
221
|
function getBounds(geometry) {
|
|
209
222
|
let minX = Number.POSITIVE_INFINITY;
|
package/src/data/segment.js
CHANGED
|
@@ -5,20 +5,27 @@ class SegmentVector {
|
|
|
5
5
|
this.segments = segments;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
prepareSegment(numVertices, layoutVertexArray, indexArray) {
|
|
8
|
+
prepareSegment(numVertices, layoutVertexArray, indexArray, sortKey) {
|
|
9
9
|
if (numVertices > SegmentVector.MAX_VERTEX_ARRAY_LENGTH) {
|
|
10
10
|
warn.once(
|
|
11
11
|
`Max vertices per segment is ${SegmentVector.MAX_VERTEX_ARRAY_LENGTH}: bucket requested ${numVertices}`
|
|
12
12
|
);
|
|
13
13
|
}
|
|
14
14
|
let segment = this.segments.at(-1);
|
|
15
|
-
if (
|
|
15
|
+
if (
|
|
16
|
+
!segment ||
|
|
17
|
+
segment.vertexLength + numVertices > SegmentVector.MAX_VERTEX_ARRAY_LENGTH ||
|
|
18
|
+
segment.sortKey !== sortKey
|
|
19
|
+
) {
|
|
16
20
|
segment = {
|
|
17
21
|
vertexOffset: layoutVertexArray.length,
|
|
18
22
|
primitiveOffset: indexArray.length,
|
|
19
23
|
vertexLength: 0,
|
|
20
24
|
primitiveLength: 0
|
|
21
25
|
};
|
|
26
|
+
if (sortKey !== undefined) {
|
|
27
|
+
segment.sortKey = sortKey;
|
|
28
|
+
}
|
|
22
29
|
this.segments.push(segment);
|
|
23
30
|
}
|
|
24
31
|
return segment;
|
|
@@ -39,11 +46,12 @@ class SegmentVector {
|
|
|
39
46
|
static simpleSegment(vertexOffset, primitiveOffset, vertexLength, primitiveLength) {
|
|
40
47
|
return new SegmentVector([
|
|
41
48
|
{
|
|
42
|
-
vertexOffset
|
|
43
|
-
primitiveOffset
|
|
44
|
-
vertexLength
|
|
45
|
-
primitiveLength
|
|
46
|
-
vaos: {}
|
|
49
|
+
vertexOffset,
|
|
50
|
+
primitiveOffset,
|
|
51
|
+
vertexLength,
|
|
52
|
+
primitiveLength,
|
|
53
|
+
vaos: {},
|
|
54
|
+
sortKey: 0
|
|
47
55
|
}
|
|
48
56
|
]);
|
|
49
57
|
}
|
package/src/index.js
CHANGED
|
@@ -5,20 +5,21 @@ import { Point } from '@mapwhit/point-geometry';
|
|
|
5
5
|
import packageJSON from '../package.json' with { type: 'json' };
|
|
6
6
|
import { default as LngLat } from './geo/lng_lat.js';
|
|
7
7
|
import { default as LngLatBounds } from './geo/lng_lat_bounds.js';
|
|
8
|
-
import {
|
|
8
|
+
import { rtlPluginLoader } from './source/rtl_text_plugin.js';
|
|
9
9
|
import { default as Style } from './style/style.js';
|
|
10
10
|
import { default as Map } from './ui/map.js';
|
|
11
11
|
import config from './util/config.js';
|
|
12
12
|
|
|
13
13
|
const { version } = packageJSON;
|
|
14
14
|
|
|
15
|
-
export { version, config, setRTLTextPlugin, Point, LngLat, LngLatBounds, Style, Map, Evented };
|
|
15
|
+
export { version, config, setRTLTextPlugin, getRTLTextPluginStatus, Point, LngLat, LngLatBounds, Style, Map, Evented };
|
|
16
16
|
|
|
17
17
|
// for commonjs backward compatibility
|
|
18
18
|
const mapwhit = {
|
|
19
19
|
version,
|
|
20
20
|
config,
|
|
21
21
|
setRTLTextPlugin,
|
|
22
|
+
getRTLTextPluginStatus,
|
|
22
23
|
Point,
|
|
23
24
|
LngLat,
|
|
24
25
|
LngLatBounds,
|
|
@@ -27,24 +28,7 @@ const mapwhit = {
|
|
|
27
28
|
Evented
|
|
28
29
|
};
|
|
29
30
|
|
|
30
|
-
const properties = {
|
|
31
|
-
workerCount: {
|
|
32
|
-
get() {
|
|
33
|
-
return config.WORKER_COUNT;
|
|
34
|
-
},
|
|
35
|
-
set(count) {
|
|
36
|
-
config.WORKER_COUNT = count;
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
workerUrl: {
|
|
40
|
-
get() {
|
|
41
|
-
return config.WORKER_URL;
|
|
42
|
-
},
|
|
43
|
-
set(url) {
|
|
44
|
-
config.WORKER_URL = url;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
};
|
|
31
|
+
const properties = {};
|
|
48
32
|
|
|
49
33
|
Object.defineProperties(mapwhit, properties);
|
|
50
34
|
Object.defineProperties(config, properties);
|
|
@@ -64,11 +48,27 @@ export default mapwhit;
|
|
|
64
48
|
*
|
|
65
49
|
* @function setRTLTextPlugin
|
|
66
50
|
* @param {string} pluginURL URL pointing to the Mapbox RTL text plugin source.
|
|
67
|
-
* @param {
|
|
51
|
+
* @param {boolean} lazy If set to `true`, mapboxgl will defer loading the plugin until rtl text is encountered,
|
|
52
|
+
* rtl text will then be rendered only after the plugin finishes loading.
|
|
53
|
+
* @example
|
|
54
|
+
* setRTLTextPlugin('https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.3.0/dist/mapbox-gl-rtl-text.js', false);
|
|
55
|
+
* @see [Add support for right-to-left scripts](https://maplibre.org/maplibre-gl-js/docs/examples/mapbox-gl-rtl-text/)
|
|
56
|
+
*/
|
|
57
|
+
function setRTLTextPlugin(pluginURL, lazy) {
|
|
58
|
+
return rtlPluginLoader.setRTLTextPlugin(pluginURL, lazy);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Gets the map's [RTL text plugin](https://www.mapbox.com/mapbox-gl-js/plugins/#mapbox-gl-rtl-text) status.
|
|
62
|
+
* The status can be `unavailable` (i.e. not requested or removed), `loading`, `loaded` or `error`.
|
|
63
|
+
* If the status is `loaded` and the plugin is requested again, an error will be thrown.
|
|
64
|
+
*
|
|
65
|
+
* @function getRTLTextPluginStatus
|
|
68
66
|
* @example
|
|
69
|
-
*
|
|
70
|
-
* @see [Add support for right-to-left scripts](https://www.mapbox.com/mapbox-gl-js/example/mapbox-gl-rtl-text/)
|
|
67
|
+
* const pluginStatus = getRTLTextPluginStatus();
|
|
71
68
|
*/
|
|
69
|
+
function getRTLTextPluginStatus() {
|
|
70
|
+
return rtlPluginLoader.getRTLTextPluginStatus();
|
|
71
|
+
}
|
|
72
72
|
|
|
73
73
|
// canary assert: used to confirm that asserts have been removed from production build
|
|
74
74
|
import assert from 'assert';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import SegmentVector from '../data/segment.js';
|
|
1
2
|
import CullFaceMode from '../gl/cull_face_mode.js';
|
|
2
3
|
import DepthMode from '../gl/depth_mode.js';
|
|
3
4
|
import StencilMode from '../gl/stencil_mode.js';
|
|
@@ -12,6 +13,7 @@ function drawCircles(painter, sourceCache, layer, coords) {
|
|
|
12
13
|
const opacity = layer._paint.get('circle-opacity');
|
|
13
14
|
const strokeWidth = layer._paint.get('circle-stroke-width');
|
|
14
15
|
const strokeOpacity = layer._paint.get('circle-stroke-opacity');
|
|
16
|
+
const sortFeaturesByKey = layer._layout.get('circle-sort-key').constantOr(1) !== undefined;
|
|
15
17
|
|
|
16
18
|
if (opacity.constantOr(1) === 0 && (strokeWidth.constantOr(1) === 0 || strokeOpacity.constantOr(1) === 0)) {
|
|
17
19
|
return;
|
|
@@ -26,6 +28,8 @@ function drawCircles(painter, sourceCache, layer, coords) {
|
|
|
26
28
|
const stencilMode = StencilMode.disabled;
|
|
27
29
|
const colorMode = painter.colorModeForRenderPass();
|
|
28
30
|
|
|
31
|
+
const segmentsRenderStates = [];
|
|
32
|
+
|
|
29
33
|
for (let i = 0; i < coords.length; i++) {
|
|
30
34
|
const coord = coords[i];
|
|
31
35
|
|
|
@@ -37,6 +41,43 @@ function drawCircles(painter, sourceCache, layer, coords) {
|
|
|
37
41
|
|
|
38
42
|
const programConfiguration = bucket.programConfigurations.get(layer.id);
|
|
39
43
|
const program = painter.useProgram('circle', programConfiguration);
|
|
44
|
+
const layoutVertexBuffer = bucket.layoutVertexBuffer;
|
|
45
|
+
const indexBuffer = bucket.indexBuffer;
|
|
46
|
+
const uniformValues = circleUniformValues(painter, coord, tile, layer);
|
|
47
|
+
|
|
48
|
+
const state = {
|
|
49
|
+
programConfiguration,
|
|
50
|
+
program,
|
|
51
|
+
layoutVertexBuffer,
|
|
52
|
+
indexBuffer,
|
|
53
|
+
uniformValues
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (sortFeaturesByKey) {
|
|
57
|
+
const oldSegments = bucket.segments.get();
|
|
58
|
+
for (const segment of oldSegments) {
|
|
59
|
+
segmentsRenderStates.push({
|
|
60
|
+
segments: new SegmentVector([segment]),
|
|
61
|
+
sortKey: segment.sortKey,
|
|
62
|
+
state
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
segmentsRenderStates.push({
|
|
67
|
+
segments: bucket.segments,
|
|
68
|
+
sortKey: 0,
|
|
69
|
+
state
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (sortFeaturesByKey) {
|
|
75
|
+
segmentsRenderStates.sort((a, b) => a.sortKey - b.sortKey);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const segmentsState of segmentsRenderStates) {
|
|
79
|
+
const { programConfiguration, program, layoutVertexBuffer, indexBuffer, uniformValues } = segmentsState.state;
|
|
80
|
+
const segments = segmentsState.segments;
|
|
40
81
|
|
|
41
82
|
program.draw(
|
|
42
83
|
context,
|
|
@@ -45,11 +86,11 @@ function drawCircles(painter, sourceCache, layer, coords) {
|
|
|
45
86
|
stencilMode,
|
|
46
87
|
colorMode,
|
|
47
88
|
CullFaceMode.disabled,
|
|
48
|
-
|
|
89
|
+
uniformValues,
|
|
49
90
|
layer.id,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
91
|
+
layoutVertexBuffer,
|
|
92
|
+
indexBuffer,
|
|
93
|
+
segments,
|
|
53
94
|
layer._paint,
|
|
54
95
|
painter.transform.zoom,
|
|
55
96
|
programConfiguration
|