@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.
Files changed (46) 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 +112 -40
  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/index.js +23 -23
  15. package/src/render/draw_circle.js +45 -4
  16. package/src/render/draw_symbol.js +190 -22
  17. package/src/render/painter.js +1 -1
  18. package/src/source/geojson_source.js +118 -21
  19. package/src/source/geojson_source_diff.js +148 -0
  20. package/src/source/geojson_tiler.js +89 -0
  21. package/src/source/rtl_text_plugin.js +133 -66
  22. package/src/source/source.js +16 -5
  23. package/src/source/source_cache.js +6 -6
  24. package/src/source/source_state.js +4 -2
  25. package/src/source/tile.js +5 -3
  26. package/src/source/vector_tile_source.js +2 -0
  27. package/src/source/worker_tile.js +6 -2
  28. package/src/style/evaluation_parameters.js +2 -3
  29. package/src/style/pauseable_placement.js +39 -7
  30. package/src/style/style.js +34 -23
  31. package/src/style/style_layer/circle_style_layer_properties.js +8 -1
  32. package/src/style/style_layer/fill_style_layer_properties.js +8 -1
  33. package/src/style/style_layer/line_style_layer_properties.js +4 -0
  34. package/src/style/style_layer/symbol_style_layer_properties.js +17 -2
  35. package/src/style-spec/reference/v8.json +161 -4
  36. package/src/symbol/one_em.js +4 -0
  37. package/src/symbol/placement.js +406 -173
  38. package/src/symbol/projection.js +3 -3
  39. package/src/symbol/quads.js +1 -6
  40. package/src/symbol/shaping.js +18 -29
  41. package/src/symbol/symbol_layout.js +243 -81
  42. package/src/symbol/transform_text.js +3 -4
  43. package/src/util/config.js +1 -9
  44. package/src/util/script_detection.js +19 -7
  45. package/src/util/vectortile_to_geojson.js +3 -4
  46. package/src/source/geojson_worker_source.js +0 -97
@@ -0,0 +1,89 @@
1
+ import rewind from '@mapwhit/geojson-rewind';
2
+ import geojsonvt from 'geojson-vt';
3
+ import Supercluster from 'supercluster';
4
+ import FeatureIndex from '../data/feature_index.js';
5
+ import GeoJSONWrapper from './geojson_wrapper.js';
6
+ import { makeSingleSourceLayerWorkerTile as makeWorkerTile } from './worker_tile.js';
7
+
8
+ /**
9
+ * Creates tiles from GeoJSON data.
10
+ */
11
+ export default function makeTiler(resources, layerIndex) {
12
+ let geoJSONIndex;
13
+ let createGeoJSONIndex;
14
+
15
+ /**
16
+ * Fetches (if appropriate), parses, and index geojson data into tiles. This
17
+ * preparatory method must be called before {@link GeoJSONWorkerSource#loadTile}
18
+ * can correctly serve up tiles.
19
+ *
20
+ * Defers to {@link GeoJSONWorkerSource#loadGeoJSON} for the fetching/parsing,
21
+ * expecting `callback(error, data)` to be called with either an error or a
22
+ * parsed GeoJSON object.
23
+ *
24
+ * @param params
25
+ */
26
+ function loadData(params) {
27
+ const { cluster, data, geojsonVtOptions, superclusterOptions } = params;
28
+ geoJSONIndex = undefined;
29
+ createGeoJSONIndex = cluster
30
+ ? () => {
31
+ rewind(data, true);
32
+ return new Supercluster(superclusterOptions).load(data.features);
33
+ }
34
+ : () => {
35
+ rewind(data, true);
36
+ return geojsonvt(data, geojsonVtOptions);
37
+ };
38
+ }
39
+
40
+ function getTile(tileID) {
41
+ if (!geoJSONIndex) {
42
+ if (!createGeoJSONIndex) {
43
+ return; // we couldn't load the file
44
+ }
45
+
46
+ try {
47
+ geoJSONIndex = createGeoJSONIndex();
48
+ } finally {
49
+ createGeoJSONIndex = undefined;
50
+ }
51
+ }
52
+ const { z, x, y } = tileID.canonical;
53
+ return geoJSONIndex.getTile(z, x, y);
54
+ }
55
+
56
+ /**
57
+ * Returns a single tile.
58
+ */
59
+ async function loadTile(params) {
60
+ const { tileID, source, promoteId } = params;
61
+ const geoJSONTile = getTile(tileID);
62
+ if (!geoJSONTile) {
63
+ return; // nothing in the given tile
64
+ }
65
+ const sourceLayer = new GeoJSONWrapper(geoJSONTile.features);
66
+ const layerFamilies = layerIndex.familiesBySource.get(source);
67
+ if (!layerFamilies) {
68
+ return;
69
+ }
70
+ params.featureIndex = new FeatureIndex(tileID, promoteId);
71
+ const { name } = sourceLayer;
72
+ const features = new Array(sourceLayer.length);
73
+ for (let index = 0; index < sourceLayer.length; index++) {
74
+ const feature = sourceLayer.feature(index);
75
+ const id = params.featureIndex.getId(feature, name);
76
+ features[index] = { feature, id, index, sourceLayerIndex: 0 };
77
+ }
78
+
79
+ const result = await makeWorkerTile(params, features, layerFamilies.get(name), resources);
80
+
81
+ result.vectorTile = sourceLayer;
82
+ return result;
83
+ }
84
+
85
+ return {
86
+ loadData,
87
+ loadTile
88
+ };
89
+ }
@@ -1,82 +1,145 @@
1
+ import { Event, Evented } from '@mapwhit/events';
1
2
  import dynload from 'dynload';
2
3
  import browser from '../util/browser.js';
3
4
 
4
- let pluginRequested = false;
5
- let pluginURL;
6
- let loading = false;
5
+ /**
6
+ * The possible option of the plugin's status
7
+ *
8
+ * `unavailable`: Not loaded.
9
+ *
10
+ * `deferred`: The plugin URL has been specified, but loading has been deferred.
11
+ *
12
+ * `loading`: request in-flight.
13
+ *
14
+ * `loaded`: The plugin is now loaded
15
+ *
16
+ * `error`: The plugin failed to load
17
+ */
7
18
 
8
- let _completionCallback;
9
- const _loadedCallbacks = [];
10
-
11
- const rtlPlugin = {
12
- clearRTLTextPlugin, // exported for testing
13
- loadScript, // exported for testing
14
- registerForPluginAvailability,
15
- setRTLTextPlugin
16
- };
19
+ function RTLPlugin() {
20
+ const self = {
21
+ isRTLSupported
22
+ };
17
23
 
18
- export function registerForPluginAvailability(callback) {
19
- if (plugin.isLoaded()) {
20
- callback();
21
- return;
24
+ /**
25
+ * Checks whether the RTL language support is available.
26
+ * If `canChangeShortly` is false, it will only return true if the RTL language
27
+ * is properly supported.
28
+ * If `canChangeShortly` is true, it will also return true if the RTL language
29
+ * is not supported unless it can obtain the RTL text plugin.
30
+ * @param {boolean} canChangeShortly
31
+ * @returns
32
+ */
33
+ function isRTLSupported(canChangeShortly) {
34
+ if (rtlPluginLoader.getRTLTextPluginStatus() === 'loaded') {
35
+ return true;
36
+ }
37
+ if (!canChangeShortly) {
38
+ return false;
39
+ }
40
+ rtlPluginLoader.lazyLoad();
41
+ // any transitive state other than 'loading' means we can consider RTL supported as best as possible for now
42
+ return rtlPluginLoader.getRTLTextPluginStatus() !== 'loading';
22
43
  }
23
- _loadedCallbacks.push(callback);
24
- loadRTLTextPlugin();
25
- return () => _loadedCallbacks.splice(_loadedCallbacks.indexOf(callback), 1);
26
- }
27
44
 
28
- export function clearRTLTextPlugin() {
29
- _loadedCallbacks.length = 0;
30
- pluginRequested = false;
31
- pluginURL = undefined;
45
+ return self;
32
46
  }
33
47
 
34
- export function setRTLTextPlugin(url, callback) {
35
- if (pluginRequested) {
36
- throw new Error('setRTLTextPlugin cannot be called multiple times.');
48
+ export const rtlPlugin = RTLPlugin();
49
+
50
+ function RTLPluginLoader() {
51
+ let status = 'unavailable';
52
+ let url;
53
+
54
+ const self = {
55
+ getRTLTextPluginStatus,
56
+ setRTLTextPlugin,
57
+ lazyLoad,
58
+ _clearRTLTextPlugin,
59
+ _registerRTLTextPlugin
60
+ };
61
+
62
+ /** This one is exposed to outside */
63
+ function getRTLTextPluginStatus() {
64
+ return status;
65
+ }
66
+
67
+ // public for testing
68
+ function _clearRTLTextPlugin() {
69
+ url = undefined;
70
+ status = 'unavailable';
71
+ _setMethods();
37
72
  }
38
- pluginRequested = true;
39
- pluginURL = browser.resolveURL(url);
40
- _completionCallback = error => {
41
- if (error) {
42
- const msg = `RTL Text Plugin failed to load scripts from ${pluginURL}`;
43
- // Clear loaded state to allow retries
44
- clearRTLTextPlugin();
45
- if (callback) {
46
- callback(new Error(msg));
73
+
74
+ function setRTLTextPlugin(pluginURL, deferred = false) {
75
+ if (url) {
76
+ // error
77
+ return Promise.reject(new Error('setRTLTextPlugin cannot be called multiple times.'));
78
+ }
79
+ url = browser.resolveURL(pluginURL);
80
+ if (!url) {
81
+ return Promise.reject(new Error(`requested url ${pluginURL} is invalid`));
82
+ }
83
+ if (status === 'requested') {
84
+ return _downloadRTLTextPlugin();
85
+ }
86
+ if (status === 'unavailable') {
87
+ // from initial state:
88
+ if (!deferred) {
89
+ return _downloadRTLTextPlugin();
47
90
  }
91
+ status = 'deferred';
48
92
  }
49
- loading = false;
50
- _completionCallback = undefined;
51
- };
52
- loadRTLTextPlugin();
53
- }
93
+ }
54
94
 
55
- function loadRTLTextPlugin() {
56
- if (pluginURL && !plugin.isLoaded() && _loadedCallbacks.length > 0 && !loading) {
57
- // needs to be called as exported method for mock testing
58
- loading = rtlPlugin.loadScript(pluginURL).catch(_completionCallback);
95
+ async function _downloadRTLTextPlugin() {
96
+ status = 'loading';
97
+ try {
98
+ await rtlPluginLoader._loadScript({ url });
99
+ } catch {
100
+ status = 'error';
101
+ }
102
+ rtlPluginLoader.fire(new Event('RTLPluginLoaded'));
59
103
  }
60
- }
61
104
 
62
- function registerRTLTextPlugin(loadedPlugin) {
63
- if (plugin.isLoaded()) {
64
- throw new Error('RTL text plugin already registered.');
105
+ /** Start a lazy loading process of RTL plugin */
106
+ function lazyLoad() {
107
+ if (status === 'unavailable') {
108
+ status = 'requested';
109
+ return;
110
+ }
111
+ if (status === 'deferred') {
112
+ return _downloadRTLTextPlugin();
113
+ }
65
114
  }
66
- plugin['applyArabicShaping'] = loadedPlugin.applyArabicShaping;
67
- plugin['processBidirectionalText'] = loadedPlugin.processBidirectionalText;
68
- plugin['processStyledBidirectionalText'] = loadedPlugin.processStyledBidirectionalText;
69
115
 
70
- if (_completionCallback) {
71
- _completionCallback();
116
+ function _setMethods(rtlTextPlugin) {
117
+ if (!rtlTextPlugin) {
118
+ // for testing only
119
+ rtlPlugin.processStyledBidirectionalText = null;
120
+ rtlPlugin.processBidirectionalText = null;
121
+ rtlPlugin.applyArabicShaping = null;
122
+ return;
123
+ }
124
+ rtlPlugin.applyArabicShaping = rtlTextPlugin.applyArabicShaping;
125
+ rtlPlugin.processBidirectionalText = rtlTextPlugin.processBidirectionalText;
126
+ rtlPlugin.processStyledBidirectionalText = rtlTextPlugin.processStyledBidirectionalText;
72
127
  }
73
- _loadedCallbacks.forEach(callback => callback());
74
- _loadedCallbacks.length = 0;
75
- }
76
128
 
77
- globalThis.registerRTLTextPlugin ??= registerRTLTextPlugin;
129
+ // This is invoked by the RTL text plugin when the download has finished, and the code has been parsed.
130
+ function _registerRTLTextPlugin(rtlTextPlugin) {
131
+ if (rtlPlugin.isRTLSupported()) {
132
+ throw new Error('RTL text plugin already registered.');
133
+ }
134
+ status = 'loaded';
135
+ _setMethods(rtlTextPlugin);
136
+ }
137
+
138
+ return self;
139
+ }
78
140
 
79
- function loadScript(url) {
141
+ // public for testing
142
+ function _loadScript({ url }) {
80
143
  const { promise, resolve, reject } = Promise.withResolvers();
81
144
  const s = dynload(url);
82
145
  s.onload = () => resolve();
@@ -84,11 +147,15 @@ function loadScript(url) {
84
147
  return promise;
85
148
  }
86
149
 
87
- export const plugin = (rtlPlugin.plugin = {
88
- applyArabicShaping: null,
89
- processBidirectionalText: null,
90
- processStyledBidirectionalText: null,
91
- isLoaded: () => plugin.applyArabicShaping != null
92
- });
150
+ const { getRTLTextPluginStatus, setRTLTextPlugin, lazyLoad, _clearRTLTextPlugin, _registerRTLTextPlugin } =
151
+ RTLPluginLoader();
152
+
153
+ globalThis.registerRTLTextPlugin ??= _registerRTLTextPlugin;
93
154
 
94
- export default rtlPlugin;
155
+ export const rtlPluginLoader = Object.assign(new Evented(), {
156
+ getRTLTextPluginStatus,
157
+ setRTLTextPlugin,
158
+ lazyLoad,
159
+ _clearRTLTextPlugin,
160
+ _loadScript
161
+ });
@@ -25,6 +25,7 @@ import { bindAll } from '../util/object.js';
25
25
  */
26
26
 
27
27
  import geojson from './geojson_source.js';
28
+ import geojsonTiler from './geojson_tiler.js';
28
29
  import image from './image_source.js';
29
30
  import rasterDem from './raster_dem_tile_source.js';
30
31
  import raster from './raster_tile_source.js';
@@ -38,6 +39,9 @@ const sourceTypes = {
38
39
  image
39
40
  };
40
41
 
42
+ const tilerTypes = {
43
+ geojson: geojsonTiler
44
+ };
41
45
  /*
42
46
  * Creates a tiled data source instance given an options object.
43
47
  *
@@ -48,11 +52,18 @@ const sourceTypes = {
48
52
  * @returns {Source}
49
53
  */
50
54
  export function create(id, specification, eventedParent, { resources, layerIndex, showTileBoundaries }) {
51
- const source = new sourceTypes[specification.type](id, specification, eventedParent, {
52
- resources,
53
- layerIndex,
54
- showTileBoundaries
55
- });
55
+ const tiler = tilerTypes[specification.type]?.(resources, layerIndex);
56
+
57
+ const source = new sourceTypes[specification.type](
58
+ id,
59
+ specification,
60
+ eventedParent,
61
+ tiler ?? {
62
+ resources,
63
+ layerIndex,
64
+ showTileBoundaries
65
+ }
66
+ );
56
67
 
57
68
  bindAll(['load', 'abort', 'unload', 'serialize', 'prepare'], source);
58
69
  return source;
@@ -799,27 +799,27 @@ class SourceCache extends Evented {
799
799
  * Set the value of a particular state for a feature
800
800
  * @private
801
801
  */
802
- setFeatureState(sourceLayer, feature, state) {
802
+ setFeatureState(sourceLayer, featureId, state) {
803
803
  sourceLayer = sourceLayer || '_geojsonTileLayer';
804
- this._state.updateState(sourceLayer, feature, state);
804
+ this._state.updateState(sourceLayer, featureId, state);
805
805
  }
806
806
 
807
807
  /**
808
808
  * Resets the value of a particular state key for a feature
809
809
  * @private
810
810
  */
811
- removeFeatureState(sourceLayer, feature, key) {
811
+ removeFeatureState(sourceLayer, featureId, key) {
812
812
  sourceLayer = sourceLayer || '_geojsonTileLayer';
813
- this._state.removeFeatureState(sourceLayer, feature, key);
813
+ this._state.removeFeatureState(sourceLayer, featureId, key);
814
814
  }
815
815
 
816
816
  /**
817
817
  * Get the entire state object for a feature
818
818
  * @private
819
819
  */
820
- getFeatureState(sourceLayer, feature) {
820
+ getFeatureState(sourceLayer, featureId) {
821
821
  sourceLayer = sourceLayer || '_geojsonTileLayer';
822
- return this._state.getState(sourceLayer, feature);
822
+ return this._state.getState(sourceLayer, featureId);
823
823
  }
824
824
  }
825
825
 
@@ -13,7 +13,8 @@ class SourceFeatureState {
13
13
  #stateChanges = {};
14
14
  #deletedStates = {};
15
15
 
16
- updateState(sourceLayer, feature, newState) {
16
+ updateState(sourceLayer, featureId, newState) {
17
+ const feature = String(featureId);
17
18
  const changes = (this.#stateChanges[sourceLayer] ??= {});
18
19
  const featureState = (changes[feature] ??= {});
19
20
  Object.assign(featureState, newState);
@@ -76,7 +77,8 @@ class SourceFeatureState {
76
77
  }
77
78
  }
78
79
 
79
- getState(sourceLayer, feature) {
80
+ getState(sourceLayer, featureId) {
81
+ const feature = String(featureId);
80
82
  const base = this.#state[sourceLayer];
81
83
  const changes = this.#stateChanges[sourceLayer];
82
84
  const reconciledState = Object.assign({}, base?.[feature], changes?.[feature]);
@@ -191,11 +191,12 @@ class Tile {
191
191
  }
192
192
 
193
193
  querySourceFeatures(result, params) {
194
- if (!this.latestFeatureIndex?.vectorTile) {
194
+ const featureIndex = this.latestFeatureIndex;
195
+ if (!featureIndex?.vectorTile) {
195
196
  return;
196
197
  }
197
198
 
198
- const vtLayers = this.latestFeatureIndex.loadVTLayers();
199
+ const vtLayers = featureIndex.loadVTLayers();
199
200
 
200
201
  const sourceLayer = params ? params.sourceLayer : '';
201
202
  const layer = vtLayers._geojsonTileLayer || vtLayers[sourceLayer];
@@ -211,7 +212,8 @@ class Tile {
211
212
  for (let i = 0; i < layer.length; i++) {
212
213
  const feature = layer.feature(i);
213
214
  if (filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) {
214
- const geojsonFeature = new GeoJSONFeature(feature, z, x, y);
215
+ const id = featureIndex.getId(feature, sourceLayer);
216
+ const geojsonFeature = new GeoJSONFeature(feature, z, x, y, id);
215
217
  geojsonFeature.tile = coord;
216
218
  result.push(geojsonFeature);
217
219
  }
@@ -22,6 +22,7 @@ class VectorTileSource extends Evented {
22
22
  this.url = options.url;
23
23
  this.scheme = options.scheme ?? 'xyz';
24
24
  this.tileSize = options.tileSize ?? 512;
25
+ this.promoteId = options.promoteId;
25
26
 
26
27
  if (this.tileSize !== 512) {
27
28
  throw new Error('vector tile sources must have a tileSize of 512');
@@ -93,6 +94,7 @@ class VectorTileSource extends Evented {
93
94
  source: this.id,
94
95
  pixelRatio: browser.devicePixelRatio,
95
96
  showCollisionBoxes: this.map.showCollisionBoxes,
97
+ promoteId: this.promoteId,
96
98
  painter: this.map.painter
97
99
  };
98
100
  tile.workerID ??= true;
@@ -27,7 +27,9 @@ async function makeWorkerTile(params, { layers }, layerIndex, resources) {
27
27
  const sourceLayerIndex = sourceLayerCoder.encode(sourceLayerId);
28
28
  const features = new Array(sourceLayer.length);
29
29
  for (let index = 0; index < sourceLayer.length; index++) {
30
- features[index] = { feature: sourceLayer.feature(index), index, sourceLayerIndex };
30
+ const feature = sourceLayer.feature(index);
31
+ const id = options.featureIndex.getId(feature, sourceLayerId);
32
+ features[index] = { feature, id, index, sourceLayerIndex };
31
33
  }
32
34
 
33
35
  makeBucketsForSourceLayer(sourceLayerFamilies, sourceLayerIndex, features, params, options);
@@ -47,7 +49,7 @@ export async function makeSingleSourceLayerWorkerTile(params, features, sourceLa
47
49
  function initializeBucketsOptions(params) {
48
50
  const tileID = createTileID(params);
49
51
 
50
- const featureIndex = new FeatureIndex(tileID);
52
+ const featureIndex = params.featureIndex ?? new FeatureIndex(tileID, params.promoteId);
51
53
  featureIndex.bucketLayerIDs = [];
52
54
 
53
55
  return {
@@ -146,6 +148,8 @@ async function finalizeBuckets(params, options, resources) {
146
148
  }
147
149
 
148
150
  async function makeAtlasses({ glyphDependencies, patternDependencies, iconDependencies }, resources) {
151
+ // options.glyphDependencies looks like: {"SomeFontName":{"10":true,"32":true}}
152
+ // this line makes an object like: {"SomeFontName":[10,32]}
149
153
  const stacks = mapObject(glyphDependencies, glyphs => Object.keys(glyphs).map(Number));
150
154
  const icons = Object.keys(iconDependencies);
151
155
  const patterns = Object.keys(patternDependencies);
@@ -1,4 +1,4 @@
1
- import { plugin as rtlTextPlugin } from '../source/rtl_text_plugin.js';
1
+ import { rtlPlugin } from '../source/rtl_text_plugin.js';
2
2
  import { isStringInSupportedScript } from '../util/script_detection.js';
3
3
  import ZoomHistory from './zoom_history.js';
4
4
 
@@ -20,7 +20,6 @@ export default class EvaluationParameters {
20
20
  this.transition = {};
21
21
  }
22
22
  }
23
-
24
23
  crossFadingFactor() {
25
24
  if (this.fadeDuration === 0) {
26
25
  return 1;
@@ -40,5 +39,5 @@ export default class EvaluationParameters {
40
39
  }
41
40
 
42
41
  function isSupportedScript(str) {
43
- return isStringInSupportedScript(str, rtlTextPlugin.isLoaded());
42
+ return isStringInSupportedScript(str, rtlPlugin.isRTLSupported());
44
43
  }
@@ -2,27 +2,59 @@ import { Placement } from '../symbol/placement.js';
2
2
  import browser from '../util/browser.js';
3
3
 
4
4
  class LayerPlacement {
5
- constructor() {
5
+ constructor(styleLayer) {
6
+ this._sortAcrossTiles =
7
+ styleLayer._layout.get('symbol-z-order') !== 'viewport-y' &&
8
+ styleLayer._layout.get('symbol-sort-key').constantOr(1) !== undefined;
9
+
6
10
  this._currentTileIndex = 0;
11
+ this._currentPartIndex = 0;
7
12
  this._seenCrossTileIDs = {};
13
+ this._bucketParts = [];
8
14
  }
9
15
 
10
16
  continuePlacement(tiles, placement, showCollisionBoxes, styleLayer, shouldPausePlacement) {
17
+ const bucketParts = this._bucketParts;
18
+
11
19
  while (this._currentTileIndex < tiles.length) {
12
20
  const tile = tiles[this._currentTileIndex];
13
- placement.placeLayerTile(styleLayer, tile, showCollisionBoxes, this._seenCrossTileIDs);
21
+ placement.getBucketParts(bucketParts, styleLayer, tile, this._sortAcrossTiles);
14
22
 
15
23
  this._currentTileIndex++;
16
24
  if (shouldPausePlacement()) {
17
25
  return true;
18
26
  }
19
27
  }
28
+
29
+ if (this._sortAcrossTiles) {
30
+ this._sortAcrossTiles = false;
31
+ bucketParts.sort((a, b) => a.sortKey - b.sortKey);
32
+ }
33
+
34
+ while (this._currentPartIndex < bucketParts.length) {
35
+ const bucketPart = bucketParts[this._currentPartIndex];
36
+ placement.placeLayerBucketPart(bucketPart, this._seenCrossTileIDs, showCollisionBoxes);
37
+
38
+ this._currentPartIndex++;
39
+ if (shouldPausePlacement()) {
40
+ return true;
41
+ }
42
+ }
43
+ return false;
20
44
  }
21
45
  }
22
46
 
23
47
  class PauseablePlacement {
24
- constructor(transform, maxIndex, forceFullPlacement, showCollisionBoxes, fadeDuration, crossSourceCollisions) {
25
- this.placement = new Placement(transform, fadeDuration, crossSourceCollisions);
48
+ constructor(
49
+ transform,
50
+ maxIndex,
51
+ forceFullPlacement,
52
+ showCollisionBoxes,
53
+ fadeDuration,
54
+ crossSourceCollisions,
55
+ prevPlacement
56
+ ) {
57
+ this.placement = new Placement(transform, fadeDuration, crossSourceCollisions, prevPlacement);
26
58
  this._currentPlacementIndex = maxIndex;
27
59
  this._forceFullPlacement = forceFullPlacement;
28
60
  this._showCollisionBoxes = showCollisionBoxes;
@@ -50,7 +82,7 @@ class PauseablePlacement {
50
82
  (!layer.maxzoom || layer.maxzoom > placementZoom)
51
83
  ) {
52
84
  if (!this._inProgressLayer) {
53
- this._inProgressLayer = new LayerPlacement();
85
+ this._inProgressLayer = new LayerPlacement(layer);
54
86
  }
55
87
 
56
88
  const pausePlacement = this._inProgressLayer.continuePlacement(
@@ -77,8 +109,8 @@ class PauseablePlacement {
77
109
  this._done = true;
78
110
  }
79
111
 
80
- commit(previousPlacement, now) {
81
- this.placement.commit(previousPlacement, now);
112
+ commit(now) {
113
+ this.placement.commit(now);
82
114
  return this.placement;
83
115
  }
84
116
  }