@mapwhit/tilerenderer 1.2.2 → 1.4.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.
Files changed (40) hide show
  1. package/build/min/package.json +1 -1
  2. package/package.json +1 -1
  3. package/src/data/array_types.js +115 -64
  4. package/src/data/bucket/circle_bucket.js +42 -5
  5. package/src/data/bucket/fill_bucket.js +31 -13
  6. package/src/data/bucket/fill_extrusion_bucket.js +8 -6
  7. package/src/data/bucket/line_bucket.js +38 -14
  8. package/src/data/bucket/symbol_attributes.js +13 -5
  9. package/src/data/bucket/symbol_bucket.js +87 -33
  10. package/src/data/bucket/symbol_collision_buffers.js +1 -1
  11. package/src/data/bucket.js +3 -1
  12. package/src/data/feature_index.js +24 -11
  13. package/src/data/segment.js +15 -7
  14. package/src/render/draw_circle.js +45 -4
  15. package/src/render/draw_symbol.js +190 -22
  16. package/src/render/painter.js +1 -1
  17. package/src/source/geojson_source.js +118 -21
  18. package/src/source/geojson_source_diff.js +148 -0
  19. package/src/source/geojson_tiler.js +89 -0
  20. package/src/source/source.js +16 -5
  21. package/src/source/source_cache.js +6 -6
  22. package/src/source/source_state.js +4 -2
  23. package/src/source/tile.js +5 -3
  24. package/src/source/vector_tile_source.js +2 -0
  25. package/src/source/worker_tile.js +4 -2
  26. package/src/style/pauseable_placement.js +39 -7
  27. package/src/style/style.js +86 -34
  28. package/src/style/style_layer/circle_style_layer_properties.js +8 -1
  29. package/src/style/style_layer/fill_style_layer_properties.js +8 -1
  30. package/src/style/style_layer/line_style_layer_properties.js +4 -0
  31. package/src/style/style_layer/symbol_style_layer_properties.js +17 -2
  32. package/src/style-spec/reference/v8.json +161 -4
  33. package/src/symbol/one_em.js +4 -0
  34. package/src/symbol/placement.js +406 -173
  35. package/src/symbol/projection.js +3 -3
  36. package/src/symbol/quads.js +1 -6
  37. package/src/symbol/shaping.js +16 -27
  38. package/src/symbol/symbol_layout.js +243 -81
  39. package/src/util/vectortile_to_geojson.js +3 -4
  40. 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([{ name: 'a_placed', components: 2, type: 'Uint8' }], 4);
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: 'horizontalPlacedTextSymbolIndex' },
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: 'numGlyphVertices' },
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' }]);
@@ -86,6 +86,7 @@ export default class SymbolBucket {
86
86
  this.pixelRatio = options.pixelRatio;
87
87
  this.sourceLayerIndex = options.sourceLayerIndex;
88
88
  this.hasPattern = false;
89
+ this.sortKeyRanges = [];
89
90
 
90
91
  const layer = this.layers[0];
91
92
  const unevaluatedLayoutValues = layer._unevaluatedLayout._values;
@@ -94,7 +95,10 @@ export default class SymbolBucket {
94
95
  this.iconSizeData = getSizeData(this.zoom, unevaluatedLayoutValues['icon-size']);
95
96
 
96
97
  const layout = this.layers[0]._layout;
97
- const zOrderByViewportY = layout.get('symbol-z-order') === 'viewport-y';
98
+ const sortKey = layout.get('symbol-sort-key');
99
+ const zOrder = layout.get('symbol-z-order');
100
+ this.sortFeaturesByKey = zOrder !== 'viewport-y' && sortKey.constantOr(1) !== undefined;
101
+ const zOrderByViewportY = zOrder === 'viewport-y' || (zOrder === 'auto' && !this.sortFeaturesByKey);
98
102
  this.sortFeaturesByY =
99
103
  zOrderByViewportY &&
100
104
  (layout.get('text-allow-overlap') ||
@@ -102,6 +106,8 @@ export default class SymbolBucket {
102
106
  layout.get('text-ignore-placement') ||
103
107
  layout.get('icon-ignore-placement'));
104
108
 
109
+ this.stateDependentLayerIds = this.layers.filter(l => l.isStateDependent()).map(l => l.id);
110
+
105
111
  this.sourceID = options.sourceID;
106
112
  }
107
113
 
@@ -140,6 +146,7 @@ export default class SymbolBucket {
140
146
  (textField.value.kind !== 'constant' || textField.value.value.toString().length > 0) &&
141
147
  (textFont.value.kind !== 'constant' || textFont.value.value.length > 0);
142
148
  const hasIcon = iconImage.value.kind !== 'constant' || iconImage.value.value?.length > 0;
149
+ const symbolSortKey = layout.get('symbol-sort-key');
143
150
 
144
151
  this.features = [];
145
152
 
@@ -151,7 +158,7 @@ export default class SymbolBucket {
151
158
  const stacks = options.glyphDependencies;
152
159
  const globalProperties = new EvaluationParameters(this.zoom);
153
160
 
154
- for (const { feature, index, sourceLayerIndex } of features) {
161
+ for (const { feature, id, index, sourceLayerIndex } of features) {
155
162
  if (!layer._featureFilter(globalProperties, feature)) {
156
163
  continue;
157
164
  }
@@ -178,18 +185,19 @@ export default class SymbolBucket {
178
185
  continue;
179
186
  }
180
187
 
188
+ const sortKey = this.sortFeaturesByKey ? symbolSortKey.evaluate(feature, {}) : undefined;
189
+
181
190
  const symbolFeature = {
191
+ id,
182
192
  text,
183
193
  icon,
184
194
  index,
185
195
  sourceLayerIndex,
186
196
  geometry: loadGeometry(feature),
187
197
  properties: feature.properties,
188
- type: VectorTileFeature.types[feature.type]
198
+ type: VectorTileFeature.types[feature.type],
199
+ sortKey
189
200
  };
190
- if (typeof feature.id !== 'undefined') {
191
- symbolFeature.id = feature.id;
192
- }
193
201
  this.features.push(symbolFeature);
194
202
 
195
203
  if (icon) {
@@ -214,6 +222,13 @@ export default class SymbolBucket {
214
222
  // It's better to place labels on one long line than on many short segments.
215
223
  this.features = mergeLines(this.features);
216
224
  }
225
+
226
+ if (this.sortFeaturesByKey) {
227
+ this.features.sort((a, b) => {
228
+ // a.sortKey is always a number when sortFeaturesByKey is true
229
+ return a.sortKey - b.sortKey;
230
+ });
231
+ }
217
232
  }
218
233
 
219
234
  update(states, vtLayer, imagePositions) {
@@ -295,7 +310,7 @@ export default class SymbolBucket {
295
310
  lineLength
296
311
  ) {
297
312
  const { indexArray, layoutVertexArray, dynamicLayoutVertexArray, segments } = arrays;
298
- const segment = segments.prepareSegment(4 * quads.length, layoutVertexArray, indexArray);
313
+ const segment = segments.prepareSegment(4 * quads.length, layoutVertexArray, indexArray, feature.sortKey);
299
314
  const glyphOffsetArrayStart = this.glyphOffsetArray.length;
300
315
  const vertexStartIndex = segment.vertexLength;
301
316
  const { x: lax, y: lay } = labelAnchor;
@@ -335,7 +350,9 @@ export default class SymbolBucket {
335
350
  lineOffset[0],
336
351
  lineOffset[1],
337
352
  writingMode,
338
- false
353
+ false,
354
+ // The crossTileID is only filled/used on the foreground for dynamic text anchors
355
+ 0
339
356
  );
340
357
 
341
358
  arrays.programConfigurations.populatePaintArrays(arrays.layoutVertexArray.length, feature, feature.index, {
@@ -439,6 +456,43 @@ export default class SymbolBucket {
439
456
  }
440
457
  }
441
458
 
459
+ getSortedSymbolIndexes(angle) {
460
+ if (this.sortedAngle === angle && this.symbolInstanceIndexes !== undefined) {
461
+ return this.symbolInstanceIndexes;
462
+ }
463
+ const sin = Math.sin(angle);
464
+ const cos = Math.cos(angle);
465
+ const rotatedYs = [];
466
+ const featureIndexes = [];
467
+ const result = [];
468
+
469
+ for (let i = 0; i < this.symbolInstances.length; ++i) {
470
+ result.push(i);
471
+ const symbolInstance = this.symbolInstances.get(i);
472
+ rotatedYs.push(Math.round(sin * symbolInstance.anchorX + cos * symbolInstance.anchorY) | 0);
473
+ featureIndexes.push(symbolInstance.featureIndex);
474
+ }
475
+
476
+ result.sort((aIndex, bIndex) => {
477
+ return rotatedYs[aIndex] - rotatedYs[bIndex] || featureIndexes[bIndex] - featureIndexes[aIndex];
478
+ });
479
+
480
+ return result;
481
+ }
482
+
483
+ addToSortKeyRanges(symbolInstanceIndex, sortKey) {
484
+ const last = this.sortKeyRanges[this.sortKeyRanges.length - 1];
485
+ if (last && last.sortKey === sortKey) {
486
+ last.symbolInstanceEnd = symbolInstanceIndex + 1;
487
+ } else {
488
+ this.sortKeyRanges.push({
489
+ sortKey,
490
+ symbolInstanceStart: symbolInstanceIndex,
491
+ symbolInstanceEnd: symbolInstanceIndex + 1
492
+ });
493
+ }
494
+ }
495
+
442
496
  sortFeatures(angle) {
443
497
  if (!this.sortFeaturesByY) {
444
498
  return;
@@ -447,7 +501,6 @@ export default class SymbolBucket {
447
501
  if (this.sortedAngle === angle) {
448
502
  return;
449
503
  }
450
- this.sortedAngle = angle;
451
504
 
452
505
  // The current approach to sorting doesn't sort across segments so don't try.
453
506
  // Sorting within segments separately seemed not to be worth the complexity.
@@ -460,35 +513,36 @@ export default class SymbolBucket {
460
513
  // sorted order.
461
514
 
462
515
  // To avoid sorting the actual symbolInstance array we sort an array of indexes.
463
- const slen = this.symbolInstances.length;
464
- const symbolInstanceIndexes = new Array(slen);
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]);
516
+ this.symbolInstanceIndexes = this.getSortedSymbolIndexes(angle);
517
+ this.sortedAngle = angle;
477
518
 
478
519
  this.text.indexArray.clear();
479
520
  this.icon.indexArray.clear();
480
521
 
481
- this.featureSortOrder = new Array(slen);
482
-
483
- for (let i = 0; i < slen; i++) {
484
- const index = symbolInstanceIndexes[i];
485
- const { featureIndex, horizontalPlacedTextSymbolIndex, verticalPlacedTextSymbolIndex } =
486
- this.symbolInstances.get(index);
487
- this.featureSortOrder[i] = featureIndex;
522
+ this.featureSortOrder = new Array(this.symbolInstanceIndexes.length);
523
+
524
+ for (let i = 0; i < this.symbolInstanceIndexes.length; i++) {
525
+ const index = this.symbolInstanceIndexes[i];
526
+ const {
527
+ centerJustifiedTextSymbolIndex,
528
+ featureIndex,
529
+ leftJustifiedTextSymbolIndex,
530
+ rightJustifiedTextSymbolIndex,
531
+ verticalPlacedTextSymbolIndex
532
+ } = this.symbolInstances.get(index);
533
+ this.featureSortOrder.push(featureIndex);
534
+
535
+ [rightJustifiedTextSymbolIndex, centerJustifiedTextSymbolIndex, leftJustifiedTextSymbolIndex].forEach(
536
+ (index, i, array) => {
537
+ // Only add a given index the first time it shows up,
538
+ // to avoid duplicate opacity entries when multiple justifications
539
+ // share the same glyphs.
540
+ if (index >= 0 && array.indexOf(index) === i) {
541
+ this.addIndicesForPlacedTextSymbol(index);
542
+ }
543
+ }
544
+ );
488
545
 
489
- if (horizontalPlacedTextSymbolIndex >= 0) {
490
- this.addIndicesForPlacedTextSymbol(horizontalPlacedTextSymbolIndex);
491
- }
492
546
  if (verticalPlacedTextSymbolIndex >= 0) {
493
547
  this.addIndicesForPlacedTextSymbol(verticalPlacedTextSymbolIndex);
494
548
  }
@@ -1,4 +1,4 @@
1
- import { StructArrayLayout2ub4 as CollisionVertexArray } from '../array_types.js';
1
+ import { CollisionVertexArray } from '../array_types.js';
2
2
  import SegmentVector from '../segment.js';
3
3
  import { collisionVertexAttributes } from './symbol_attributes.js';
4
4
 
@@ -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
- bucket.stateDependentLayers = layers.filter(layer => layer.isStateDependent());
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, grid = new Grid(EXTENT, 16, 0), featureIndexArray = new FeatureIndexArray()) {
12
+ export default class FeatureIndex {
13
+ constructor(tileID, promoteId) {
14
14
  this.tileID = tileID;
15
- this.grid = grid;
15
+ this.grid = new Grid(EXTENT, 16, 0);
16
16
  this.grid3D = new Grid(EXTENT, 16, 0);
17
- this.featureIndexArray = 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 (feature.id) {
100
+ if (id !== undefined) {
100
101
  // `feature-state` expression evaluation requires feature state to be available
101
- featureState = sourceFeatureState.getState(styleLayer.sourceLayer || '_geojsonTileLayer', String(feature.id));
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
- export default FeatureIndex;
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;
@@ -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 (!segment || segment.vertexLength + numVertices > SegmentVector.MAX_VERTEX_ARRAY_LENGTH) {
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: vertexOffset,
43
- primitiveOffset: primitiveOffset,
44
- vertexLength: vertexLength,
45
- primitiveLength: primitiveLength,
46
- vaos: {}
49
+ vertexOffset,
50
+ primitiveOffset,
51
+ vertexLength,
52
+ primitiveLength,
53
+ vaos: {},
54
+ sortKey: 0
47
55
  }
48
56
  ]);
49
57
  }
@@ -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
- circleUniformValues(painter, coord, tile, layer),
89
+ uniformValues,
49
90
  layer.id,
50
- bucket.layoutVertexBuffer,
51
- bucket.indexBuffer,
52
- bucket.segments,
91
+ layoutVertexBuffer,
92
+ indexBuffer,
93
+ segments,
53
94
  layer._paint,
54
95
  painter.transform.zoom,
55
96
  programConfiguration