From 1c178c7000cc61caa1bb8eb08a055c0abf790d62 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Dec 2017 14:29:54 -0800 Subject: [PATCH 1/7] Extract some SymbolBucket properties that are needed only temporarily This saves needing to serialize these properties. --- src/data/bucket/symbol_bucket.js | 39 ----------- src/style/properties.js | 1 - src/symbol/symbol_layout.js | 116 ++++++++++++++++++++++--------- 3 files changed, 83 insertions(+), 73 deletions(-) diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index 1052c080180..ae4631d4b52 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -33,7 +33,6 @@ import type IndexBuffer from '../../gl/index_buffer'; import type VertexBuffer from '../../gl/vertex_buffer'; import type {SymbolQuad} from '../../symbol/quads'; import type {SizeData} from '../../symbol/symbol_size'; -import type {PossiblyEvaluatedPropertyValue} from '../../style/properties'; export type SingleCollisionBox = { x1: number; @@ -359,27 +358,8 @@ class SymbolBucket implements Bucket { sdfIcons: boolean; iconsNeedLinear: boolean; - // The symbol layout process needs `text-size` evaluated at up to five different zoom levels, and - // `icon-size` at up to three: - // - // 1. `text-size` at the zoom level of the bucket. Used to calculate a per-feature size for source `text-size` - // expressions, and to calculate the box dimensions for icon-text-fit. - // 2. `icon-size` at the zoom level of the bucket. Used to calculate a per-feature size for source `icon-size` - // expressions. - // 3. `text-size` and `icon-size` at the zoom level of the bucket, plus one. Used to calculate collision boxes. - // 4. `text-size` at zoom level 18. Used for something line-symbol-placement-related. - // 5. For composite `*-size` expressions: two zoom levels of curve stops that "cover" the zoom level of the - // bucket. These go into a vertex buffer and are used by the shader to interpolate the size at render time. - // - // (1) and (2) are stored in `this.layers[0].layout`. The remainder are below. - // textSizeData: SizeData; iconSizeData: SizeData; - layoutTextSize: PossiblyEvaluatedPropertyValue; // (3) - layoutIconSize: PossiblyEvaluatedPropertyValue; // (3) - textMaxSize: PossiblyEvaluatedPropertyValue; // (4) - compositeTextSizes: [PossiblyEvaluatedPropertyValue, PossiblyEvaluatedPropertyValue]; // (5) - compositeIconSizes: [PossiblyEvaluatedPropertyValue, PossiblyEvaluatedPropertyValue]; // (5) placedGlyphArray: StructArray; placedIconArray: StructArray; @@ -413,26 +393,7 @@ class SymbolBucket implements Bucket { const unevaluatedLayoutValues = layer._unevaluatedLayout._values; this.textSizeData = getSizeData(this.zoom, unevaluatedLayoutValues['text-size']); - if (this.textSizeData.functionType === 'composite') { - const {min, max} = this.textSizeData.zoomRange; - this.compositeTextSizes = [ - unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: min}), - unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: max}) - ]; - } - this.iconSizeData = getSizeData(this.zoom, unevaluatedLayoutValues['icon-size']); - if (this.iconSizeData.functionType === 'composite') { - const {min, max} = this.iconSizeData.zoomRange; - this.compositeIconSizes = [ - unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: min}), - unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: max}) - ]; - } - - this.layoutTextSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: this.zoom + 1}); - this.layoutIconSize = unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: this.zoom + 1}); - this.textMaxSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: 18}); const layout = this.layers[0].layout; this.sortFeaturesByY = layout.get('text-allow-overlap') || layout.get('icon-allow-overlap') || diff --git a/src/style/properties.js b/src/style/properties.js index 6863c8b9b1a..e7619a620e2 100644 --- a/src/style/properties.js +++ b/src/style/properties.js @@ -681,7 +681,6 @@ class Properties { } } -register(PossiblyEvaluatedPropertyValue); register(DataDrivenProperty); register(DataConstantProperty); register(CrossFadedProperty); diff --git a/src/symbol/symbol_layout.js b/src/symbol/symbol_layout.js index e9e832b42b1..6b386ef1595 100644 --- a/src/symbol/symbol_layout.js +++ b/src/symbol/symbol_layout.js @@ -1,4 +1,5 @@ // @flow + const Anchor = require('./anchor'); const getAnchors = require('./get_anchors'); const clipLine = require('./clip_line'); @@ -21,6 +22,7 @@ import type {StyleGlyph} from '../style/style_glyph'; import type SymbolStyleLayer from '../style/style_layer/symbol_style_layer'; import type {ImagePosition} from '../render/image_atlas'; import type {GlyphPosition} from '../render/glyph_atlas'; +import type {PossiblyEvaluatedPropertyValue} from '../style/properties'; const Point = require('@mapbox/point-geometry'); @@ -28,6 +30,28 @@ module.exports = { performSymbolLayout }; +// The symbol layout process needs `text-size` evaluated at up to five different zoom levels, and +// `icon-size` at up to three: +// +// 1. `text-size` at the zoom level of the bucket. Used to calculate a per-feature size for source `text-size` +// expressions, and to calculate the box dimensions for icon-text-fit. +// 2. `icon-size` at the zoom level of the bucket. Used to calculate a per-feature size for source `icon-size` +// expressions. +// 3. `text-size` and `icon-size` at the zoom level of the bucket, plus one. Used to calculate collision boxes. +// 4. `text-size` at zoom level 18. Used for something line-symbol-placement-related. +// 5. For composite `*-size` expressions: two zoom levels of curve stops that "cover" the zoom level of the +// bucket. These go into a vertex buffer and are used by the shader to interpolate the size at render time. +// +// (1) and (2) are stored in `bucket.layers[0].layout`. The remainder are below. +// +type Sizes = { + layoutTextSize: PossiblyEvaluatedPropertyValue, // (3) + layoutIconSize: PossiblyEvaluatedPropertyValue, // (3) + textMaxSize: PossiblyEvaluatedPropertyValue, // (4) + compositeTextSizes: [PossiblyEvaluatedPropertyValue, PossiblyEvaluatedPropertyValue], // (5) + compositeIconSizes: [PossiblyEvaluatedPropertyValue, PossiblyEvaluatedPropertyValue], // (5) +}; + function performSymbolLayout(bucket: SymbolBucket, glyphMap: {[string]: {[number]: ?StyleGlyph}}, glyphPositions: {[string]: {[number]: GlyphPosition}}, @@ -43,6 +67,29 @@ function performSymbolLayout(bucket: SymbolBucket, bucket.iconsNeedLinear = false; const layout = bucket.layers[0].layout; + const unevaluatedLayoutValues = bucket.layers[0]._unevaluatedLayout._values; + + const sizes = {}; + + if (bucket.textSizeData.functionType === 'composite') { + const {min, max} = bucket.textSizeData.zoomRange; + sizes.compositeTextSizes = [ + unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: min}), + unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: max}) + ]; + } + + if (bucket.iconSizeData.functionType === 'composite') { + const {min, max} = bucket.iconSizeData.zoomRange; + sizes.compositeIconSizes = [ + unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: min}), + unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: max}) + ]; + } + + sizes.layoutTextSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: bucket.zoom + 1}); + sizes.layoutIconSize = unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: bucket.zoom + 1}); + sizes.textMaxSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: 18}); const oneEm = 24; const lineHeight = layout.get('text-line-height') * oneEm; @@ -96,7 +143,7 @@ function performSymbolLayout(bucket: SymbolBucket, } if (shapedTextOrientations.horizontal || shapedIcon) { - addFeature(bucket, feature, shapedTextOrientations, shapedIcon, glyphPositionMap); + addFeature(bucket, feature, shapedTextOrientations, shapedIcon, glyphPositionMap, sizes); } } @@ -117,15 +164,16 @@ function addFeature(bucket: SymbolBucket, feature: SymbolFeature, shapedTextOrientations: any, shapedIcon: PositionedIcon | void, - glyphPositionMap: {[number]: GlyphPosition}) { - const layoutTextSize = bucket.layoutTextSize.evaluate(feature); - const layoutIconSize = bucket.layoutIconSize.evaluate(feature); + glyphPositionMap: {[number]: GlyphPosition}, + sizes: Sizes) { + const layoutTextSize = sizes.layoutTextSize.evaluate(feature); + const layoutIconSize = sizes.layoutIconSize.evaluate(feature); // To reduce the number of labels that jump around when zooming we need // to use a text-size value that is the same for all zoom levels. // bucket calculates text-size at a high zoom level so that all tiles can // use the same value when calculating anchor positions. - let textMaxSize = bucket.textMaxSize.evaluate(feature); + let textMaxSize = sizes.textMaxSize.evaluate(feature); if (textMaxSize === undefined) { textMaxSize = layoutTextSize; } @@ -160,7 +208,7 @@ function addFeature(bucket: SymbolBucket, bucket.collisionBoxArray, feature.index, feature.sourceLayerIndex, bucket.index, textBoxScale, textPadding, textAlongLine, textOffset, iconBoxScale, iconPadding, iconAlongLine, iconOffset, - {zoom: bucket.zoom}, feature, glyphPositionMap)); + {zoom: bucket.zoom}, feature, glyphPositionMap, sizes)); }; if (symbolPlacement === 'line') { @@ -214,7 +262,8 @@ function addTextVertices(bucket: SymbolBucket, lineArray: any, writingMode: number, placedTextSymbolIndices: Array, - glyphPositionMap: {[number]: GlyphPosition}) { + glyphPositionMap: {[number]: GlyphPosition}, + sizes: Sizes) { const glyphQuads = getGlyphQuads(anchor, shapedText, layer, textAlongLine, globalProperties, feature, glyphPositionMap); @@ -227,8 +276,8 @@ function addTextVertices(bucket: SymbolBucket, ]; } else if (sizeData.functionType === 'composite') { textSizeData = [ - 10 * bucket.compositeTextSizes[0].evaluate(feature), - 10 * bucket.compositeTextSizes[1].evaluate(feature) + 10 * sizes.compositeTextSizes[0].evaluate(feature), + 10 * sizes.compositeTextSizes[1].evaluate(feature) ]; } @@ -259,26 +308,27 @@ function addTextVertices(bucket: SymbolBucket, * @private */ function addSymbol(bucket: SymbolBucket, - anchor: Anchor, - line: Array, - shapedTextOrientations: any, - shapedIcon: PositionedIcon | void, - layer: SymbolStyleLayer, - collisionBoxArray: CollisionBoxArray, - featureIndex: number, - sourceLayerIndex: number, - bucketIndex: number, - textBoxScale: number, - textPadding: number, - textAlongLine: boolean, - textOffset: [number, number], - iconBoxScale: number, - iconPadding: number, - iconAlongLine: boolean, - iconOffset: [number, number], - globalProperties: Object, - feature: SymbolFeature, - glyphPositionMap: {[number]: GlyphPosition}) { + anchor: Anchor, + line: Array, + shapedTextOrientations: any, + shapedIcon: PositionedIcon | void, + layer: SymbolStyleLayer, + collisionBoxArray: CollisionBoxArray, + featureIndex: number, + sourceLayerIndex: number, + bucketIndex: number, + textBoxScale: number, + textPadding: number, + textAlongLine: boolean, + textOffset: [number, number], + iconBoxScale: number, + iconPadding: number, + iconAlongLine: boolean, + iconOffset: [number, number], + globalProperties: Object, + feature: SymbolFeature, + glyphPositionMap: {[number]: GlyphPosition}, + sizes: Sizes) { const lineArray = bucket.addToLineVertexArray(anchor, line); let textCollisionFeature, iconCollisionFeature; @@ -292,10 +342,10 @@ function addSymbol(bucket: SymbolBucket, // As a collision approximation, we can use either the vertical or the horizontal version of the feature // We're counting on the two versions having similar dimensions textCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, shapedTextOrientations.horizontal, textBoxScale, textPadding, textAlongLine, bucket.overscaling); - numGlyphVertices += addTextVertices(bucket, anchor, shapedTextOrientations.horizontal, layer, textAlongLine, globalProperties, feature, textOffset, lineArray, shapedTextOrientations.vertical ? WritingMode.horizontal : WritingMode.horizontalOnly, placedTextSymbolIndices, glyphPositionMap); + numGlyphVertices += addTextVertices(bucket, anchor, shapedTextOrientations.horizontal, layer, textAlongLine, globalProperties, feature, textOffset, lineArray, shapedTextOrientations.vertical ? WritingMode.horizontal : WritingMode.horizontalOnly, placedTextSymbolIndices, glyphPositionMap, sizes); if (shapedTextOrientations.vertical) { - numVerticalGlyphVertices += addTextVertices(bucket, anchor, shapedTextOrientations.vertical, layer, textAlongLine, globalProperties, feature, textOffset, lineArray, WritingMode.vertical, placedTextSymbolIndices, glyphPositionMap); + numVerticalGlyphVertices += addTextVertices(bucket, anchor, shapedTextOrientations.vertical, layer, textAlongLine, globalProperties, feature, textOffset, lineArray, WritingMode.vertical, placedTextSymbolIndices, glyphPositionMap, sizes); } } @@ -319,8 +369,8 @@ function addSymbol(bucket: SymbolBucket, ]; } else if (sizeData.functionType === 'composite') { iconSizeData = [ - 10 * bucket.compositeIconSizes[0].evaluate(feature), - 10 * bucket.compositeIconSizes[1].evaluate(feature) + 10 * sizes.compositeIconSizes[0].evaluate(feature), + 10 * sizes.compositeIconSizes[1].evaluate(feature) ]; } From 26d2df520ba281ddf8f670f7429eaf4d3f736275 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Dec 2017 10:48:10 -0800 Subject: [PATCH 2/7] Combine Style#{update,_recalculate} --- src/style/style.js | 99 ++++++++++++++++++----------------- src/ui/map.js | 3 +- test/unit/style/style.test.js | 9 ++-- 3 files changed, 54 insertions(+), 57 deletions(-) diff --git a/src/style/style.js b/src/style/style.js index 88518f0cfdf..fe7aaf3f18c 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -271,32 +271,6 @@ class Style extends Evented { return ids.map((id) => this._layers[id].serialize()); } - _recalculate(z: number, fadeDuration: number) { - if (!this._loaded) return; - - for (const sourceId in this.sourceCaches) - this.sourceCaches[sourceId].used = false; - - const parameters = { - zoom: z, - now: browser.now(), - fadeDuration, - zoomHistory: this._updateZoomHistory(z) - }; - - for (const layerId of this._order) { - const layer = this._layers[layerId]; - - layer.recalculate(parameters); - if (!layer.isHidden(z) && layer.source) { - this.sourceCaches[layer.source].used = true; - } - } - - this.light.recalculate(parameters); - this.z = z; - } - hasTransitions() { if (this.light && this.light.hasTransition()) { return true; @@ -350,41 +324,68 @@ class Style extends Evented { } /** - * Apply queued style updates in a batch + * Apply queued style updates in a batch and recalculate zoom-dependent paint properties. */ - update() { - if (!this._changed || !this._loaded) return; + update(z: number, fadeDuration: number) { + if (!this._loaded) { + return; + } - const updatedIds = Object.keys(this._updatedLayers); - const removedIds = Object.keys(this._removedLayers); + if (this._changed) { + const updatedIds = Object.keys(this._updatedLayers); + const removedIds = Object.keys(this._removedLayers); - if (updatedIds.length || removedIds.length) { - this._updateWorkerLayers(updatedIds, removedIds); - } - for (const id in this._updatedSources) { - const action = this._updatedSources[id]; - assert(action === 'reload' || action === 'clear'); - if (action === 'reload') { - this._reloadSource(id); - } else if (action === 'clear') { - this._clearSource(id); + if (updatedIds.length || removedIds.length) { + this._updateWorkerLayers(updatedIds, removedIds); } + for (const id in this._updatedSources) { + const action = this._updatedSources[id]; + assert(action === 'reload' || action === 'clear'); + if (action === 'reload') { + this._reloadSource(id); + } else if (action === 'clear') { + this._clearSource(id); + } + } + + const parameters = { + now: browser.now(), + transition: util.extend({ duration: 300, delay: 0 }, this.stylesheet.transition) + }; + + for (const id in this._updatedPaintProps) { + this._layers[id].updateTransitions(parameters); + } + + this.light.updateTransitions(parameters); + + this._resetUpdates(); + + this.fire('data', {dataType: 'style'}); + } + + for (const sourceId in this.sourceCaches) { + this.sourceCaches[sourceId].used = false; } const parameters = { + zoom: z, now: browser.now(), - transition: util.extend({ duration: 300, delay: 0 }, this.stylesheet.transition) + fadeDuration, + zoomHistory: this._updateZoomHistory(z) }; - for (const id in this._updatedPaintProps) { - this._layers[id].updateTransitions(parameters); - } - - this.light.updateTransitions(parameters); + for (const layerId of this._order) { + const layer = this._layers[layerId]; - this._resetUpdates(); + layer.recalculate(parameters); + if (!layer.isHidden(z) && layer.source) { + this.sourceCaches[layer.source].used = true; + } + } - this.fire('data', {dataType: 'style'}); + this.light.recalculate(parameters); + this.z = z; } _updateWorkerLayers(updatedIds: Array, removedIds: Array) { diff --git a/src/ui/map.js b/src/ui/map.js index a5214fde9ac..e1619a0fd7f 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -1464,8 +1464,7 @@ class Map extends Camera { // - Recalculate zoom-dependent paint properties. if (this.style && this._styleDirty) { this._styleDirty = false; - this.style.update(); - this.style._recalculate(this.transform.zoom, this._fadeDuration); + this.style.update(this.transform.zoom, this._fadeDuration); } // If we are in _render for any reason other than an in-progress paint diff --git a/test/unit/style/style.test.js b/test/unit/style/style.test.js index a8daf1d3896..4cf38f0b69d 100644 --- a/test/unit/style/style.test.js +++ b/test/unit/style/style.test.js @@ -708,8 +708,7 @@ test('Style#removeSource', (t) => { }] })); style.on('style.load', () => { - style.update(); - style._recalculate(1); + style.update(1, 0); callback(style); }); return style; @@ -1281,8 +1280,7 @@ test('Style#setPaintProperty', (t) => { tr.resize(512, 512); style.once('style.load', () => { - style.update(); - style._recalculate(tr.zoom); + style.update(tr.zoom, 0); const sourceCache = style.sourceCaches['geojson']; const source = style.getSource('geojson'); @@ -1577,8 +1575,7 @@ test('Style#queryRenderedFeatures', (t) => { }); style.on('style.load', () => { - style.update(); - style._recalculate(0); + style.update(0, 0); t.test('returns feature type', (t) => { const results = style.queryRenderedFeatures([{column: 1, row: 1, zoom: 1}], {}, 0, 0); From f94e495fe75edbaac0cbebb69e81e65a33ac1842 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Dec 2017 12:32:30 -0800 Subject: [PATCH 3/7] Push parameters up --- src/style/style.js | 23 +++++++-------------- src/ui/map.js | 8 ++++++- test/unit/style/style.test.js | 39 ++++++++++++++++------------------- test/unit/ui/map.test.js | 2 +- 4 files changed, 34 insertions(+), 38 deletions(-) diff --git a/src/style/style.js b/src/style/style.js index fe7aaf3f18c..504a7c72462 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -33,6 +33,11 @@ import type {StyleImage} from './style_image'; import type {StyleGlyph} from './style_glyph'; import type CollisionIndex from '../symbol/collision_index'; import type {Callback} from '../types/callback'; +import type { + TransitionParameters, + EvaluationParameters +} from './properties'; + const supportedDiffOperations = util.pick(diff.operations, [ 'addLayer', @@ -326,7 +331,7 @@ class Style extends Evented { /** * Apply queued style updates in a batch and recalculate zoom-dependent paint properties. */ - update(z: number, fadeDuration: number) { + update(parameters: TransitionParameters & EvaluationParameters) { if (!this._loaded) { return; } @@ -348,11 +353,6 @@ class Style extends Evented { } } - const parameters = { - now: browser.now(), - transition: util.extend({ duration: 300, delay: 0 }, this.stylesheet.transition) - }; - for (const id in this._updatedPaintProps) { this._layers[id].updateTransitions(parameters); } @@ -368,24 +368,17 @@ class Style extends Evented { this.sourceCaches[sourceId].used = false; } - const parameters = { - zoom: z, - now: browser.now(), - fadeDuration, - zoomHistory: this._updateZoomHistory(z) - }; - for (const layerId of this._order) { const layer = this._layers[layerId]; layer.recalculate(parameters); - if (!layer.isHidden(z) && layer.source) { + if (!layer.isHidden(parameters.zoom) && layer.source) { this.sourceCaches[layer.source].used = true; } } this.light.recalculate(parameters); - this.z = z; + this.z = parameters.zoom; } _updateWorkerLayers(updatedIds: Array, removedIds: Array) { diff --git a/src/ui/map.js b/src/ui/map.js index e1619a0fd7f..439838eab0e 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -1464,7 +1464,13 @@ class Map extends Camera { // - Recalculate zoom-dependent paint properties. if (this.style && this._styleDirty) { this._styleDirty = false; - this.style.update(this.transform.zoom, this._fadeDuration); + this.style.update({ + zoom: this.transform.zoom, + now: browser.now(), + fadeDuration: this._fadeDuration, + zoomHistory: this.style._updateZoomHistory(this.transform.zoom), + transition: util.extend({ duration: 300, delay: 0 }, this.style.stylesheet.transition) + }); } // If we are in _render for any reason other than an in-progress paint diff --git a/test/unit/style/style.test.js b/test/unit/style/style.test.js index 4cf38f0b69d..6ff76232a3b 100644 --- a/test/unit/style/style.test.js +++ b/test/unit/style/style.test.js @@ -329,7 +329,7 @@ test('Style#loadJSON', (t) => { 'source': '-source-id-', 'source-layer': '-source-layer-' }); - style.update(); + style.update({}); }); style.on('error', (event) => { @@ -431,7 +431,7 @@ test('Style#update', (t) => { t.end(); }; - style.update(); + style.update({}); }); }); @@ -581,7 +581,7 @@ test('Style#addSource', (t) => { style.once('data', t.end); style.on('style.load', () => { style.addSource('source-id', source); - style.update(); + style.update({}); }); }); @@ -664,7 +664,7 @@ test('Style#removeSource', (t) => { style.on('style.load', () => { style.addSource('source-id', source); style.removeSource('source-id'); - style.update(); + style.update({}); }); }); @@ -900,7 +900,7 @@ test('Style#addLayer', (t) => { if (e.dataType === 'source' && e.sourceDataType === 'content') { style.sourceCaches['mapbox'].reload = t.end; style.addLayer(layer); - style.update(); + style.update({}); } }); }); @@ -936,7 +936,7 @@ test('Style#addLayer', (t) => { style.sourceCaches['mapbox'].clearTiles = t.fail; style.removeLayer('my-layer'); style.addLayer(layer); - style.update(); + style.update({}); } }); @@ -972,7 +972,7 @@ test('Style#addLayer', (t) => { style.sourceCaches['mapbox'].clearTiles = t.end; style.removeLayer('my-layer'); style.addLayer(layer); - style.update(); + style.update({}); } }); @@ -987,7 +987,7 @@ test('Style#addLayer', (t) => { style.on('style.load', () => { style.addLayer(layer); - style.update(); + style.update({}); }); }); @@ -1119,7 +1119,7 @@ test('Style#removeLayer', (t) => { style.on('style.load', () => { style.addLayer(layer); style.removeLayer('background'); - style.update(); + style.update({}); }); }); @@ -1220,7 +1220,7 @@ test('Style#moveLayer', (t) => { style.on('style.load', () => { style.addLayer(layer); style.moveLayer('background'); - style.update(); + style.update({}); }); }); @@ -1306,7 +1306,7 @@ test('Style#setPaintProperty', (t) => { // after the next Style#update() setTimeout(() => { styleUpdateCalled = true; - style.update(); + style.update({}); }, 50); } })); @@ -1350,7 +1350,7 @@ test('Style#setFilter', (t) => { style.setFilter('symbol', ['==', 'id', 1]); t.deepEqual(style.getFilter('symbol'), ['==', 'id', 1]); - style.update(); // trigger dispatcher broadcast + style.update({}); // trigger dispatcher broadcast }); }); @@ -1377,7 +1377,7 @@ test('Style#setFilter', (t) => { style.on('style.load', () => { const filter = ['==', 'id', 1]; style.setFilter('symbol', filter); - style.update(); // flush pending operations + style.update({}); // flush pending operations style.dispatcher.broadcast = function(key, value) { t.equal(key, 'updateLayers'); @@ -1387,7 +1387,7 @@ test('Style#setFilter', (t) => { }; filter[2] = 2; style.setFilter('symbol', filter); - style.update(); // trigger dispatcher broadcast + style.update({}); // trigger dispatcher broadcast }); }); @@ -1658,7 +1658,7 @@ test('Style defers expensive methods', (t) => { })); style.on('style.load', () => { - style.update(); + style.update({}); // spies to track defered methods t.spy(style, 'fire'); @@ -1676,7 +1676,7 @@ test('Style defers expensive methods', (t) => { t.notOk(style._reloadSource.called, '_reloadSource is deferred'); t.notOk(style._updateWorkerLayers.called, '_updateWorkerLayers is deferred'); - style.update(); + style.update({}); t.ok(style.fire.calledWith('data'), 'a data event was fired'); @@ -1811,7 +1811,7 @@ test('Style#hasTransitions', (t) => { style.on('style.load', () => { style.setPaintProperty("background", "background-color", "blue"); - style.update(); + style.update({transition: { duration: 300, delay: 0 }}); t.equal(style.hasTransitions(), true); t.end(); }); @@ -1822,9 +1822,6 @@ test('Style#hasTransitions', (t) => { style.loadJSON({ "version": 8, "sources": {}, - "transition": { - "duration": 0 - }, "layers": [{ "id": "background", "type": "background" @@ -1833,7 +1830,7 @@ test('Style#hasTransitions', (t) => { style.on('style.load', () => { style.setPaintProperty("background", "background-color", "blue"); - style.update(); + style.update({transition: { duration: 0, delay: 0 }}); t.equal(style.hasTransitions(), false); t.end(); }); diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js index fd41dca2d5e..44999e5b09a 100755 --- a/test/unit/ui/map.test.js +++ b/test/unit/ui/map.test.js @@ -919,7 +919,7 @@ test('Map', (t) => { }; map.setLayoutProperty('symbol', 'text-transform', 'lowercase'); - map.style.update(); + map.style.update({}); t.deepEqual(map.getLayoutProperty('symbol', 'text-transform'), 'lowercase'); t.end(); }); From aaf815bab69549a6df96407d3a309003179ae381 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Dec 2017 12:50:37 -0800 Subject: [PATCH 4/7] Extract ZoomHistory class --- src/source/worker_tile.js | 9 +++----- src/style/properties.js | 2 +- src/style/style.js | 37 +++----------------------------- src/style/zoom_history.js | 44 +++++++++++++++++++++++++++++++++++++++ src/ui/map.js | 9 +++++--- 5 files changed, 57 insertions(+), 44 deletions(-) create mode 100644 src/style/zoom_history.js diff --git a/src/source/worker_tile.js b/src/source/worker_tile.js index 5c7f7738c53..729090e85ef 100644 --- a/src/source/worker_tile.js +++ b/src/source/worker_tile.js @@ -11,6 +11,7 @@ const {makeImageAtlas} = require('../render/image_atlas'); const {makeGlyphAtlas} = require('../render/glyph_atlas'); const {serialize} = require('../util/web_worker_transfer'); const TileCoord = require('./tile_coord'); +const ZoomHistory = require('../style/zoom_history'); import type {Bucket} from '../data/bucket'; import type Actor from '../util/actor'; @@ -186,12 +187,8 @@ function recalculateLayers(layers: $ReadOnlyArray, zoom: number) { layer.recalculate({ zoom, now: Number.MAX_VALUE, - defaultFadeDuration: 0, - zoomHistory: { - lastIntegerZoom: 0, - lastIntegerZoomTime: 0, - lastZoom: 0 - } + fadeDuration: 0, + zoomHistory: new ZoomHistory() }); } } diff --git a/src/style/properties.js b/src/style/properties.js index e7619a620e2..f5acc06936f 100644 --- a/src/style/properties.js +++ b/src/style/properties.js @@ -9,7 +9,7 @@ const {register} = require('../util/web_worker_transfer'); import type {StylePropertySpecification} from '../style-spec/style-spec'; import type {CrossFaded} from './cross_faded'; -import type {ZoomHistory} from './style'; +import type ZoomHistory from './zoom_history'; import type { Feature, diff --git a/src/style/style.js b/src/style/style.js index 504a7c72462..f943a2858ff 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -25,6 +25,7 @@ const deref = require('../style-spec/deref'); const diff = require('../style-spec/diff'); const rtlTextPlugin = require('../source/rtl_text_plugin'); const Placement = require('./placement'); +const ZoomHistory = require('./zoom_history'); import type Map from '../ui/map'; import type Transform from '../geo/transform'; @@ -67,12 +68,6 @@ export type StyleOptions = { localIdeographFontFamily?: string }; -export type ZoomHistory = { - lastIntegerZoom: number, - lastIntegerZoomTime: number, - lastZoom: number -}; - /** * @private */ @@ -88,7 +83,7 @@ class Style extends Evented { _layers: {[string]: StyleLayer}; _order: Array; sourceCaches: {[string]: SourceCache}; - zoomHistory: ZoomHistory | {}; + zoomHistory: ZoomHistory; _loaded: boolean; _rtlTextPluginCallback: Function; _changed: boolean; @@ -114,7 +109,7 @@ class Style extends Evented { this._layers = {}; this._order = []; this.sourceCaches = {}; - this.zoomHistory = {}; + this.zoomHistory = new ZoomHistory(); this._loaded = false; this._resetUpdates(); @@ -296,32 +291,6 @@ class Style extends Evented { return false; } - _updateZoomHistory(z: number) { - - const zh: ZoomHistory = (this.zoomHistory: any); - - if (zh.lastIntegerZoom === undefined) { - // first time - zh.lastIntegerZoom = Math.floor(z); - zh.lastIntegerZoomTime = 0; - zh.lastZoom = z; - } - - // check whether an integer zoom level as passed since the last frame - // and if yes, record it with the time. Used for transitioning patterns. - if (Math.floor(zh.lastZoom) < Math.floor(z)) { - zh.lastIntegerZoom = Math.floor(z); - zh.lastIntegerZoomTime = browser.now(); - - } else if (Math.floor(zh.lastZoom) > Math.floor(z)) { - zh.lastIntegerZoom = Math.floor(z + 1); - zh.lastIntegerZoomTime = browser.now(); - } - - zh.lastZoom = z; - return zh; - } - _checkLoaded() { if (!this._loaded) { throw new Error('Style is not done loading'); diff --git a/src/style/zoom_history.js b/src/style/zoom_history.js new file mode 100644 index 00000000000..17599e0deae --- /dev/null +++ b/src/style/zoom_history.js @@ -0,0 +1,44 @@ +// @flow + +class ZoomHistory { + lastZoom: number; + lastFloorZoom: number; + lastIntegerZoom: number; + lastIntegerZoomTime: number; + first: boolean; + + constructor() { + this.first = true; + } + + update(z: number, now: number) { + const floorZ = Math.floor(z); + + if (this.first) { + this.first = false; + this.lastIntegerZoom = floorZ; + this.lastIntegerZoomTime = 0; + this.lastZoom = z; + this.lastFloorZoom = floorZ; + return true; + } + + if (this.lastFloorZoom > floorZ) { + this.lastIntegerZoom = floorZ + 1; + this.lastIntegerZoomTime = now; + } else if (this.lastFloorZoom < floorZ) { + this.lastIntegerZoom = floorZ; + this.lastIntegerZoomTime = now; + } + + if (z !== this.lastZoom) { + this.lastZoom = z; + this.lastFloorZoom = floorZ; + return true; + } + + return false; + } +} + +module.exports = ZoomHistory; diff --git a/src/ui/map.js b/src/ui/map.js index 439838eab0e..4539d46492a 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -1464,11 +1464,14 @@ class Map extends Camera { // - Recalculate zoom-dependent paint properties. if (this.style && this._styleDirty) { this._styleDirty = false; + const zoom = this.transform.zoom; + const now = browser.now(); + this.style.zoomHistory.update(zoom, now); this.style.update({ - zoom: this.transform.zoom, - now: browser.now(), + zoom, + now, fadeDuration: this._fadeDuration, - zoomHistory: this.style._updateZoomHistory(this.transform.zoom), + zoomHistory: this.style.zoomHistory, transition: util.extend({ duration: 300, delay: 0 }, this.style.stylesheet.transition) }); } From 1b88c8ea6caf9500a34a48e1304333a134e8d01f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 30 Nov 2017 12:10:43 -0800 Subject: [PATCH 5/7] Extract EvaluationParameters class --- src/source/worker_tile.js | 10 +++---- src/style/evaluation_parameters.js | 29 +++++++++++++++++++++ src/style/light.js | 8 +++--- src/style/properties.js | 10 ++----- src/style/style.js | 8 ++---- src/style/style_layer.js | 6 ++--- src/style/style_layer/fill_style_layer.js | 2 +- src/style/style_layer/line_style_layer.js | 2 +- src/style/style_layer/symbol_style_layer.js | 2 +- src/symbol/symbol_layout.js | 15 ++++++----- src/ui/map.js | 13 +++++---- 11 files changed, 61 insertions(+), 44 deletions(-) create mode 100644 src/style/evaluation_parameters.js diff --git a/src/source/worker_tile.js b/src/source/worker_tile.js index 729090e85ef..852a763e1df 100644 --- a/src/source/worker_tile.js +++ b/src/source/worker_tile.js @@ -11,7 +11,7 @@ const {makeImageAtlas} = require('../render/image_atlas'); const {makeGlyphAtlas} = require('../render/glyph_atlas'); const {serialize} = require('../util/web_worker_transfer'); const TileCoord = require('./tile_coord'); -const ZoomHistory = require('../style/zoom_history'); +const EvaluationParameters = require('../style/evaluation_parameters'); import type {Bucket} from '../data/bucket'; import type Actor from '../util/actor'; @@ -183,13 +183,9 @@ class WorkerTile { function recalculateLayers(layers: $ReadOnlyArray, zoom: number) { // Layers are shared and may have been used by a WorkerTile with a different zoom. + const parameters = new EvaluationParameters(zoom); for (const layer of layers) { - layer.recalculate({ - zoom, - now: Number.MAX_VALUE, - fadeDuration: 0, - zoomHistory: new ZoomHistory() - }); + layer.recalculate(parameters); } } diff --git a/src/style/evaluation_parameters.js b/src/style/evaluation_parameters.js new file mode 100644 index 00000000000..26cc2d375c9 --- /dev/null +++ b/src/style/evaluation_parameters.js @@ -0,0 +1,29 @@ +// @flow + +const ZoomHistory = require('./zoom_history'); + +class EvaluationParameters { + zoom: number; + now: number; + fadeDuration: number; + zoomHistory: ZoomHistory; + transition: TransitionSpecification; + + constructor(zoom: number, options?: *) { + this.zoom = zoom; + + if (options) { + this.now = options.now; + this.fadeDuration = options.fadeDuration; + this.zoomHistory = options.zoomHistory; + this.transition = options.transition; + } else { + this.now = 0; + this.fadeDuration = 0; + this.zoomHistory = new ZoomHistory(); + this.transition = {}; + } + } +} + +module.exports = EvaluationParameters; diff --git a/src/style/light.js b/src/style/light.js index 99de9939cbe..56d4ff50124 100644 --- a/src/style/light.js +++ b/src/style/light.js @@ -1,7 +1,5 @@ // @flow -import type {StylePropertySpecification} from "../style-spec/style-spec"; - const styleSpec = require('../style-spec/reference/latest'); const util = require('../util/util'); const Evented = require('../util/evented'); @@ -10,6 +8,9 @@ const {sphericalToCartesian} = require('../util/util'); const Color = require('../style-spec/util/color'); const interpolate = require('../style-spec/util/interpolate'); +import type {StylePropertySpecification} from '../style-spec/style-spec'; +import type EvaluationParameters from './evaluation_parameters'; + const { Properties, Transitionable, @@ -21,8 +22,7 @@ const { import type { Property, PropertyValue, - TransitionParameters, - EvaluationParameters + TransitionParameters } from './properties'; type LightPosition = { diff --git a/src/style/properties.js b/src/style/properties.js index f5acc06936f..0ba5774239e 100644 --- a/src/style/properties.js +++ b/src/style/properties.js @@ -9,7 +9,7 @@ const {register} = require('../util/web_worker_transfer'); import type {StylePropertySpecification} from '../style-spec/style-spec'; import type {CrossFaded} from './cross_faded'; -import type ZoomHistory from './zoom_history'; +import type EvaluationParameters from './evaluation_parameters'; import type { Feature, @@ -21,12 +21,6 @@ import type { type TimePoint = number; -export type EvaluationParameters = GlobalProperties & { - now?: TimePoint, - fadeDuration?: number, - zoomHistory?: ZoomHistory -}; - /** * Implements a number of classes that define state and behavior for paint and layout properties, most * importantly their respective evaluation chains: @@ -610,7 +604,7 @@ class CrossFadedProperty implements Property> { } } - _calculate(min: T, mid: T, max: T, parameters: any): ?CrossFaded { + _calculate(min: T, mid: T, max: T, parameters: EvaluationParameters): ?CrossFaded { const z = parameters.zoom; const fraction = z - Math.floor(z); const d = parameters.fadeDuration; diff --git a/src/style/style.js b/src/style/style.js index f943a2858ff..a46b2937134 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -34,11 +34,7 @@ import type {StyleImage} from './style_image'; import type {StyleGlyph} from './style_glyph'; import type CollisionIndex from '../symbol/collision_index'; import type {Callback} from '../types/callback'; -import type { - TransitionParameters, - EvaluationParameters -} from './properties'; - +import type EvaluationParameters from './evaluation_parameters'; const supportedDiffOperations = util.pick(diff.operations, [ 'addLayer', @@ -300,7 +296,7 @@ class Style extends Evented { /** * Apply queued style updates in a batch and recalculate zoom-dependent paint properties. */ - update(parameters: TransitionParameters & EvaluationParameters) { + update(parameters: EvaluationParameters) { if (!this._loaded) { return; } diff --git a/src/style/style_layer.js b/src/style/style_layer.js index d0b2b3bef8f..edb35dfdcd5 100644 --- a/src/style/style_layer.js +++ b/src/style/style_layer.js @@ -17,10 +17,8 @@ import type {Bucket} from '../data/bucket'; import type Point from '@mapbox/point-geometry'; import type RenderTexture from '../render/render_texture'; import type {FeatureFilter} from '../style-spec/feature_filter'; -import type { - TransitionParameters, - EvaluationParameters -} from './properties'; +import type {TransitionParameters} from './properties'; +import type EvaluationParameters from './evaluation_parameters'; const TRANSITION_SUFFIX = '-transition'; diff --git a/src/style/style_layer/fill_style_layer.js b/src/style/style_layer/fill_style_layer.js index d78ee259437..7ad4d89d637 100644 --- a/src/style/style_layer/fill_style_layer.js +++ b/src/style/style_layer/fill_style_layer.js @@ -15,7 +15,7 @@ const { import type {BucketParameters} from '../../data/bucket'; import type Point from '@mapbox/point-geometry'; import type {PaintProps} from './fill_style_layer_properties'; -import type {EvaluationParameters} from '../properties'; +import type EvaluationParameters from '../evaluation_parameters'; class FillStyleLayer extends StyleLayer { _transitionablePaint: Transitionable; diff --git a/src/style/style_layer/line_style_layer.js b/src/style/style_layer/line_style_layer.js index f51ca5660b9..d2e8c9f6fd8 100644 --- a/src/style/style_layer/line_style_layer.js +++ b/src/style/style_layer/line_style_layer.js @@ -18,7 +18,7 @@ const { import type {Bucket, BucketParameters} from '../../data/bucket'; import type {LayoutProps, PaintProps} from './line_style_layer_properties'; -import type {EvaluationParameters} from '../properties'; +import type EvaluationParameters from '../evaluation_parameters'; const lineFloorwidthProperty = new DataDrivenProperty(properties.paint.properties['line-width'].specification, true); diff --git a/src/style/style_layer/symbol_style_layer.js b/src/style/style_layer/symbol_style_layer.js index 27a2425c28a..b780628f42a 100644 --- a/src/style/style_layer/symbol_style_layer.js +++ b/src/style/style_layer/symbol_style_layer.js @@ -17,7 +17,7 @@ const { import type {BucketParameters} from '../../data/bucket'; import type {LayoutProps, PaintProps} from './symbol_style_layer_properties'; import type {Feature} from '../../style-spec/expression'; -import type {EvaluationParameters} from '../properties'; +import type EvaluationParameters from '../evaluation_parameters'; class SymbolStyleLayer extends StyleLayer { _unevaluatedLayout: Layout; diff --git a/src/symbol/symbol_layout.js b/src/symbol/symbol_layout.js index 6b386ef1595..00bc43e65f2 100644 --- a/src/symbol/symbol_layout.js +++ b/src/symbol/symbol_layout.js @@ -13,6 +13,7 @@ const findPoleOfInaccessibility = require('../util/find_pole_of_inaccessibility' const classifyRings = require('../util/classify_rings'); const EXTENT = require('../data/extent'); const SymbolBucket = require('../data/bucket/symbol_bucket'); +const EvaluationParameters = require('../style/evaluation_parameters'); import type {Shaping, PositionedIcon} from './shaping'; import type CollisionBoxArray from './collision_box'; @@ -74,22 +75,22 @@ function performSymbolLayout(bucket: SymbolBucket, if (bucket.textSizeData.functionType === 'composite') { const {min, max} = bucket.textSizeData.zoomRange; sizes.compositeTextSizes = [ - unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: min}), - unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: max}) + unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(min)), + unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(max)) ]; } if (bucket.iconSizeData.functionType === 'composite') { const {min, max} = bucket.iconSizeData.zoomRange; sizes.compositeIconSizes = [ - unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: min}), - unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: max}) + unevaluatedLayoutValues['icon-size'].possiblyEvaluate(new EvaluationParameters(min)), + unevaluatedLayoutValues['icon-size'].possiblyEvaluate(new EvaluationParameters(max)) ]; } - sizes.layoutTextSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: bucket.zoom + 1}); - sizes.layoutIconSize = unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: bucket.zoom + 1}); - sizes.textMaxSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: 18}); + sizes.layoutTextSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(bucket.zoom + 1)); + sizes.layoutIconSize = unevaluatedLayoutValues['icon-size'].possiblyEvaluate(new EvaluationParameters(bucket.zoom + 1)); + sizes.textMaxSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(18)); const oneEm = 24; const lineHeight = layout.get('text-line-height') * oneEm; diff --git a/src/ui/map.js b/src/ui/map.js index 4539d46492a..76ed974e63c 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -8,6 +8,7 @@ const DOM = require('../util/dom'); const ajax = require('../util/ajax'); const Style = require('../style/style'); +const EvaluationParameters = require('../style/evaluation_parameters'); const Painter = require('../render/painter'); const Transform = require('../geo/transform'); @@ -1458,22 +1459,24 @@ class Map extends Camera { this._updateEase(); } - // If the style has changed, the map is being zoomed, or a transition - // is in progress: + // If the style has changed, the map is being zoomed, or a transition or fade is in progress: // - Apply style changes (in a batch) - // - Recalculate zoom-dependent paint properties. + // - Recalculate paint properties. if (this.style && this._styleDirty) { this._styleDirty = false; + const zoom = this.transform.zoom; const now = browser.now(); this.style.zoomHistory.update(zoom, now); - this.style.update({ - zoom, + + const parameters = new EvaluationParameters(zoom, { now, fadeDuration: this._fadeDuration, zoomHistory: this.style.zoomHistory, transition: util.extend({ duration: 300, delay: 0 }, this.style.stylesheet.transition) }); + + this.style.update(parameters); } // If we are in _render for any reason other than an in-progress paint From 56173f3cc0bfa73b881cc55a91e3ca4d6ee97073 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 30 Nov 2017 12:10:43 -0800 Subject: [PATCH 6/7] Don't include _placementDirty in loaded() loaded() is an indication of data completeness, not rendering quiescence. Transitory effects (transitions, cross-fading, collision fading) are not included by design. --- src/ui/map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/map.js b/src/ui/map.js index 76ed974e63c..10472067dc3 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -1420,7 +1420,7 @@ class Map extends Camera { * @returns {boolean} A Boolean indicating whether the map is fully loaded. */ loaded() { - if (this._styleDirty || this._sourcesDirty || this._placementDirty) + if (this._styleDirty || this._sourcesDirty) return false; if (!this.style || !this.style.loaded()) return false; From 1ac4a0ce2409fbb323db8490b12e67a693ae8913 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 30 Nov 2017 12:10:43 -0800 Subject: [PATCH 7/7] Fix cross-fading of *-pattern and line-dasharray --- src/style/evaluation_parameters.js | 8 ++ src/style/properties.js | 3 +- src/ui/map.js | 12 ++- .../mapbox-gl-js#5740/expected.png | Bin 0 -> 11496 bytes .../regressions/mapbox-gl-js#5740/style.json | 72 ++++++++++++++++++ test/suite_implementation.js | 2 +- 6 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 test/integration/render-tests/regressions/mapbox-gl-js#5740/expected.png create mode 100644 test/integration/render-tests/regressions/mapbox-gl-js#5740/style.json diff --git a/src/style/evaluation_parameters.js b/src/style/evaluation_parameters.js index 26cc2d375c9..78c83e7f6ce 100644 --- a/src/style/evaluation_parameters.js +++ b/src/style/evaluation_parameters.js @@ -24,6 +24,14 @@ class EvaluationParameters { this.transition = {}; } } + + crossFadingFactor() { + if (this.fadeDuration === 0) { + return 1; + } else { + return Math.min((this.now - this.zoomHistory.lastIntegerZoomTime) / this.fadeDuration, 1); + } + } } module.exports = EvaluationParameters; diff --git a/src/style/properties.js b/src/style/properties.js index 0ba5774239e..44efd4e1ed2 100644 --- a/src/style/properties.js +++ b/src/style/properties.js @@ -607,8 +607,7 @@ class CrossFadedProperty implements Property> { _calculate(min: T, mid: T, max: T, parameters: EvaluationParameters): ?CrossFaded { const z = parameters.zoom; const fraction = z - Math.floor(z); - const d = parameters.fadeDuration; - const t = d !== 0 ? Math.min((parameters.now - parameters.zoomHistory.lastIntegerZoomTime) / d, 1) : 1; + const t = parameters.crossFadingFactor(); return z > parameters.zoomHistory.lastIntegerZoom ? { from: min, to: mid, fromScale: 2, toScale: 1, t: fraction + (1 - fraction) * t } : { from: max, to: mid, fromScale: 0.5, toScale: 1, t: 1 - (1 - t) * fraction }; diff --git a/src/ui/map.js b/src/ui/map.js index 10472067dc3..db970383bc5 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -243,6 +243,7 @@ class Map extends Camera { _hash: Hash; _delegatedListeners: any; _fadeDuration: number; + _crossFadingFactor: number; scrollZoom: ScrollZoomHandler; boxZoom: BoxZoomHandler; @@ -270,6 +271,7 @@ class Map extends Camera { this._bearingSnap = options.bearingSnap; this._refreshExpiredTiles = options.refreshExpiredTiles; this._fadeDuration = options.fadeDuration; + this._crossFadingFactor = 1; const transformRequestFn = options.transformRequest; this._transformRequest = transformRequestFn ? (url, type) => transformRequestFn(url, type) || ({ url }) : (url) => ({ url }); @@ -1459,6 +1461,8 @@ class Map extends Camera { this._updateEase(); } + let crossFading = false; + // If the style has changed, the map is being zoomed, or a transition or fade is in progress: // - Apply style changes (in a batch) // - Recalculate paint properties. @@ -1476,6 +1480,12 @@ class Map extends Camera { transition: util.extend({ duration: 300, delay: 0 }, this.style.stylesheet.transition) }); + const factor = parameters.crossFadingFactor(); + if (factor !== 1 || factor !== this._crossFadingFactor) { + crossFading = true; + this._crossFadingFactor = factor; + } + this.style.update(parameters); } @@ -1505,7 +1515,7 @@ class Map extends Camera { this.fire('load'); } - if (this.style && this.style.hasTransitions()) { + if (this.style && (this.style.hasTransitions() || crossFading)) { this._styleDirty = true; } diff --git a/test/integration/render-tests/regressions/mapbox-gl-js#5740/expected.png b/test/integration/render-tests/regressions/mapbox-gl-js#5740/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..c86291d37b329589cd6519931e9c194adb02fbdb GIT binary patch literal 11496 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