From f95b8fab753b2ae29e92adda604fdee4ce9b7e11 Mon Sep 17 00:00:00 2001 From: Aidan H Date: Mon, 18 Oct 2021 12:39:43 -0600 Subject: [PATCH] Image fallback expressions within paint properties (#11049) * Adding tests for icon-color, line-color and text-color * Expanded tests to cover opacity, circle-radius and line-width * Adding zoom render test * Added tests for feature-state * Feature state fix * Added Flow type to painter * Shallow copy of style._availableImages * Updating benchmark to use images --- bench/benchmarks/symbol_layout.js | 1 + src/data/bucket.js | 2 +- src/data/bucket/circle_bucket.js | 10 +- src/data/bucket/fill_bucket.js | 14 +- src/data/bucket/fill_extrusion_bucket.js | 14 +- src/data/bucket/line_bucket.js | 14 +- src/data/bucket/symbol_bucket.js | 9 +- src/data/program_configuration.js | 43 +++--- src/source/source_state.js | 3 +- src/source/tile.js | 9 +- src/source/worker.js | 8 +- src/source/worker_tile.js | 6 +- src/style/style.js | 5 +- src/symbol/symbol_layout.js | 24 +++- .../image-fallback-nested/circle/expected.png | Bin 0 -> 672 bytes .../image-fallback-nested/circle/style.json | 83 +++++++++++ .../feature-state-inside/expected.png | Bin 0 -> 472 bytes .../feature-state-inside/style.json | 134 ++++++++++++++++++ .../feature-state-outside/expected.png | Bin 0 -> 472 bytes .../feature-state-outside/style.json | 127 +++++++++++++++++ .../image-fallback-nested/icon/expected.png | Bin 0 -> 3050 bytes .../image-fallback-nested/icon/style.json | 112 +++++++++++++++ .../image-fallback-nested/line/expected.png | Bin 0 -> 289 bytes .../image-fallback-nested/line/style.json | 88 ++++++++++++ .../image-fallback-nested/text/expected.png | Bin 0 -> 5546 bytes .../image-fallback-nested/text/style.json | 95 +++++++++++++ .../image-fallback-nested/zoom/expected.png | Bin 0 -> 226 bytes .../image-fallback-nested/zoom/style.json | 70 +++++++++ 28 files changed, 799 insertions(+), 72 deletions(-) create mode 100644 test/integration/render-tests/image-fallback-nested/circle/expected.png create mode 100644 test/integration/render-tests/image-fallback-nested/circle/style.json create mode 100644 test/integration/render-tests/image-fallback-nested/feature-state-inside/expected.png create mode 100644 test/integration/render-tests/image-fallback-nested/feature-state-inside/style.json create mode 100644 test/integration/render-tests/image-fallback-nested/feature-state-outside/expected.png create mode 100644 test/integration/render-tests/image-fallback-nested/feature-state-outside/style.json create mode 100644 test/integration/render-tests/image-fallback-nested/icon/expected.png create mode 100644 test/integration/render-tests/image-fallback-nested/icon/style.json create mode 100644 test/integration/render-tests/image-fallback-nested/line/expected.png create mode 100644 test/integration/render-tests/image-fallback-nested/line/style.json create mode 100644 test/integration/render-tests/image-fallback-nested/text/expected.png create mode 100644 test/integration/render-tests/image-fallback-nested/text/style.json create mode 100644 test/integration/render-tests/image-fallback-nested/zoom/expected.png create mode 100644 test/integration/render-tests/image-fallback-nested/zoom/style.json diff --git a/bench/benchmarks/symbol_layout.js b/bench/benchmarks/symbol_layout.js index 73e767a7239..d03ee8b4979 100644 --- a/bench/benchmarks/symbol_layout.js +++ b/bench/benchmarks/symbol_layout.js @@ -38,6 +38,7 @@ export default class SymbolLayout extends Layout { tileResult.iconMap, tileResult.imageAtlas.iconPositions, false, + this.parser.style.listImages(), tileResult.tileID.canonical, tileResult.tileZoom); } diff --git a/src/data/bucket.js b/src/data/bucket.js index b2c5c1a47d0..b286d445c07 100644 --- a/src/data/bucket.js +++ b/src/data/bucket.js @@ -79,7 +79,7 @@ export interface Bucket { +stateDependentLayers: Array; +stateDependentLayerIds: Array; populate(features: Array, options: PopulateParameters, canonical: CanonicalTileID): void; - update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[_: string]: ImagePosition}): void; + update(states: FeatureStates, vtLayer: VectorTileLayer, availableImages: Array, imagePositions: {[_: string]: ImagePosition}): void; isEmpty(): boolean; upload(context: Context): void; diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index b350d64f0ff..6afe789757a 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -123,14 +123,14 @@ class CircleBucket implements Bucke const {geometry, index, sourceLayerIndex} = bucketFeature; const feature = features[index].feature; - this.addFeature(bucketFeature, geometry, index, canonical); + this.addFeature(bucketFeature, geometry, index, options.availableImages, canonical); options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); } } - update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[_: string]: ImagePosition}) { + update(states: FeatureStates, vtLayer: VectorTileLayer, availableImages: Array, imagePositions: {[_: string]: ImagePosition}) { if (!this.stateDependentLayers.length) return; - this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); + this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, availableImages, imagePositions); } isEmpty() { @@ -158,7 +158,7 @@ class CircleBucket implements Bucke this.segments.destroy(); } - addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID) { + addFeature(feature: BucketFeature, geometry: Array>, index: number, availableImages: Array, canonical: CanonicalTileID) { for (const ring of geometry) { for (const point of ring) { const x = point.x; @@ -192,7 +192,7 @@ class CircleBucket implements Bucke } } - this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, {}, canonical); + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, {}, availableImages, canonical); } } diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index bf73ff00393..2c2e6da9b62 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -120,7 +120,7 @@ class FillBucket implements Bucket { // so are stored during populate until later updated with positions by tile worker in addFeatures this.patternFeatures.push(patternFeature); } else { - this.addFeature(bucketFeature, geometry, index, canonical, {}); + this.addFeature(bucketFeature, geometry, index, canonical, {}, options.availableImages); } const feature = features[index].feature; @@ -128,14 +128,14 @@ class FillBucket implements Bucket { } } - update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[_: string]: ImagePosition}) { + update(states: FeatureStates, vtLayer: VectorTileLayer, availableImages: Array, imagePositions: {[_: string]: ImagePosition}) { if (!this.stateDependentLayers.length) return; - this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); + this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, availableImages, imagePositions); } - addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { + addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}, availableImages: Array) { for (const feature of this.patternFeatures) { - this.addFeature(feature, feature.geometry, feature.index, canonical, imagePositions); + this.addFeature(feature, feature.geometry, feature.index, canonical, imagePositions, availableImages); } } @@ -166,7 +166,7 @@ class FillBucket implements Bucket { this.segments2.destroy(); } - addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { + addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}, availableImages: Array = []) { for (const polygon of classifyRings(geometry, EARCUT_MAX_RINGS)) { let numVertices = 0; for (const ring of polygon) { @@ -220,7 +220,7 @@ class FillBucket implements Bucket { triangleSegment.vertexLength += numVertices; triangleSegment.primitiveLength += indices.length / 3; } - this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, canonical); + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, availableImages, canonical); } } diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index 703a86c91de..16505a40d6c 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -244,7 +244,7 @@ class FillExtrusionBucket implements Bucket { if (this.hasPattern) { this.features.push(addPatternDependencies('fill-extrusion', this.layers, bucketFeature, this.zoom, options)); } else { - this.addFeature(bucketFeature, bucketFeature.geometry, index, canonical, {}); + this.addFeature(bucketFeature, bucketFeature.geometry, index, canonical, {}, options.availableImages); } options.featureIndex.insert(feature, bucketFeature.geometry, index, sourceLayerIndex, this.index, vertexArrayOffset); @@ -252,17 +252,17 @@ class FillExtrusionBucket implements Bucket { this.sortBorders(); } - addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { + addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}, availableImages: Array) { for (const feature of this.features) { const {geometry} = feature; - this.addFeature(feature, geometry, feature.index, canonical, imagePositions); + this.addFeature(feature, geometry, feature.index, canonical, imagePositions, availableImages); } this.sortBorders(); } - update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[_: string]: ImagePosition}) { + update(states: FeatureStates, vtLayer: VectorTileLayer, availableImages: Array, imagePositions: {[_: string]: ImagePosition}) { if (!this.stateDependentLayers.length) return; - this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); + this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, availableImages, imagePositions); } isEmpty() { @@ -301,7 +301,7 @@ class FillExtrusionBucket implements Bucket { this.segments.destroy(); } - addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { + addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}, availableImages: Array) { const metadata = this.enableTerrain ? new PartMetadata() : null; for (const polygon of classifyRings(geometry, EARCUT_MAX_RINGS)) { @@ -432,7 +432,7 @@ class FillExtrusionBucket implements Bucket { assert(!this.centroidVertexArray.length || this.centroidVertexArray.length === this.layoutVertexArray.length); } - this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, canonical); + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, availableImages, canonical); } sortBorders() { diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 4dfd299598c..f63758a9eae 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -187,7 +187,7 @@ class LineBucket implements Bucket { this.patternFeatures.push(patternBucketFeature); } else { - this.addFeature(bucketFeature, geometry, index, canonical, lineAtlas.positions); + this.addFeature(bucketFeature, geometry, index, canonical, lineAtlas.positions, options.availableImages); } const feature = features[index].feature; @@ -266,14 +266,14 @@ class LineBucket implements Bucket { } - update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[_: string]: ImagePosition}) { + update(states: FeatureStates, vtLayer: VectorTileLayer, availableImages: Array, imagePositions: {[_: string]: ImagePosition}) { if (!this.stateDependentLayers.length) return; - this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); + this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, availableImages, imagePositions); } - addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { + addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}, availableImages: Array) { for (const feature of this.patternFeatures) { - this.addFeature(feature, feature.geometry, feature.index, canonical, imagePositions); + this.addFeature(feature, feature.geometry, feature.index, canonical, imagePositions, availableImages); } } @@ -313,7 +313,7 @@ class LineBucket implements Bucket { } } - addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { + addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}, availableImages: Array) { const layout = this.layers[0].layout; const join = layout.get('line-join').evaluate(feature, {}); const cap = layout.get('line-cap').evaluate(feature, {}); @@ -325,7 +325,7 @@ class LineBucket implements Bucket { this.addLine(line, feature, join, cap, miterLimit, roundLimit); } - this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, canonical); + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, availableImages, canonical); } addLine(vertices: Array, feature: BucketFeature, join: string, cap: string, miterLimit: number, roundLimit: number) { diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index b7e836e7792..50cc769f276 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -553,10 +553,10 @@ class SymbolBucket implements Bucket { } } - update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[_: string]: ImagePosition}) { + update(states: FeatureStates, vtLayer: VectorTileLayer, availableImages: Array, imagePositions: {[_: string]: ImagePosition}) { if (!this.stateDependentLayers.length) return; - this.text.programConfigurations.updatePaintArrays(states, vtLayer, this.layers, imagePositions); - this.icon.programConfigurations.updatePaintArrays(states, vtLayer, this.layers, imagePositions); + this.text.programConfigurations.updatePaintArrays(states, vtLayer, this.layers, availableImages, imagePositions); + this.icon.programConfigurations.updatePaintArrays(states, vtLayer, this.layers, availableImages, imagePositions); } isEmpty() { @@ -634,6 +634,7 @@ class SymbolBucket implements Bucket { lineStartIndex: number, lineLength: number, associatedIconIndex: number, + availableImages: Array, canonical: CanonicalTileID) { const indexArray = arrays.indexArray; const layoutVertexArray = arrays.layoutVertexArray; @@ -667,7 +668,7 @@ class SymbolBucket implements Bucket { this.glyphOffsetArray.emplaceBack(glyphOffset[0]); if (i === quads.length - 1 || sectionIndex !== quads[i + 1].sectionIndex) { - arrays.programConfigurations.populatePaintArrays(layoutVertexArray.length, feature, feature.index, {}, canonical, sections && sections[sectionIndex]); + arrays.programConfigurations.populatePaintArrays(layoutVertexArray.length, feature, feature.index, {}, availableImages, canonical, sections && sections[sectionIndex]); } } diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index dc8c0602ee2..f4605127351 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -80,8 +80,8 @@ function packColor(color: Color): [number, number] { */ interface AttributeBinder { - populatePaintArray(length: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, canonical?: CanonicalTileID, formattedSection?: FormattedSection): void; - updatePaintArray(start: number, length: number, feature: Feature, featureState: FeatureState, imagePositions: {[_: string]: ImagePosition}): void; + populatePaintArray(length: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, availableImages: Array, canonical?: CanonicalTileID, formattedSection?: FormattedSection): void; + updatePaintArray(start: number, length: number, feature: Feature, featureState: FeatureState, availableImages: Array, imagePositions: {[_: string]: ImagePosition}): void; upload(Context): void; destroy(): void; } @@ -174,15 +174,16 @@ class SourceExpressionBinder implements AttributeBinder { this.paintVertexArray = new PaintVertexArray(); } - populatePaintArray(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, canonical?: CanonicalTileID, formattedSection?: FormattedSection) { + populatePaintArray(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, availableImages: Array, canonical?: CanonicalTileID, formattedSection?: FormattedSection) { const start = this.paintVertexArray.length; - const value = this.expression.evaluate(new EvaluationParameters(0), feature, {}, canonical, [], formattedSection); + assert(Array.isArray(availableImages)); + const value = this.expression.evaluate(new EvaluationParameters(0), feature, {}, canonical, availableImages, formattedSection); this.paintVertexArray.resize(newLength); this._setPaintValue(start, newLength, value); } - updatePaintArray(start: number, end: number, feature: Feature, featureState: FeatureState) { - const value = this.expression.evaluate({zoom: 0}, feature, featureState); + updatePaintArray(start: number, end: number, feature: Feature, featureState: FeatureState, availableImages: Array) { + const value = this.expression.evaluate({zoom: 0}, feature, featureState, undefined, availableImages); this._setPaintValue(start, end, value); } @@ -245,17 +246,17 @@ class CompositeExpressionBinder implements AttributeBinder, UniformBinder { this.paintVertexArray = new PaintVertexArray(); } - populatePaintArray(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, canonical?: CanonicalTileID, formattedSection?: FormattedSection) { - const min = this.expression.evaluate(new EvaluationParameters(this.zoom), feature, {}, canonical, [], formattedSection); - const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1), feature, {}, canonical, [], formattedSection); + populatePaintArray(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, availableImages: Array, canonical?: CanonicalTileID, formattedSection?: FormattedSection) { + const min = this.expression.evaluate(new EvaluationParameters(this.zoom), feature, {}, canonical, availableImages, formattedSection); + const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1), feature, {}, canonical, availableImages, formattedSection); const start = this.paintVertexArray.length; this.paintVertexArray.resize(newLength); this._setPaintValue(start, newLength, min, max); } - updatePaintArray(start: number, end: number, feature: Feature, featureState: FeatureState) { - const min = this.expression.evaluate({zoom: this.zoom}, feature, featureState); - const max = this.expression.evaluate({zoom: this.zoom + 1}, feature, featureState); + updatePaintArray(start: number, end: number, feature: Feature, featureState: FeatureState, availableImages: Array) { + const min = this.expression.evaluate({zoom: this.zoom}, feature, featureState, undefined, availableImages); + const max = this.expression.evaluate({zoom: this.zoom + 1}, feature, featureState, undefined, availableImages); this._setPaintValue(start, end, min, max); } @@ -337,7 +338,7 @@ class CrossFadedCompositeBinder implements AttributeBinder { this._setPaintValues(start, length, feature.patterns && feature.patterns[this.layerId], imagePositions); } - updatePaintArray(start: number, end: number, feature: Feature, featureState: FeatureState, imagePositions: {[_: string]: ImagePosition}) { + updatePaintArray(start: number, end: number, feature: Feature, featureState: FeatureState, availableImages: Array, imagePositions: {[_: string]: ImagePosition}) { this._setPaintValues(start, end, feature.patterns && feature.patterns[this.layerId], imagePositions); } @@ -455,11 +456,11 @@ export default class ProgramConfiguration { return binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder ? binder.maxValue : 0; } - populatePaintArrays(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, canonical?: CanonicalTileID, formattedSection?: FormattedSection) { + populatePaintArrays(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, availableImages: Array, canonical?: CanonicalTileID, formattedSection?: FormattedSection) { for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder || binder instanceof CrossFadedCompositeBinder) - (binder: AttributeBinder).populatePaintArray(newLength, feature, imagePositions, canonical, formattedSection); + (binder: AttributeBinder).populatePaintArray(newLength, feature, imagePositions, availableImages, canonical, formattedSection); } } setConstantPatternPositions(posTo: ImagePosition, posFrom: ImagePosition) { @@ -470,7 +471,7 @@ export default class ProgramConfiguration { } } - updatePaintArrays(featureStates: FeatureStates, featureMap: FeaturePositionMap, vtLayer: VectorTileLayer, layer: TypedStyleLayer, imagePositions: {[_: string]: ImagePosition}): boolean { + updatePaintArrays(featureStates: FeatureStates, featureMap: FeaturePositionMap, vtLayer: VectorTileLayer, layer: TypedStyleLayer, availableImages: Array, imagePositions: {[_: string]: ImagePosition}): boolean { let dirty: boolean = false; for (const id in featureStates) { const positions = featureMap.getPositions(id); @@ -485,7 +486,7 @@ export default class ProgramConfiguration { //AHM: Remove after https://github.com/mapbox/mapbox-gl-js/issues/6255 const value = layer.paint.get(property); (binder: any).expression = value.value; - (binder: AttributeBinder).updatePaintArray(pos.start, pos.end, feature, featureStates[id], imagePositions); + (binder: AttributeBinder).updatePaintArray(pos.start, pos.end, feature, featureStates[id], availableImages, imagePositions); dirty = true; } } @@ -608,9 +609,9 @@ export class ProgramConfigurationSet { this._bufferOffset = 0; } - populatePaintArrays(length: number, feature: Feature, index: number, imagePositions: {[_: string]: ImagePosition}, canonical: CanonicalTileID, formattedSection?: FormattedSection) { + populatePaintArrays(length: number, feature: Feature, index: number, imagePositions: {[_: string]: ImagePosition}, availableImages: Array, canonical: CanonicalTileID, formattedSection?: FormattedSection) { for (const key in this.programConfigurations) { - this.programConfigurations[key].populatePaintArrays(length, feature, imagePositions, canonical, formattedSection); + this.programConfigurations[key].populatePaintArrays(length, feature, imagePositions, availableImages, canonical, formattedSection); } if (feature.id !== undefined) { @@ -621,9 +622,9 @@ export class ProgramConfigurationSet { this.needsUpload = true; } - updatePaintArrays(featureStates: FeatureStates, vtLayer: VectorTileLayer, layers: $ReadOnlyArray, imagePositions: {[_: string]: ImagePosition}) { + updatePaintArrays(featureStates: FeatureStates, vtLayer: VectorTileLayer, layers: $ReadOnlyArray, availableImages: Array, imagePositions: {[_: string]: ImagePosition}) { for (const layer of layers) { - this.needsUpload = this.programConfigurations[layer.id].updatePaintArrays(featureStates, this._featureMap, vtLayer, layer, imagePositions) || this.needsUpload; + this.needsUpload = this.programConfigurations[layer.id].updatePaintArrays(featureStates, this._featureMap, vtLayer, layer, availableImages, imagePositions) || this.needsUpload; } } diff --git a/src/source/source_state.js b/src/source/source_state.js index f41fea5783c..ceb78178b7e 100644 --- a/src/source/source_state.js +++ b/src/source/source_state.js @@ -3,6 +3,7 @@ import {extend} from '../util/util.js'; import Tile from './tile.js'; import type {FeatureState} from '../style-spec/expression/index.js'; +import type Painter from '../render/painter.js'; export type FeatureStates = {[feature_id: string]: FeatureState}; export type LayerFeatureStates = {[layer: string]: FeatureStates}; @@ -99,7 +100,7 @@ class SourceFeatureState { return reconciledState; } - initializeTileState(tile: Tile, painter: any) { + initializeTileState(tile: Tile, painter: ?Painter) { tile.setFeatureState(this.state, painter); } diff --git a/src/source/tile.js b/src/source/tile.js index a29dc1629ee..e72b38db13d 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -36,6 +36,7 @@ import type {LayerFeatureStates} from './source_state.js'; import type {Cancelable} from '../types/cancelable.js'; import type {FilterSpecification} from '../style-spec/types.js'; import type {TilespaceQueryGeometry} from '../style/query_geometry.js'; +import type Painter from '../render/painter.js'; export type TileState = | 'loading' // Tile data is in the process of loading. @@ -436,14 +437,16 @@ class Tile { } } - setFeatureState(states: LayerFeatureStates, painter: any) { + setFeatureState(states: LayerFeatureStates, painter: ?Painter) { if (!this.latestFeatureIndex || !this.latestFeatureIndex.rawTileData || - Object.keys(states).length === 0) { + Object.keys(states).length === 0 || + !painter) { return; } const vtLayers = this.latestFeatureIndex.loadVTLayers(); + const availableImages = painter.style.listImages(); for (const id in this.buckets) { if (!painter.style.hasLayer(id)) continue; @@ -455,7 +458,7 @@ class Tile { const sourceLayerStates = states[sourceLayerId]; if (!sourceLayer || !sourceLayerStates || Object.keys(sourceLayerStates).length === 0) continue; - bucket.update(sourceLayerStates, sourceLayer, this.imageAtlas && this.imageAtlas.patternPositions || {}); + bucket.update(sourceLayerStates, sourceLayer, availableImages, this.imageAtlas && this.imageAtlas.patternPositions || {}); const layer = painter && painter.style && painter.style.getLayer(id); if (layer) { this.queryPadding = Math.max(this.queryPadding, layer.queryRadius(bucket)); diff --git a/src/source/worker.js b/src/source/worker.js index 621ee02ff49..f78f6bd70c4 100644 --- a/src/source/worker.js +++ b/src/source/worker.js @@ -38,7 +38,7 @@ export default class Worker { workerSourceTypes: {[_: string]: Class }; workerSources: {[_: string]: {[_: string]: {[_: string]: WorkerSource } } }; demWorkerSources: {[_: string]: {[_: string]: RasterDEMTileWorkerSource } }; - isSpriteLoaded: boolean; + isSpriteLoaded: {[_: string]: boolean }; referrer: ?string; terrain: ?boolean; @@ -49,7 +49,7 @@ export default class Worker { this.layerIndexes = {}; this.availableImages = {}; - this.isSpriteLoaded = false; + this.isSpriteLoaded = {}; this.workerSourceTypes = { vector: VectorTileWorkerSource, @@ -96,7 +96,7 @@ export default class Worker { } spriteLoaded(mapId: string, bool: boolean) { - this.isSpriteLoaded = bool; + this.isSpriteLoaded[mapId] = bool; for (const workerSource in this.workerSources[mapId]) { const ws = this.workerSources[mapId][workerSource]; for (const source in ws) { @@ -248,7 +248,7 @@ export default class Worker { }, scheduler: this.actor.scheduler }; - this.workerSources[mapId][type][source] = new (this.workerSourceTypes[type]: any)((actor: any), this.getLayerIndex(mapId), this.getAvailableImages(mapId), this.isSpriteLoaded); + this.workerSources[mapId][type][source] = new (this.workerSourceTypes[type]: any)((actor: any), this.getLayerIndex(mapId), this.getAvailableImages(mapId), this.isSpriteLoaded[mapId]); } return this.workerSources[mapId][type][source]; diff --git a/src/source/worker_tile.js b/src/source/worker_tile.js index 4831ddb792f..948fa6b68bf 100644 --- a/src/source/worker_tile.js +++ b/src/source/worker_tile.js @@ -152,7 +152,8 @@ class WorkerTile { collisionBoxArray: this.collisionBoxArray, sourceLayerIndex, sourceID: this.source, - enableTerrain: this.enableTerrain + enableTerrain: this.enableTerrain, + availableImages }); bucket.populate(features, options, this.tileID.canonical); @@ -229,6 +230,7 @@ class WorkerTile { iconMap, imageAtlas.iconPositions, this.showCollisionBoxes, + availableImages, this.tileID.canonical, this.tileZoom); } else if (bucket.hasPattern && @@ -236,7 +238,7 @@ class WorkerTile { bucket instanceof FillBucket || bucket instanceof FillExtrusionBucket)) { recalculateLayers(bucket.layers, this.zoom, availableImages); - bucket.addFeatures(options, this.tileID.canonical, imageAtlas.patternPositions); + bucket.addFeatures(options, this.tileID.canonical, imageAtlas.patternPositions, availableImages); } } diff --git a/src/style/style.js b/src/style/style.js index c7c4e9737ef..ca5a4cb4e10 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -652,10 +652,9 @@ class Style extends Evented { this.fire(new Event('data', {dataType: 'style'})); } - listImages() { + listImages(): Array { this._checkLoaded(); - - return this.imageManager.listImages(); + return this._availableImages.slice(); } addSource(id: string, source: SourceSpecification, options: StyleSetterOptions = {}) { diff --git a/src/symbol/symbol_layout.js b/src/symbol/symbol_layout.js index 850d5676ab6..b888b6739e7 100644 --- a/src/symbol/symbol_layout.js +++ b/src/symbol/symbol_layout.js @@ -153,6 +153,7 @@ export function performSymbolLayout(bucket: SymbolBucket, imageMap: {[_: string]: StyleImage}, imagePositions: {[_: string]: ImagePosition}, showCollisionBoxes: boolean, + availableImages: Array, canonical: CanonicalTileID, tileZoom: number) { bucket.createArrays(); @@ -315,7 +316,7 @@ export function performSymbolLayout(bucket: SymbolBucket, bucket.iconsInText = shapedText ? shapedText.iconsInText : false; } if (shapedText || shapedIcon) { - addFeature(bucket, feature, shapedTextOrientations, shapedIcon, imageMap, sizes, layoutTextSize, layoutIconSize, textOffset, isSDFIcon, canonical); + addFeature(bucket, feature, shapedTextOrientations, shapedIcon, imageMap, sizes, layoutTextSize, layoutIconSize, textOffset, isSDFIcon, availableImages, canonical); } } @@ -355,7 +356,9 @@ function addFeature(bucket: SymbolBucket, layoutTextSize: number, layoutIconSize: number, textOffset: [number, number], - isSDFIcon: boolean, canonical: CanonicalTileID) { + isSDFIcon: boolean, + availableImages: Array, + canonical: CanonicalTileID) { // 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 @@ -407,7 +410,7 @@ function addFeature(bucket: SymbolBucket, bucket.collisionBoxArray, feature.index, feature.sourceLayerIndex, bucket.index, textPadding, textAlongLine, textOffset, iconBoxScale, iconPadding, iconAlongLine, iconOffset, - feature, sizes, isSDFIcon, canonical); + feature, sizes, isSDFIcon, availableImages, canonical); }; if (symbolPlacement === 'line') { @@ -486,6 +489,7 @@ function addTextVertices(bucket: SymbolBucket, placedTextSymbolIndices: {[_: string]: number}, placedIconIndex: number, sizes: Sizes, + availableImages: Array, canonical: CanonicalTileID) { const glyphQuads = getGlyphQuads(anchor, shapedText, textOffset, layer, textAlongLine, feature, imageMap, bucket.allowVerticalPlacement); @@ -523,6 +527,7 @@ function addTextVertices(bucket: SymbolBucket, lineArray.lineStartIndex, lineArray.lineLength, placedIconIndex, + availableImages, canonical); // The placedSymbolArray is used at render time in drawTileSymbols @@ -643,6 +648,7 @@ function addSymbol(bucket: SymbolBucket, feature: SymbolFeature, sizes: Sizes, isSDFIcon: boolean, + availableImages: Array, canonical: CanonicalTileID) { const lineArray = bucket.addToLineVertexArray(anchor, line); @@ -729,7 +735,9 @@ function addSymbol(bucket: SymbolBucket, lineArray.lineStartIndex, lineArray.lineLength, // The icon itself does not have an associated symbol since the text isnt placed yet - -1, canonical); + -1, + availableImages, + canonical); placedIconSymbolIndex = bucket.icon.placedSymbolArray.length - 1; @@ -749,7 +757,9 @@ function addSymbol(bucket: SymbolBucket, lineArray.lineStartIndex, lineArray.lineLength, // The icon itself does not have an associated symbol since the text isnt placed yet - -1, canonical); + -1, + availableImages, + canonical); verticalPlacedIconSymbolIndex = bucket.icon.placedSymbolArray.length - 1; } @@ -775,7 +785,7 @@ function addSymbol(bucket: SymbolBucket, bucket, projectedAnchor, anchor, shaping, imageMap, layer, textAlongLine, feature, textOffset, lineArray, shapedTextOrientations.vertical ? WritingMode.horizontal : WritingMode.horizontalOnly, singleLine ? (Object.keys(shapedTextOrientations.horizontal): any) : [justification], - placedTextSymbolIndices, placedIconSymbolIndex, sizes, canonical); + placedTextSymbolIndices, placedIconSymbolIndex, sizes, availableImages, canonical); if (singleLine) { break; @@ -785,7 +795,7 @@ function addSymbol(bucket: SymbolBucket, if (shapedTextOrientations.vertical) { numVerticalGlyphVertices += addTextVertices( bucket, projectedAnchor, anchor, shapedTextOrientations.vertical, imageMap, layer, textAlongLine, feature, - textOffset, lineArray, WritingMode.vertical, ['vertical'], placedTextSymbolIndices, verticalPlacedIconSymbolIndex, sizes, canonical); + textOffset, lineArray, WritingMode.vertical, ['vertical'], placedTextSymbolIndices, verticalPlacedIconSymbolIndex, sizes, availableImages, canonical); } // Check if runtime collision circles should be used for any of the collision features. diff --git a/test/integration/render-tests/image-fallback-nested/circle/expected.png b/test/integration/render-tests/image-fallback-nested/circle/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..743399c2c7cf07c7c4a94612ab309f64395ec795 GIT binary patch literal 672 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU`qFNaSW-L^Y-pRug3`@M?VHf z&sZt4I$)xfaYyvgRgaZ-d=-s3X z#fy3I&ukgC-+IGfHCdjWo7GAy-^L7U%n(T+F9o@&Yxq>ES_LoRh5#?5XgOC zri1kl6NNvQU##31@;aR%OmsePM*oD{4FCV#UV4#Pk+132$#mY9dNEtouunT0StZ>4 zJGbr$PX1wi@$$kcQPUbe>^>SFdY0W)?B>=Bf%p75>g{(`ha59Ld0f@WUaZ}w_PPG4 zeSCJOvuEuEMFt9Z!Qjp8J$+i;v(n7Y_rJf#%Kh1^x_(8q*uv#HZ=gzEy&Pb*ve z+qG4PS9k9ej{Pgdy>0jx= zf5N?O1XlU$%#Enh7W_AR>Esu6fy~~)-u~H#>NlD6z7saxye0SLu}kasp=fS?83{1OQ0|8I%A3 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/image-fallback-nested/circle/style.json b/test/integration/render-tests/image-fallback-nested/circle/style.json new file mode 100644 index 00000000000..7a309997db5 --- /dev/null +++ b/test/integration/render-tests/image-fallback-nested/circle/style.json @@ -0,0 +1,83 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sprite": "local://sprites/sprite", + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { "icon": "fav-bicycle-18"}, + "geometry": { + "type": "Point", + "coordinates": [ + 0, + 16 + ] + } + }, + { + "type": "Feature", + "properties": { "icon": "missing-icon"}, + "geometry": { + "type": "Point", + "coordinates": [ + 0, + -16 + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "text", + "type": "circle", + "source": "geojson", + "paint": { + "circle-color": [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + "red", + "blue" + ], + "circle-radius": [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + 8, + 16 + ], + "circle-opacity": [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + 1, + 0.2 + ] + } + } + ] +} diff --git a/test/integration/render-tests/image-fallback-nested/feature-state-inside/expected.png b/test/integration/render-tests/image-fallback-nested/feature-state-inside/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..820b2ef40ae43e9c8c1dabee9420ed3b60288548 GIT binary patch literal 472 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 A@c;k- literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/image-fallback-nested/icon/style.json b/test/integration/render-tests/image-fallback-nested/icon/style.json new file mode 100644 index 00000000000..6b3d01c0bbf --- /dev/null +++ b/test/integration/render-tests/image-fallback-nested/icon/style.json @@ -0,0 +1,112 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sprite": "local://sprites/sprite", + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { "icon": "fav-bicycle-18"}, + "geometry": { + "type": "Point", + "coordinates": [ + 0, + 16 + ] + } + }, + { + "type": "Feature", + "properties": { "icon": "missing-icon"}, + "geometry": { + "type": "Point", + "coordinates": [ + 0, + -16 + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "geojson", + "layout": { + "text-allow-overlap": true, + "icon-allow-overlap": true, + "icon-image": "dot.sdf", + "text-field": ["to-string", + ["coalesce", + ["image", ["get", "icon"]], + "no icon found" + ] + ], + + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-offset": [0, 0.6], + "text-anchor": "top" + }, + "paint": { + "icon-color": [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + "red", + "blue" + ], + "text-color": [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + "red", + "blue" + ], + "icon-opacity": [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + 1, + 0.2 + ], + "text-opacity": [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + 1, + 0.2 + ] + } + } + ] +} diff --git a/test/integration/render-tests/image-fallback-nested/line/expected.png b/test/integration/render-tests/image-fallback-nested/line/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..4c9c0110c24be1fb5be815124f496bbdf8828bcf GIT binary patch literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSFFaiwLn`LHy~N1L9LU3Z(1AC^ zYvDiB&_njZ?B45)1(++XnSsiXK*8eAw<_;EsVSE;U)e6=utqM3VPlmCro!7n{ZX-F|!pLF28B-}i?9=S4G!q2a3DN^TodCa>LV OK~kQselF{r5}E*^%2we3 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/image-fallback-nested/line/style.json b/test/integration/render-tests/image-fallback-nested/line/style.json new file mode 100644 index 00000000000..b6fc6dace13 --- /dev/null +++ b/test/integration/render-tests/image-fallback-nested/line/style.json @@ -0,0 +1,88 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sprite": "local://sprites/sprite", + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { "icon": "fav-bicycle-18"}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ -16, 16], + [16, 16] + ] + } + }, + { + "type": "Feature", + "properties": { "icon": "missing-icon"}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ -16, -16], + [16, -16] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "text", + "type": "line", + "source": "geojson", + "layout": { + }, + "paint": { + "line-color": + [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + "red", + "blue" + ], + "line-width": + [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + 10, + 20 + ], + "line-opacity": + [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + 1, + 0.2 + ] + } + } + ] +} diff --git a/test/integration/render-tests/image-fallback-nested/text/expected.png b/test/integration/render-tests/image-fallback-nested/text/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..ad51ebf12fab0f782fc63d6ac5c98b1f06681172 GIT binary patch literal 5546 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 literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/image-fallback-nested/text/style.json b/test/integration/render-tests/image-fallback-nested/text/style.json new file mode 100644 index 00000000000..f5ee0d116ad --- /dev/null +++ b/test/integration/render-tests/image-fallback-nested/text/style.json @@ -0,0 +1,95 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 128, + "width": 128 + } + }, + "center": [ 0, 0 ], + "zoom": 0, + "sprite": "local://sprites/sprite", + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { "icon": "fav-bicycle-18"}, + "geometry": { + "type": "Point", + "coordinates": [ + 0, + 16 + ] + } + }, + { + "type": "Feature", + "properties": { "icon": "missing-icon"}, + "geometry": { + "type": "Point", + "coordinates": [ + 0, + -16 + ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "text", + "type": "symbol", + "source": "geojson", + "layout": { + "text-allow-overlap": true, + "icon-allow-overlap": true, + "icon-image": [ + "coalesce", + ["image", ["get", "icon"]], + ["image", "park"] + ], + "text-field": ["to-string", + ["coalesce", + ["image", ["get", "icon"]], + "no icon found" + ] + ], + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-offset": [0, 0.6], + "text-anchor": "top" + }, + "paint": { + "text-color": [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + "red", + "blue" + ], + "text-opacity": [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + 1, + 0.2 + ] + } + } + ] +} diff --git a/test/integration/render-tests/image-fallback-nested/zoom/expected.png b/test/integration/render-tests/image-fallback-nested/zoom/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..64bc1adfda7bdb223f4715355cdff4e9335e052e GIT binary patch literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=D?MEtLn`LHy=KkVY#`!t@pj;n zG~+dmvu3$o*eqw}?r)kY*p>8fN5>J{Nw+87_xRVK;yDR|mi#ty3Om75Y3kh?p1`-J zabswnv!nvM%Ji*&=J1?0H7Q_e6SK}&_}hN)eD*58wwKWb3-T>Jm*%uGeS4L1gPS!+ zSAFG_y#Wpici)D*@LJgTsz`cM!*Th>