1 | import assert from 'assert';
|
2 |
|
3 | import {Event, ErrorEvent, Evented} from '../util/evented';
|
4 | import StyleLayer from './style_layer';
|
5 | import createStyleLayer from './create_style_layer';
|
6 | import loadSprite from './load_sprite';
|
7 | import ImageManager from '../render/image_manager';
|
8 | import GlyphManager from '../render/glyph_manager';
|
9 | import Light from './light';
|
10 | import LineAtlas from '../render/line_atlas';
|
11 | import {pick, clone, extend, deepEqual, filterObject, mapObject} from '../util/util';
|
12 | import {getJSON, getReferrer, makeRequest, ResourceType} from '../util/ajax';
|
13 | import browser from '../util/browser';
|
14 | import Dispatcher from '../util/dispatcher';
|
15 | import {validateStyle, emitValidationErrors as _emitValidationErrors} from './validate_style';
|
16 | import {getSourceType, setSourceType, Source} from '../source/source';
|
17 | import type {SourceClass} from '../source/source';
|
18 | import {queryRenderedFeatures, queryRenderedSymbols, querySourceFeatures} from '../source/query_features';
|
19 | import SourceCache from '../source/source_cache';
|
20 | import GeoJSONSource from '../source/geojson_source';
|
21 | import styleSpec from '../style-spec/reference/latest';
|
22 | import getWorkerPool from '../util/global_worker_pool';
|
23 | import deref from '../style-spec/deref';
|
24 | import emptyStyle from '../style-spec/empty';
|
25 | import diffStyles, {operations as diffOperations} from '../style-spec/diff';
|
26 | import {
|
27 | registerForPluginStateChange,
|
28 | evented as rtlTextPluginEvented,
|
29 | triggerPluginCompletionEvent
|
30 | } from '../source/rtl_text_plugin';
|
31 | import PauseablePlacement from './pauseable_placement';
|
32 | import ZoomHistory from './zoom_history';
|
33 | import CrossTileSymbolIndex from '../symbol/cross_tile_symbol_index';
|
34 | import {validateCustomStyleLayer} from './style_layer/custom_style_layer';
|
35 | import type {MapGeoJSONFeature} from '../util/vectortile_to_geojson';
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | const emitValidationErrors = (evented: Evented, errors?: ReadonlyArray<{
|
41 | message: string;
|
42 | identifier?: string;
|
43 | }> | null) =>
|
44 | _emitValidationErrors(evented, errors && errors.filter(error => error.identifier !== 'source.canvas'));
|
45 |
|
46 | import type Map from '../ui/map';
|
47 | import type Transform from '../geo/transform';
|
48 | import type {StyleImage} from './style_image';
|
49 | import type {StyleGlyph} from './style_glyph';
|
50 | import type {Callback} from '../types/callback';
|
51 | import type EvaluationParameters from './evaluation_parameters';
|
52 | import type {Placement} from '../symbol/placement';
|
53 | import type {Cancelable} from '../types/cancelable';
|
54 | import type {RequestParameters, ResponseCallback} from '../util/ajax';
|
55 | import type {
|
56 | LayerSpecification,
|
57 | FilterSpecification,
|
58 | StyleSpecification,
|
59 | LightSpecification,
|
60 | SourceSpecification
|
61 | } from '../style-spec/types.g';
|
62 | import type {CustomLayerInterface} from './style_layer/custom_style_layer';
|
63 | import type {Validator} from './validate_style';
|
64 | import type {OverscaledTileID} from '../source/tile_id';
|
65 |
|
66 | const supportedDiffOperations = pick(diffOperations, [
|
67 | 'addLayer',
|
68 | 'removeLayer',
|
69 | 'setPaintProperty',
|
70 | 'setLayoutProperty',
|
71 | 'setFilter',
|
72 | 'addSource',
|
73 | 'removeSource',
|
74 | 'setLayerZoomRange',
|
75 | 'setLight',
|
76 | 'setTransition',
|
77 | 'setGeoJSONSourceData'
|
78 |
|
79 |
|
80 | ]);
|
81 |
|
82 | const ignoredDiffOperations = pick(diffOperations, [
|
83 | 'setCenter',
|
84 | 'setZoom',
|
85 | 'setBearing',
|
86 | 'setPitch'
|
87 | ]);
|
88 |
|
89 | const empty = emptyStyle() as StyleSpecification;
|
90 |
|
91 | export type FeatureIdentifier = {
|
92 | id?: string | number | undefined;
|
93 | source: string;
|
94 | sourceLayer?: string | undefined;
|
95 | };
|
96 |
|
97 | export type StyleOptions = {
|
98 | validate?: boolean;
|
99 | localIdeographFontFamily?: string;
|
100 | };
|
101 |
|
102 | export type StyleSetterOptions = {
|
103 | validate?: boolean;
|
104 | };
|
105 |
|
106 |
|
107 |
|
108 | class Style extends Evented {
|
109 | map: Map;
|
110 | stylesheet: StyleSpecification;
|
111 | dispatcher: Dispatcher;
|
112 | imageManager: ImageManager;
|
113 | glyphManager: GlyphManager;
|
114 | lineAtlas: LineAtlas;
|
115 | light: Light;
|
116 |
|
117 | _request: Cancelable;
|
118 | _spriteRequest: Cancelable;
|
119 | _layers: {[_: string]: StyleLayer};
|
120 | _serializedLayers: {[_: string]: any};
|
121 | _order: Array<string>;
|
122 | sourceCaches: {[_: string]: SourceCache};
|
123 | zoomHistory: ZoomHistory;
|
124 | _loaded: boolean;
|
125 | _rtlTextPluginCallback: (a: any) => any;
|
126 | _changed: boolean;
|
127 | _updatedSources: {[_: string]: 'clear' | 'reload'};
|
128 | _updatedLayers: {[_: string]: true};
|
129 | _removedLayers: {[_: string]: StyleLayer};
|
130 | _changedImages: {[_: string]: true};
|
131 | _updatedPaintProps: {[layer: string]: true};
|
132 | _layerOrderChanged: boolean;
|
133 | _availableImages: Array<string>;
|
134 |
|
135 | crossTileSymbolIndex: CrossTileSymbolIndex;
|
136 | pauseablePlacement: PauseablePlacement;
|
137 | placement: Placement;
|
138 | z: number;
|
139 |
|
140 |
|
141 | static getSourceType: typeof getSourceType;
|
142 | static setSourceType: typeof setSourceType;
|
143 | static registerForPluginStateChange: typeof registerForPluginStateChange;
|
144 |
|
145 | constructor(map: Map, options: StyleOptions = {}) {
|
146 | super();
|
147 |
|
148 | this.map = map;
|
149 | this.dispatcher = new Dispatcher(getWorkerPool(), this);
|
150 | this.imageManager = new ImageManager();
|
151 | this.imageManager.setEventedParent(this);
|
152 | this.glyphManager = new GlyphManager(map._requestManager, options.localIdeographFontFamily);
|
153 | this.lineAtlas = new LineAtlas(256, 512);
|
154 | this.crossTileSymbolIndex = new CrossTileSymbolIndex();
|
155 |
|
156 | this._layers = {};
|
157 | this._serializedLayers = {};
|
158 | this._order = [];
|
159 | this.sourceCaches = {};
|
160 | this.zoomHistory = new ZoomHistory();
|
161 | this._loaded = false;
|
162 | this._availableImages = [];
|
163 |
|
164 | this._resetUpdates();
|
165 |
|
166 | this.dispatcher.broadcast('setReferrer', getReferrer());
|
167 |
|
168 | const self = this;
|
169 | this._rtlTextPluginCallback = Style.registerForPluginStateChange((event) => {
|
170 | const state = {
|
171 | pluginStatus: event.pluginStatus,
|
172 | pluginURL: event.pluginURL
|
173 | };
|
174 | self.dispatcher.broadcast('syncRTLPluginState', state, (err, results) => {
|
175 | triggerPluginCompletionEvent(err);
|
176 | if (results) {
|
177 | const allComplete = results.every((elem) => elem);
|
178 | if (allComplete) {
|
179 | for (const id in self.sourceCaches) {
|
180 | self.sourceCaches[id].reload();
|
181 | }
|
182 | }
|
183 | }
|
184 |
|
185 | });
|
186 | });
|
187 |
|
188 | this.on('data', (event) => {
|
189 | if (event.dataType !== 'source' || event.sourceDataType !== 'metadata') {
|
190 | return;
|
191 | }
|
192 |
|
193 | const sourceCache = this.sourceCaches[event.sourceId];
|
194 | if (!sourceCache) {
|
195 | return;
|
196 | }
|
197 |
|
198 | const source = sourceCache.getSource();
|
199 | if (!source || !source.vectorLayerIds) {
|
200 | return;
|
201 | }
|
202 |
|
203 | for (const layerId in this._layers) {
|
204 | const layer = this._layers[layerId];
|
205 | if (layer.source === source.id) {
|
206 | this._validateLayer(layer);
|
207 | }
|
208 | }
|
209 | });
|
210 | }
|
211 |
|
212 | loadURL(url: string, options: {
|
213 | validate?: boolean;
|
214 | } = {}) {
|
215 | this.fire(new Event('dataloading', {dataType: 'style'}));
|
216 |
|
217 | const validate = typeof options.validate === 'boolean' ?
|
218 | options.validate : true;
|
219 |
|
220 | const request = this.map._requestManager.transformRequest(url, ResourceType.Style);
|
221 | this._request = getJSON(request, (error?: Error | null, json?: any | null) => {
|
222 | this._request = null;
|
223 | if (error) {
|
224 | this.fire(new ErrorEvent(error));
|
225 | } else if (json) {
|
226 | this._load(json, validate);
|
227 | }
|
228 | });
|
229 | }
|
230 |
|
231 | loadJSON(json: StyleSpecification, options: StyleSetterOptions = {}) {
|
232 | this.fire(new Event('dataloading', {dataType: 'style'}));
|
233 |
|
234 | this._request = browser.frame(() => {
|
235 | this._request = null;
|
236 | this._load(json, options.validate !== false);
|
237 | });
|
238 | }
|
239 |
|
240 | loadEmpty() {
|
241 | this.fire(new Event('dataloading', {dataType: 'style'}));
|
242 | this._load(empty, false);
|
243 | }
|
244 |
|
245 | _load(json: StyleSpecification, validate: boolean) {
|
246 | if (validate && emitValidationErrors(this, validateStyle(json))) {
|
247 | return;
|
248 | }
|
249 |
|
250 | this._loaded = true;
|
251 | this.stylesheet = json;
|
252 |
|
253 | for (const id in json.sources) {
|
254 | this.addSource(id, json.sources[id], {validate: false});
|
255 | }
|
256 |
|
257 | if (json.sprite) {
|
258 | this._loadSprite(json.sprite);
|
259 | } else {
|
260 | this.imageManager.setLoaded(true);
|
261 | }
|
262 |
|
263 | this.glyphManager.setURL(json.glyphs);
|
264 |
|
265 | const layers = deref(this.stylesheet.layers);
|
266 |
|
267 | this._order = layers.map((layer) => layer.id);
|
268 |
|
269 | this._layers = {};
|
270 | this._serializedLayers = {};
|
271 | for (let layer of layers) {
|
272 | layer = createStyleLayer(layer);
|
273 | layer.setEventedParent(this, {layer: {id: layer.id}});
|
274 | this._layers[layer.id] = layer;
|
275 | this._serializedLayers[layer.id] = layer.serialize();
|
276 | }
|
277 | this.dispatcher.broadcast('setLayers', this._serializeLayers(this._order));
|
278 |
|
279 | this.light = new Light(this.stylesheet.light);
|
280 |
|
281 | this.fire(new Event('data', {dataType: 'style'}));
|
282 | this.fire(new Event('style.load'));
|
283 | }
|
284 |
|
285 | _loadSprite(url: string) {
|
286 | this._spriteRequest = loadSprite(url, this.map._requestManager, this.map.getPixelRatio(), (err, images) => {
|
287 | this._spriteRequest = null;
|
288 | if (err) {
|
289 | this.fire(new ErrorEvent(err));
|
290 | } else if (images) {
|
291 | for (const id in images) {
|
292 | this.imageManager.addImage(id, images[id]);
|
293 | }
|
294 | }
|
295 |
|
296 | this.imageManager.setLoaded(true);
|
297 | this._availableImages = this.imageManager.listImages();
|
298 | this.dispatcher.broadcast('setImages', this._availableImages);
|
299 | this.fire(new Event('data', {dataType: 'style'}));
|
300 | });
|
301 | }
|
302 |
|
303 | _validateLayer(layer: StyleLayer) {
|
304 | const sourceCache = this.sourceCaches[layer.source];
|
305 | if (!sourceCache) {
|
306 | return;
|
307 | }
|
308 |
|
309 | const sourceLayer = layer.sourceLayer;
|
310 | if (!sourceLayer) {
|
311 | return;
|
312 | }
|
313 |
|
314 | const source = sourceCache.getSource();
|
315 | if (source.type === 'geojson' || (source.vectorLayerIds && source.vectorLayerIds.indexOf(sourceLayer) === -1)) {
|
316 | this.fire(new ErrorEvent(new Error(
|
317 | `Source layer "${sourceLayer}" ` +
|
318 | `does not exist on source "${source.id}" ` +
|
319 | `as specified by style layer "${layer.id}".`
|
320 | )));
|
321 | }
|
322 | }
|
323 |
|
324 | loaded() {
|
325 | if (!this._loaded)
|
326 | return false;
|
327 |
|
328 | if (Object.keys(this._updatedSources).length)
|
329 | return false;
|
330 |
|
331 | for (const id in this.sourceCaches)
|
332 | if (!this.sourceCaches[id].loaded())
|
333 | return false;
|
334 |
|
335 | if (!this.imageManager.isLoaded())
|
336 | return false;
|
337 |
|
338 | return true;
|
339 | }
|
340 |
|
341 | _serializeLayers(ids: Array<string>): Array<LayerSpecification> {
|
342 | const serializedLayers = [];
|
343 | for (const id of ids) {
|
344 | const layer = this._layers[id];
|
345 | if (layer.type !== 'custom') {
|
346 | serializedLayers.push(layer.serialize());
|
347 | }
|
348 | }
|
349 | return serializedLayers;
|
350 | }
|
351 |
|
352 | hasTransitions() {
|
353 | if (this.light && this.light.hasTransition()) {
|
354 | return true;
|
355 | }
|
356 |
|
357 | for (const id in this.sourceCaches) {
|
358 | if (this.sourceCaches[id].hasTransition()) {
|
359 | return true;
|
360 | }
|
361 | }
|
362 |
|
363 | for (const id in this._layers) {
|
364 | if (this._layers[id].hasTransition()) {
|
365 | return true;
|
366 | }
|
367 | }
|
368 |
|
369 | return false;
|
370 | }
|
371 |
|
372 | _checkLoaded() {
|
373 | if (!this._loaded) {
|
374 | throw new Error('Style is not done loading.');
|
375 | }
|
376 | }
|
377 |
|
378 | |
379 |
|
380 |
|
381 |
|
382 | update(parameters: EvaluationParameters) {
|
383 | if (!this._loaded) {
|
384 | return;
|
385 | }
|
386 |
|
387 | const changed = this._changed;
|
388 | if (this._changed) {
|
389 | const updatedIds = Object.keys(this._updatedLayers);
|
390 | const removedIds = Object.keys(this._removedLayers);
|
391 |
|
392 | if (updatedIds.length || removedIds.length) {
|
393 | this._updateWorkerLayers(updatedIds, removedIds);
|
394 | }
|
395 | for (const id in this._updatedSources) {
|
396 | const action = this._updatedSources[id];
|
397 | assert(action === 'reload' || action === 'clear');
|
398 | if (action === 'reload') {
|
399 | this._reloadSource(id);
|
400 | } else if (action === 'clear') {
|
401 | this._clearSource(id);
|
402 | }
|
403 | }
|
404 |
|
405 | this._updateTilesForChangedImages();
|
406 |
|
407 | for (const id in this._updatedPaintProps) {
|
408 | this._layers[id].updateTransitions(parameters);
|
409 | }
|
410 |
|
411 | this.light.updateTransitions(parameters);
|
412 |
|
413 | this._resetUpdates();
|
414 | }
|
415 |
|
416 | const sourcesUsedBefore = {};
|
417 |
|
418 | for (const sourceId in this.sourceCaches) {
|
419 | const sourceCache = this.sourceCaches[sourceId];
|
420 | sourcesUsedBefore[sourceId] = sourceCache.used;
|
421 | sourceCache.used = false;
|
422 | }
|
423 |
|
424 | for (const layerId of this._order) {
|
425 | const layer = this._layers[layerId];
|
426 |
|
427 | layer.recalculate(parameters, this._availableImages);
|
428 | if (!layer.isHidden(parameters.zoom) && layer.source) {
|
429 | this.sourceCaches[layer.source].used = true;
|
430 | }
|
431 | }
|
432 |
|
433 | for (const sourceId in sourcesUsedBefore) {
|
434 | const sourceCache = this.sourceCaches[sourceId];
|
435 | if (sourcesUsedBefore[sourceId] !== sourceCache.used) {
|
436 | sourceCache.fire(new Event('data', {sourceDataType: 'visibility', dataType:'source', sourceId}));
|
437 | }
|
438 | }
|
439 |
|
440 | this.light.recalculate(parameters);
|
441 | this.z = parameters.zoom;
|
442 |
|
443 | if (changed) {
|
444 | this.fire(new Event('data', {dataType: 'style'}));
|
445 | }
|
446 |
|
447 | }
|
448 |
|
449 | |
450 |
|
451 |
|
452 | _updateTilesForChangedImages() {
|
453 | const changedImages = Object.keys(this._changedImages);
|
454 | if (changedImages.length) {
|
455 | for (const name in this.sourceCaches) {
|
456 | this.sourceCaches[name].reloadTilesForDependencies(['icons', 'patterns'], changedImages);
|
457 | }
|
458 | this._changedImages = {};
|
459 | }
|
460 | }
|
461 |
|
462 | _updateWorkerLayers(updatedIds: Array<string>, removedIds: Array<string>) {
|
463 | this.dispatcher.broadcast('updateLayers', {
|
464 | layers: this._serializeLayers(updatedIds),
|
465 | removedIds
|
466 | });
|
467 | }
|
468 |
|
469 | _resetUpdates() {
|
470 | this._changed = false;
|
471 |
|
472 | this._updatedLayers = {};
|
473 | this._removedLayers = {};
|
474 |
|
475 | this._updatedSources = {};
|
476 | this._updatedPaintProps = {};
|
477 |
|
478 | this._changedImages = {};
|
479 | }
|
480 |
|
481 | |
482 |
|
483 |
|
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
489 |
|
490 |
|
491 | setState(nextState: StyleSpecification) {
|
492 | this._checkLoaded();
|
493 |
|
494 | if (emitValidationErrors(this, validateStyle(nextState))) return false;
|
495 |
|
496 | nextState = clone(nextState);
|
497 | nextState.layers = deref(nextState.layers);
|
498 |
|
499 | const changes = diffStyles(this.serialize(), nextState)
|
500 | .filter(op => !(op.command in ignoredDiffOperations));
|
501 |
|
502 | if (changes.length === 0) {
|
503 | return false;
|
504 | }
|
505 |
|
506 | const unimplementedOps = changes.filter(op => !(op.command in supportedDiffOperations));
|
507 | if (unimplementedOps.length > 0) {
|
508 | throw new Error(`Unimplemented: ${unimplementedOps.map(op => op.command).join(', ')}.`);
|
509 | }
|
510 |
|
511 | changes.forEach((op) => {
|
512 | if (op.command === 'setTransition') {
|
513 |
|
514 |
|
515 | return;
|
516 | }
|
517 | (this as any)[op.command].apply(this, op.args);
|
518 | });
|
519 |
|
520 | this.stylesheet = nextState;
|
521 |
|
522 | return true;
|
523 | }
|
524 |
|
525 | addImage(id: string, image: StyleImage) {
|
526 | if (this.getImage(id)) {
|
527 | return this.fire(new ErrorEvent(new Error(`An image named "${id}" already exists.`)));
|
528 | }
|
529 | this.imageManager.addImage(id, image);
|
530 | this._afterImageUpdated(id);
|
531 | }
|
532 |
|
533 | updateImage(id: string, image: StyleImage) {
|
534 | this.imageManager.updateImage(id, image);
|
535 | }
|
536 |
|
537 | getImage(id: string): StyleImage {
|
538 | return this.imageManager.getImage(id);
|
539 | }
|
540 |
|
541 | removeImage(id: string) {
|
542 | if (!this.getImage(id)) {
|
543 | return this.fire(new ErrorEvent(new Error(`An image named "${id}" does not exist.`)));
|
544 | }
|
545 | this.imageManager.removeImage(id);
|
546 | this._afterImageUpdated(id);
|
547 | }
|
548 |
|
549 | _afterImageUpdated(id: string) {
|
550 | this._availableImages = this.imageManager.listImages();
|
551 | this._changedImages[id] = true;
|
552 | this._changed = true;
|
553 | this.dispatcher.broadcast('setImages', this._availableImages);
|
554 | this.fire(new Event('data', {dataType: 'style'}));
|
555 | }
|
556 |
|
557 | listImages() {
|
558 | this._checkLoaded();
|
559 |
|
560 | return this.imageManager.listImages();
|
561 | }
|
562 |
|
563 | addSource(id: string, source: SourceSpecification, options: StyleSetterOptions = {}) {
|
564 | this._checkLoaded();
|
565 |
|
566 | if (this.sourceCaches[id] !== undefined) {
|
567 | throw new Error(`Source "${id}" already exists.`);
|
568 | }
|
569 |
|
570 | if (!source.type) {
|
571 | throw new Error(`The type property must be defined, but only the following properties were given: ${Object.keys(source).join(', ')}.`);
|
572 | }
|
573 |
|
574 | const builtIns = ['vector', 'raster', 'geojson', 'video', 'image'];
|
575 | const shouldValidate = builtIns.indexOf(source.type) >= 0;
|
576 | if (shouldValidate && this._validate(validateStyle.source, `sources.${id}`, source, null, options)) return;
|
577 |
|
578 | if (this.map && this.map._collectResourceTiming) (source as any).collectResourceTiming = true;
|
579 | const sourceCache = this.sourceCaches[id] = new SourceCache(id, source, this.dispatcher);
|
580 | sourceCache.style = this;
|
581 | sourceCache.setEventedParent(this, () => ({
|
582 | isSourceLoaded: this.loaded(),
|
583 | source: sourceCache.serialize(),
|
584 | sourceId: id
|
585 | }));
|
586 |
|
587 | sourceCache.onAdd(this.map);
|
588 | this._changed = true;
|
589 | }
|
590 |
|
591 | |
592 |
|
593 |
|
594 |
|
595 |
|
596 |
|
597 | removeSource(id: string) {
|
598 | this._checkLoaded();
|
599 |
|
600 | if (this.sourceCaches[id] === undefined) {
|
601 | throw new Error('There is no source with this ID');
|
602 | }
|
603 | for (const layerId in this._layers) {
|
604 | if (this._layers[layerId].source === id) {
|
605 | return this.fire(new ErrorEvent(new Error(`Source "${id}" cannot be removed while layer "${layerId}" is using it.`)));
|
606 | }
|
607 | }
|
608 |
|
609 | const sourceCache = this.sourceCaches[id];
|
610 | delete this.sourceCaches[id];
|
611 | delete this._updatedSources[id];
|
612 | sourceCache.fire(new Event('data', {sourceDataType: 'metadata', dataType:'source', sourceId: id}));
|
613 | sourceCache.setEventedParent(null);
|
614 | sourceCache.onRemove(this.map);
|
615 | this._changed = true;
|
616 | }
|
617 |
|
618 | |
619 |
|
620 |
|
621 |
|
622 |
|
623 | setGeoJSONSourceData(id: string, data: GeoJSON.GeoJSON | string) {
|
624 | this._checkLoaded();
|
625 |
|
626 | assert(this.sourceCaches[id] !== undefined, 'There is no source with this ID');
|
627 | const geojsonSource: GeoJSONSource = (this.sourceCaches[id].getSource() as any);
|
628 | assert(geojsonSource.type === 'geojson');
|
629 |
|
630 | geojsonSource.setData(data);
|
631 | this._changed = true;
|
632 | }
|
633 |
|
634 | |
635 |
|
636 |
|
637 |
|
638 |
|
639 | getSource(id: string): Source | undefined {
|
640 | return this.sourceCaches[id] && this.sourceCaches[id].getSource();
|
641 | }
|
642 |
|
643 | |
644 |
|
645 |
|
646 |
|
647 |
|
648 |
|
649 |
|
650 |
|
651 | addLayer(layerObject: LayerSpecification | CustomLayerInterface, before?: string, options: StyleSetterOptions = {}) {
|
652 | this._checkLoaded();
|
653 |
|
654 | const id = layerObject.id;
|
655 |
|
656 | if (this.getLayer(id)) {
|
657 | this.fire(new ErrorEvent(new Error(`Layer "${id}" already exists on this map.`)));
|
658 | return;
|
659 | }
|
660 |
|
661 | let layer;
|
662 | if (layerObject.type === 'custom') {
|
663 |
|
664 | if (emitValidationErrors(this, validateCustomStyleLayer(layerObject))) return;
|
665 |
|
666 | layer = createStyleLayer(layerObject);
|
667 |
|
668 | } else {
|
669 | if (typeof (layerObject as any).source === 'object') {
|
670 | this.addSource(id, (layerObject as any).source);
|
671 | layerObject = clone(layerObject);
|
672 | layerObject = (extend(layerObject, {source: id}) as any);
|
673 | }
|
674 |
|
675 |
|
676 | if (this._validate(validateStyle.layer,
|
677 | `layers.${id}`, layerObject, {arrayIndex: -1}, options)) return;
|
678 |
|
679 | layer = createStyleLayer(layerObject);
|
680 | this._validateLayer(layer);
|
681 |
|
682 | layer.setEventedParent(this, {layer: {id}});
|
683 | this._serializedLayers[layer.id] = layer.serialize();
|
684 | }
|
685 |
|
686 | const index = before ? this._order.indexOf(before) : this._order.length;
|
687 | if (before && index === -1) {
|
688 | this.fire(new ErrorEvent(new Error(`Cannot add layer "${id}" before non-existing layer "${before}".`)));
|
689 | return;
|
690 | }
|
691 |
|
692 | this._order.splice(index, 0, id);
|
693 | this._layerOrderChanged = true;
|
694 |
|
695 | this._layers[id] = layer;
|
696 |
|
697 | if (this._removedLayers[id] && layer.source && layer.type !== 'custom') {
|
698 |
|
699 |
|
700 |
|
701 |
|
702 |
|
703 |
|
704 |
|
705 | const removed = this._removedLayers[id];
|
706 | delete this._removedLayers[id];
|
707 | if (removed.type !== layer.type) {
|
708 | this._updatedSources[layer.source] = 'clear';
|
709 | } else {
|
710 | this._updatedSources[layer.source] = 'reload';
|
711 | this.sourceCaches[layer.source].pause();
|
712 | }
|
713 | }
|
714 | this._updateLayer(layer);
|
715 |
|
716 | if (layer.onAdd) {
|
717 | layer.onAdd(this.map);
|
718 | }
|
719 | }
|
720 |
|
721 | |
722 |
|
723 |
|
724 |
|
725 |
|
726 |
|
727 | moveLayer(id: string, before?: string) {
|
728 | this._checkLoaded();
|
729 | this._changed = true;
|
730 |
|
731 | const layer = this._layers[id];
|
732 | if (!layer) {
|
733 | this.fire(new ErrorEvent(new Error(`The layer '${id}' does not exist in the map's style and cannot be moved.`)));
|
734 | return;
|
735 | }
|
736 |
|
737 | if (id === before) {
|
738 | return;
|
739 | }
|
740 |
|
741 | const index = this._order.indexOf(id);
|
742 | this._order.splice(index, 1);
|
743 |
|
744 | const newIndex = before ? this._order.indexOf(before) : this._order.length;
|
745 | if (before && newIndex === -1) {
|
746 | this.fire(new ErrorEvent(new Error(`Cannot move layer "${id}" before non-existing layer "${before}".`)));
|
747 | return;
|
748 | }
|
749 | this._order.splice(newIndex, 0, id);
|
750 |
|
751 | this._layerOrderChanged = true;
|
752 | }
|
753 |
|
754 | |
755 |
|
756 |
|
757 |
|
758 |
|
759 |
|
760 |
|
761 |
|
762 | removeLayer(id: string) {
|
763 | this._checkLoaded();
|
764 |
|
765 | const layer = this._layers[id];
|
766 | if (!layer) {
|
767 | this.fire(new ErrorEvent(new Error(`Cannot remove non-existing layer "${id}".`)));
|
768 | return;
|
769 | }
|
770 |
|
771 | layer.setEventedParent(null);
|
772 |
|
773 | const index = this._order.indexOf(id);
|
774 | this._order.splice(index, 1);
|
775 |
|
776 | this._layerOrderChanged = true;
|
777 | this._changed = true;
|
778 | this._removedLayers[id] = layer;
|
779 | delete this._layers[id];
|
780 | delete this._serializedLayers[id];
|
781 | delete this._updatedLayers[id];
|
782 | delete this._updatedPaintProps[id];
|
783 |
|
784 | if (layer.onRemove) {
|
785 | layer.onRemove(this.map);
|
786 | }
|
787 | }
|
788 |
|
789 | |
790 |
|
791 |
|
792 |
|
793 |
|
794 |
|
795 | getLayer(id: string): StyleLayer {
|
796 | return this._layers[id];
|
797 | }
|
798 |
|
799 | |
800 |
|
801 |
|
802 |
|
803 |
|
804 |
|
805 | hasLayer(id: string): boolean {
|
806 | return id in this._layers;
|
807 | }
|
808 |
|
809 | setLayerZoomRange(layerId: string, minzoom?: number | null, maxzoom?: number | null) {
|
810 | this._checkLoaded();
|
811 |
|
812 | const layer = this.getLayer(layerId);
|
813 | if (!layer) {
|
814 | this.fire(new ErrorEvent(new Error(`Cannot set the zoom range of non-existing layer "${layerId}".`)));
|
815 | return;
|
816 | }
|
817 |
|
818 | if (layer.minzoom === minzoom && layer.maxzoom === maxzoom) return;
|
819 |
|
820 | if (minzoom != null) {
|
821 | layer.minzoom = minzoom;
|
822 | }
|
823 | if (maxzoom != null) {
|
824 | layer.maxzoom = maxzoom;
|
825 | }
|
826 | this._updateLayer(layer);
|
827 | }
|
828 |
|
829 | setFilter(layerId: string, filter?: FilterSpecification | null, options: StyleSetterOptions = {}) {
|
830 | this._checkLoaded();
|
831 |
|
832 | const layer = this.getLayer(layerId);
|
833 | if (!layer) {
|
834 | this.fire(new ErrorEvent(new Error(`Cannot filter non-existing layer "${layerId}".`)));
|
835 | return;
|
836 | }
|
837 |
|
838 | if (deepEqual(layer.filter, filter)) {
|
839 | return;
|
840 | }
|
841 |
|
842 | if (filter === null || filter === undefined) {
|
843 | layer.filter = undefined;
|
844 | this._updateLayer(layer);
|
845 | return;
|
846 | }
|
847 |
|
848 | if (this._validate(validateStyle.filter, `layers.${layer.id}.filter`, filter, null, options)) {
|
849 | return;
|
850 | }
|
851 |
|
852 | layer.filter = clone(filter);
|
853 | this._updateLayer(layer);
|
854 | }
|
855 |
|
856 | |
857 |
|
858 |
|
859 |
|
860 |
|
861 | getFilter(layer: string) {
|
862 | return clone(this.getLayer(layer).filter);
|
863 | }
|
864 |
|
865 | setLayoutProperty(layerId: string, name: string, value: any, options: StyleSetterOptions = {}) {
|
866 | this._checkLoaded();
|
867 |
|
868 | const layer = this.getLayer(layerId);
|
869 | if (!layer) {
|
870 | this.fire(new ErrorEvent(new Error(`Cannot style non-existing layer "${layerId}".`)));
|
871 | return;
|
872 | }
|
873 |
|
874 | if (deepEqual(layer.getLayoutProperty(name), value)) return;
|
875 |
|
876 | layer.setLayoutProperty(name, value, options);
|
877 | this._updateLayer(layer);
|
878 | }
|
879 |
|
880 | |
881 |
|
882 |
|
883 |
|
884 |
|
885 |
|
886 | getLayoutProperty(layerId: string, name: string) {
|
887 | const layer = this.getLayer(layerId);
|
888 | if (!layer) {
|
889 | this.fire(new ErrorEvent(new Error(`Cannot get style of non-existing layer "${layerId}".`)));
|
890 | return;
|
891 | }
|
892 |
|
893 | return layer.getLayoutProperty(name);
|
894 | }
|
895 |
|
896 | setPaintProperty(layerId: string, name: string, value: any, options: StyleSetterOptions = {}) {
|
897 | this._checkLoaded();
|
898 |
|
899 | const layer = this.getLayer(layerId);
|
900 | if (!layer) {
|
901 | this.fire(new ErrorEvent(new Error(`Cannot style non-existing layer "${layerId}".`)));
|
902 | return;
|
903 | }
|
904 |
|
905 | if (deepEqual(layer.getPaintProperty(name), value)) return;
|
906 |
|
907 | const requiresRelayout = layer.setPaintProperty(name, value, options);
|
908 | if (requiresRelayout) {
|
909 | this._updateLayer(layer);
|
910 | }
|
911 |
|
912 | this._changed = true;
|
913 | this._updatedPaintProps[layerId] = true;
|
914 | }
|
915 |
|
916 | getPaintProperty(layer: string, name: string) {
|
917 | return this.getLayer(layer).getPaintProperty(name);
|
918 | }
|
919 |
|
920 | setFeatureState(target: FeatureIdentifier, state: any) {
|
921 | this._checkLoaded();
|
922 | const sourceId = target.source;
|
923 | const sourceLayer = target.sourceLayer;
|
924 | const sourceCache = this.sourceCaches[sourceId];
|
925 |
|
926 | if (sourceCache === undefined) {
|
927 | this.fire(new ErrorEvent(new Error(`The source '${sourceId}' does not exist in the map's style.`)));
|
928 | return;
|
929 | }
|
930 | const sourceType = sourceCache.getSource().type;
|
931 | if (sourceType === 'geojson' && sourceLayer) {
|
932 | this.fire(new ErrorEvent(new Error('GeoJSON sources cannot have a sourceLayer parameter.')));
|
933 | return;
|
934 | }
|
935 | if (sourceType === 'vector' && !sourceLayer) {
|
936 | this.fire(new ErrorEvent(new Error('The sourceLayer parameter must be provided for vector source types.')));
|
937 | return;
|
938 | }
|
939 | if (target.id === undefined) {
|
940 | this.fire(new ErrorEvent(new Error('The feature id parameter must be provided.')));
|
941 | }
|
942 |
|
943 | sourceCache.setFeatureState(sourceLayer, target.id, state);
|
944 | }
|
945 |
|
946 | removeFeatureState(target: FeatureIdentifier, key?: string) {
|
947 | this._checkLoaded();
|
948 | const sourceId = target.source;
|
949 | const sourceCache = this.sourceCaches[sourceId];
|
950 |
|
951 | if (sourceCache === undefined) {
|
952 | this.fire(new ErrorEvent(new Error(`The source '${sourceId}' does not exist in the map's style.`)));
|
953 | return;
|
954 | }
|
955 |
|
956 | const sourceType = sourceCache.getSource().type;
|
957 | const sourceLayer = sourceType === 'vector' ? target.sourceLayer : undefined;
|
958 |
|
959 | if (sourceType === 'vector' && !sourceLayer) {
|
960 | this.fire(new ErrorEvent(new Error('The sourceLayer parameter must be provided for vector source types.')));
|
961 | return;
|
962 | }
|
963 |
|
964 | if (key && (typeof target.id !== 'string' && typeof target.id !== 'number')) {
|
965 | this.fire(new ErrorEvent(new Error('A feature id is required to remove its specific state property.')));
|
966 | return;
|
967 | }
|
968 |
|
969 | sourceCache.removeFeatureState(sourceLayer, target.id, key);
|
970 | }
|
971 |
|
972 | getFeatureState(target: FeatureIdentifier) {
|
973 | this._checkLoaded();
|
974 | const sourceId = target.source;
|
975 | const sourceLayer = target.sourceLayer;
|
976 | const sourceCache = this.sourceCaches[sourceId];
|
977 |
|
978 | if (sourceCache === undefined) {
|
979 | this.fire(new ErrorEvent(new Error(`The source '${sourceId}' does not exist in the map's style.`)));
|
980 | return;
|
981 | }
|
982 | const sourceType = sourceCache.getSource().type;
|
983 | if (sourceType === 'vector' && !sourceLayer) {
|
984 | this.fire(new ErrorEvent(new Error('The sourceLayer parameter must be provided for vector source types.')));
|
985 | return;
|
986 | }
|
987 | if (target.id === undefined) {
|
988 | this.fire(new ErrorEvent(new Error('The feature id parameter must be provided.')));
|
989 | }
|
990 |
|
991 | return sourceCache.getFeatureState(sourceLayer, target.id);
|
992 | }
|
993 |
|
994 | getTransition() {
|
995 | return extend({duration: 300, delay: 0}, this.stylesheet && this.stylesheet.transition);
|
996 | }
|
997 |
|
998 | serialize(): StyleSpecification {
|
999 | return filterObject({
|
1000 | version: this.stylesheet.version,
|
1001 | name: this.stylesheet.name,
|
1002 | metadata: this.stylesheet.metadata,
|
1003 | light: this.stylesheet.light,
|
1004 | center: this.stylesheet.center,
|
1005 | zoom: this.stylesheet.zoom,
|
1006 | bearing: this.stylesheet.bearing,
|
1007 | pitch: this.stylesheet.pitch,
|
1008 | sprite: this.stylesheet.sprite,
|
1009 | glyphs: this.stylesheet.glyphs,
|
1010 | transition: this.stylesheet.transition,
|
1011 | sources: mapObject(this.sourceCaches, (source) => source.serialize()),
|
1012 | layers: this._serializeLayers(this._order)
|
1013 | }, (value) => { return value !== undefined; });
|
1014 | }
|
1015 |
|
1016 | _updateLayer(layer: StyleLayer) {
|
1017 | this._updatedLayers[layer.id] = true;
|
1018 | if (layer.source && !this._updatedSources[layer.source] &&
|
1019 |
|
1020 | this.sourceCaches[layer.source].getSource().type !== 'raster') {
|
1021 | this._updatedSources[layer.source] = 'reload';
|
1022 | this.sourceCaches[layer.source].pause();
|
1023 | }
|
1024 | this._changed = true;
|
1025 | }
|
1026 |
|
1027 | _flattenAndSortRenderedFeatures(sourceResults: Array<{ [key: string]: Array<{featureIndex: number; feature: MapGeoJSONFeature}> }>) {
|
1028 |
|
1029 |
|
1030 |
|
1031 |
|
1032 |
|
1033 |
|
1034 |
|
1035 |
|
1036 |
|
1037 |
|
1038 |
|
1039 |
|
1040 |
|
1041 |
|
1042 |
|
1043 |
|
1044 |
|
1045 | const isLayer3D = layerId => this._layers[layerId].type === 'fill-extrusion';
|
1046 |
|
1047 | const layerIndex = {};
|
1048 | const features3D = [];
|
1049 | for (let l = this._order.length - 1; l >= 0; l--) {
|
1050 | const layerId = this._order[l];
|
1051 | if (isLayer3D(layerId)) {
|
1052 | layerIndex[layerId] = l;
|
1053 | for (const sourceResult of sourceResults) {
|
1054 | const layerFeatures = sourceResult[layerId];
|
1055 | if (layerFeatures) {
|
1056 | for (const featureWrapper of layerFeatures) {
|
1057 | features3D.push(featureWrapper);
|
1058 | }
|
1059 | }
|
1060 | }
|
1061 | }
|
1062 | }
|
1063 |
|
1064 | features3D.sort((a, b) => {
|
1065 | return b.intersectionZ - a.intersectionZ;
|
1066 | });
|
1067 |
|
1068 | const features = [];
|
1069 | for (let l = this._order.length - 1; l >= 0; l--) {
|
1070 | const layerId = this._order[l];
|
1071 |
|
1072 | if (isLayer3D(layerId)) {
|
1073 |
|
1074 | for (let i = features3D.length - 1; i >= 0; i--) {
|
1075 | const topmost3D = features3D[i].feature;
|
1076 | if (layerIndex[topmost3D.layer.id] < l) break;
|
1077 | features.push(topmost3D);
|
1078 | features3D.pop();
|
1079 | }
|
1080 | } else {
|
1081 | for (const sourceResult of sourceResults) {
|
1082 | const layerFeatures = sourceResult[layerId];
|
1083 | if (layerFeatures) {
|
1084 | for (const featureWrapper of layerFeatures) {
|
1085 | features.push(featureWrapper.feature);
|
1086 | }
|
1087 | }
|
1088 | }
|
1089 | }
|
1090 | }
|
1091 |
|
1092 | return features;
|
1093 | }
|
1094 |
|
1095 | queryRenderedFeatures(queryGeometry: any, params: any, transform: Transform) {
|
1096 | if (params && params.filter) {
|
1097 | this._validate(validateStyle.filter, 'queryRenderedFeatures.filter', params.filter, null, params);
|
1098 | }
|
1099 |
|
1100 | const includedSources = {};
|
1101 | if (params && params.layers) {
|
1102 | if (!Array.isArray(params.layers)) {
|
1103 | this.fire(new ErrorEvent(new Error('parameters.layers must be an Array.')));
|
1104 | return [];
|
1105 | }
|
1106 | for (const layerId of params.layers) {
|
1107 | const layer = this._layers[layerId];
|
1108 | if (!layer) {
|
1109 |
|
1110 | this.fire(new ErrorEvent(new Error(`The layer '${layerId}' does not exist in the map's style and cannot be queried for features.`)));
|
1111 | return [];
|
1112 | }
|
1113 | includedSources[layer.source] = true;
|
1114 | }
|
1115 | }
|
1116 |
|
1117 | const sourceResults = [];
|
1118 |
|
1119 | params.availableImages = this._availableImages;
|
1120 |
|
1121 | for (const id in this.sourceCaches) {
|
1122 | if (params.layers && !includedSources[id]) continue;
|
1123 | sourceResults.push(
|
1124 | queryRenderedFeatures(
|
1125 | this.sourceCaches[id],
|
1126 | this._layers,
|
1127 | this._serializedLayers,
|
1128 | queryGeometry,
|
1129 | params,
|
1130 | transform)
|
1131 | );
|
1132 | }
|
1133 |
|
1134 | if (this.placement) {
|
1135 |
|
1136 |
|
1137 | sourceResults.push(
|
1138 | queryRenderedSymbols(
|
1139 | this._layers,
|
1140 | this._serializedLayers,
|
1141 | this.sourceCaches,
|
1142 | queryGeometry,
|
1143 | params,
|
1144 | this.placement.collisionIndex,
|
1145 | this.placement.retainedQueryData)
|
1146 | );
|
1147 | }
|
1148 |
|
1149 | return this._flattenAndSortRenderedFeatures(sourceResults);
|
1150 | }
|
1151 |
|
1152 | querySourceFeatures(
|
1153 | sourceID: string,
|
1154 | params?: {
|
1155 | sourceLayer: string;
|
1156 | filter: Array<any>;
|
1157 | validate?: boolean;
|
1158 | }
|
1159 | ) {
|
1160 | if (params && params.filter) {
|
1161 | this._validate(validateStyle.filter, 'querySourceFeatures.filter', params.filter, null, params);
|
1162 | }
|
1163 | const sourceCache = this.sourceCaches[sourceID];
|
1164 | return sourceCache ? querySourceFeatures(sourceCache, params) : [];
|
1165 | }
|
1166 |
|
1167 | addSourceType(name: string, SourceType: SourceClass, callback: Callback<void>) {
|
1168 | if (Style.getSourceType(name)) {
|
1169 | return callback(new Error(`A source type called "${name}" already exists.`));
|
1170 | }
|
1171 |
|
1172 | Style.setSourceType(name, SourceType);
|
1173 |
|
1174 | if (!SourceType.workerSourceURL) {
|
1175 | return callback(null, null);
|
1176 | }
|
1177 |
|
1178 | this.dispatcher.broadcast('loadWorkerSource', {
|
1179 | name,
|
1180 | url: SourceType.workerSourceURL
|
1181 | }, callback);
|
1182 | }
|
1183 |
|
1184 | getLight() {
|
1185 | return this.light.getLight();
|
1186 | }
|
1187 |
|
1188 | setLight(lightOptions: LightSpecification, options: StyleSetterOptions = {}) {
|
1189 | this._checkLoaded();
|
1190 |
|
1191 | const light = this.light.getLight();
|
1192 | let _update = false;
|
1193 | for (const key in lightOptions) {
|
1194 | if (!deepEqual(lightOptions[key], light[key])) {
|
1195 | _update = true;
|
1196 | break;
|
1197 | }
|
1198 | }
|
1199 | if (!_update) return;
|
1200 |
|
1201 | const parameters = {
|
1202 | now: browser.now(),
|
1203 | transition: extend({
|
1204 | duration: 300,
|
1205 | delay: 0
|
1206 | }, this.stylesheet.transition)
|
1207 | };
|
1208 |
|
1209 | this.light.setLight(lightOptions, options);
|
1210 | this.light.updateTransitions(parameters);
|
1211 | }
|
1212 |
|
1213 | _validate(validate: Validator, key: string, value: any, props: any, options: {
|
1214 | validate?: boolean;
|
1215 | } = {}) {
|
1216 | if (options && options.validate === false) {
|
1217 | return false;
|
1218 | }
|
1219 | return emitValidationErrors(this, validate.call(validateStyle, extend({
|
1220 | key,
|
1221 | style: this.serialize(),
|
1222 | value,
|
1223 | styleSpec
|
1224 | }, props)));
|
1225 | }
|
1226 |
|
1227 | _remove() {
|
1228 | if (this._request) {
|
1229 | this._request.cancel();
|
1230 | this._request = null;
|
1231 | }
|
1232 | if (this._spriteRequest) {
|
1233 | this._spriteRequest.cancel();
|
1234 | this._spriteRequest = null;
|
1235 | }
|
1236 | rtlTextPluginEvented.off('pluginStateChange', this._rtlTextPluginCallback);
|
1237 | for (const layerId in this._layers) {
|
1238 | const layer: StyleLayer = this._layers[layerId];
|
1239 | layer.setEventedParent(null);
|
1240 | }
|
1241 | for (const id in this.sourceCaches) {
|
1242 | const sourceCache = this.sourceCaches[id];
|
1243 | sourceCache.setEventedParent(null);
|
1244 | sourceCache.onRemove(this.map);
|
1245 | }
|
1246 | this.imageManager.setEventedParent(null);
|
1247 | this.setEventedParent(null);
|
1248 | this.dispatcher.remove();
|
1249 | }
|
1250 |
|
1251 | _clearSource(id: string) {
|
1252 | this.sourceCaches[id].clearTiles();
|
1253 | }
|
1254 |
|
1255 | _reloadSource(id: string) {
|
1256 | this.sourceCaches[id].resume();
|
1257 | this.sourceCaches[id].reload();
|
1258 | }
|
1259 |
|
1260 | _updateSources(transform: Transform) {
|
1261 | for (const id in this.sourceCaches) {
|
1262 | this.sourceCaches[id].update(transform);
|
1263 | }
|
1264 | }
|
1265 |
|
1266 | _generateCollisionBoxes() {
|
1267 | for (const id in this.sourceCaches) {
|
1268 | this._reloadSource(id);
|
1269 | }
|
1270 | }
|
1271 |
|
1272 | _updatePlacement(transform: Transform, showCollisionBoxes: boolean, fadeDuration: number, crossSourceCollisions: boolean, forceFullPlacement: boolean = false) {
|
1273 | let symbolBucketsChanged = false;
|
1274 | let placementCommitted = false;
|
1275 |
|
1276 | const layerTiles = {};
|
1277 |
|
1278 | for (const layerID of this._order) {
|
1279 | const styleLayer = this._layers[layerID];
|
1280 | if (styleLayer.type !== 'symbol') continue;
|
1281 |
|
1282 | if (!layerTiles[styleLayer.source]) {
|
1283 | const sourceCache = this.sourceCaches[styleLayer.source];
|
1284 | layerTiles[styleLayer.source] = sourceCache.getRenderableIds(true)
|
1285 | .map((id) => sourceCache.getTileByID(id))
|
1286 | .sort((a, b) => (b.tileID.overscaledZ - a.tileID.overscaledZ) || (a.tileID.isLessThan(b.tileID) ? -1 : 1));
|
1287 | }
|
1288 |
|
1289 | const layerBucketsChanged = this.crossTileSymbolIndex.addLayer(styleLayer, layerTiles[styleLayer.source], transform.center.lng);
|
1290 | symbolBucketsChanged = symbolBucketsChanged || layerBucketsChanged;
|
1291 | }
|
1292 | this.crossTileSymbolIndex.pruneUnusedLayers(this._order);
|
1293 |
|
1294 |
|
1295 |
|
1296 |
|
1297 |
|
1298 |
|
1299 |
|
1300 | forceFullPlacement = forceFullPlacement || this._layerOrderChanged || fadeDuration === 0;
|
1301 |
|
1302 | if (forceFullPlacement || !this.pauseablePlacement || (this.pauseablePlacement.isDone() && !this.placement.stillRecent(browser.now(), transform.zoom))) {
|
1303 | this.pauseablePlacement = new PauseablePlacement(transform, this._order, forceFullPlacement, showCollisionBoxes, fadeDuration, crossSourceCollisions, this.placement);
|
1304 | this._layerOrderChanged = false;
|
1305 | }
|
1306 |
|
1307 | if (this.pauseablePlacement.isDone()) {
|
1308 |
|
1309 |
|
1310 |
|
1311 |
|
1312 | this.placement.setStale();
|
1313 | } else {
|
1314 | this.pauseablePlacement.continuePlacement(this._order, this._layers, layerTiles);
|
1315 |
|
1316 | if (this.pauseablePlacement.isDone()) {
|
1317 | this.placement = this.pauseablePlacement.commit(browser.now());
|
1318 | placementCommitted = true;
|
1319 | }
|
1320 |
|
1321 | if (symbolBucketsChanged) {
|
1322 |
|
1323 |
|
1324 |
|
1325 | this.pauseablePlacement.placement.setStale();
|
1326 | }
|
1327 | }
|
1328 |
|
1329 | if (placementCommitted || symbolBucketsChanged) {
|
1330 | for (const layerID of this._order) {
|
1331 | const styleLayer = this._layers[layerID];
|
1332 | if (styleLayer.type !== 'symbol') continue;
|
1333 | this.placement.updateLayerOpacities(styleLayer, layerTiles[styleLayer.source]);
|
1334 | }
|
1335 | }
|
1336 |
|
1337 |
|
1338 | const needsRerender = !this.pauseablePlacement.isDone() || this.placement.hasTransitions(browser.now());
|
1339 | return needsRerender;
|
1340 | }
|
1341 |
|
1342 | _releaseSymbolFadeTiles() {
|
1343 | for (const id in this.sourceCaches) {
|
1344 | this.sourceCaches[id].releaseSymbolFadeTiles();
|
1345 | }
|
1346 | }
|
1347 |
|
1348 |
|
1349 |
|
1350 | getImages(
|
1351 | mapId: string,
|
1352 | params: {
|
1353 | icons: Array<string>;
|
1354 | source: string;
|
1355 | tileID: OverscaledTileID;
|
1356 | type: string;
|
1357 | },
|
1358 | callback: Callback<{[_: string]: StyleImage}>
|
1359 | ) {
|
1360 | this.imageManager.getImages(params.icons, callback);
|
1361 |
|
1362 |
|
1363 |
|
1364 |
|
1365 |
|
1366 |
|
1367 |
|
1368 |
|
1369 |
|
1370 | this._updateTilesForChangedImages();
|
1371 |
|
1372 | const sourceCache = this.sourceCaches[params.source];
|
1373 | if (sourceCache) {
|
1374 | sourceCache.setDependencies(params.tileID.key, params.type, params.icons);
|
1375 | }
|
1376 | }
|
1377 |
|
1378 | getGlyphs(
|
1379 | mapId: string,
|
1380 | params: {stacks: {[_: string]: Array<number>}},
|
1381 | callback: Callback<{[_: string]: {[_: number]: StyleGlyph}}>
|
1382 | ) {
|
1383 | this.glyphManager.getGlyphs(params.stacks, callback);
|
1384 | }
|
1385 |
|
1386 | getResource(mapId: string, params: RequestParameters, callback: ResponseCallback<any>): Cancelable {
|
1387 | return makeRequest(params, callback);
|
1388 | }
|
1389 | }
|
1390 |
|
1391 | Style.getSourceType = getSourceType;
|
1392 | Style.setSourceType = setSourceType;
|
1393 | Style.registerForPluginStateChange = registerForPluginStateChange;
|
1394 |
|
1395 | export default Style;
|