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 00000000000..743399c2c7c Binary files /dev/null and b/test/integration/render-tests/image-fallback-nested/circle/expected.png differ 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 00000000000..820b2ef40ae Binary files /dev/null and b/test/integration/render-tests/image-fallback-nested/feature-state-inside/expected.png differ diff --git a/test/integration/render-tests/image-fallback-nested/feature-state-inside/style.json b/test/integration/render-tests/image-fallback-nested/feature-state-inside/style.json new file mode 100644 index 00000000000..00daf72dc34 --- /dev/null +++ b/test/integration/render-tests/image-fallback-nested/feature-state-inside/style.json @@ -0,0 +1,134 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 64, + "height": 64, + "operations": [ + [ + "setFeatureState", + { + "source": "geojson", + "id": "0" + }, + { + "hover": true + } + ], + [ + "wait" + ], + [ + "setFeatureState", + { + "source": "geojson", + "id": "2" + }, + { + "hover": true + } + ], + [ + "wait" + ] + ] + } + }, + "zoom": 2, + "sprite": "local://sprites/sprite", + "sources": { + "geojson": { + "type": "geojson", + "data": + { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": 0, + "properties": { "icon": "fav-bicycle-18"}, + "geometry": { + "type": "Point", + "coordinates": [ + -2, + 2 + ] + } + }, + { + "type": "Feature", + "id": 1, + "properties": { "icon": "fav-bicycle-18"}, + "geometry": { + "type": "Point", + "coordinates": [ + 2, + 2 + ] + } + }, + { + "type": "Feature", + "id": 2, + "properties": { "icon": "missing-icon"}, + "geometry": { + "type": "Point", + "coordinates": [ + 2, + -2 + ] + } + }, + { + "type": "Feature", + "id": 3, + "properties": { "icon": "missing-icon"}, + "geometry": { + "type": "Point", + "coordinates": [ + -2, + -2 + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "circle", + "type": "circle", + "source": "geojson", + "paint": { + "circle-radius": 5, + "circle-color": + [ "case", + ["boolean", [ "feature-state", "hover"], false ], + [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ] + ] + ], + "yellow", + "green" + ], + [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ] + ] + ], + "red", + "blue" + ] + ] + } + } + ] +} diff --git a/test/integration/render-tests/image-fallback-nested/feature-state-outside/expected.png b/test/integration/render-tests/image-fallback-nested/feature-state-outside/expected.png new file mode 100644 index 00000000000..820b2ef40ae Binary files /dev/null and b/test/integration/render-tests/image-fallback-nested/feature-state-outside/expected.png differ diff --git a/test/integration/render-tests/image-fallback-nested/feature-state-outside/style.json b/test/integration/render-tests/image-fallback-nested/feature-state-outside/style.json new file mode 100644 index 00000000000..98f7b57aef5 --- /dev/null +++ b/test/integration/render-tests/image-fallback-nested/feature-state-outside/style.json @@ -0,0 +1,127 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 64, + "height": 64, + "operations": [ + [ + "setFeatureState", + { + "source": "geojson", + "id": "0" + }, + { + "hover": true + } + ], + [ + "wait" + ], + [ + "setFeatureState", + { + "source": "geojson", + "id": "2" + }, + { + "hover": true + } + ], + [ + "wait" + ] + ] + } + }, + "zoom": 2, + "sprite": "local://sprites/sprite", + "sources": { + "geojson": { + "type": "geojson", + "data": + { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": 0, + "properties": { "icon": "fav-bicycle-18"}, + "geometry": { + "type": "Point", + "coordinates": [ + -2, + 2 + ] + } + }, + { + "type": "Feature", + "id": 1, + "properties": { "icon": "fav-bicycle-18"}, + "geometry": { + "type": "Point", + "coordinates": [ + 2, + 2 + ] + } + }, + { + "type": "Feature", + "id": 2, + "properties": { "icon": "missing-icon"}, + "geometry": { + "type": "Point", + "coordinates": [ + 2, + -2 + ] + } + }, + { + "type": "Feature", + "id": 3, + "properties": { "icon": "missing-icon"}, + "geometry": { + "type": "Point", + "coordinates": [ + -2, + -2 + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "circle", + "type": "circle", + "source": "geojson", + "paint": { + "circle-radius": 5, + "circle-color": [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ] + ] + ], + [ "case", + ["boolean", [ "feature-state", "hover"], false ], + "yellow", + "red" + ], + [ "case", + ["boolean", [ "feature-state", "hover"], false ], + "green", + "blue" + ] + ] + } + } + ] +} diff --git a/test/integration/render-tests/image-fallback-nested/icon/expected.png b/test/integration/render-tests/image-fallback-nested/icon/expected.png new file mode 100644 index 00000000000..5232d5f8cfb Binary files /dev/null and b/test/integration/render-tests/image-fallback-nested/icon/expected.png differ 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 00000000000..4c9c0110c24 Binary files /dev/null and b/test/integration/render-tests/image-fallback-nested/line/expected.png differ 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 00000000000..ad51ebf12fa Binary files /dev/null and b/test/integration/render-tests/image-fallback-nested/text/expected.png differ 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 00000000000..64bc1adfda7 Binary files /dev/null and b/test/integration/render-tests/image-fallback-nested/zoom/expected.png differ diff --git a/test/integration/render-tests/image-fallback-nested/zoom/style.json b/test/integration/render-tests/image-fallback-nested/zoom/style.json new file mode 100644 index 00000000000..1ffe42adaff --- /dev/null +++ b/test/integration/render-tests/image-fallback-nested/zoom/style.json @@ -0,0 +1,70 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 64, + "height": 64 + } + }, + "zoom": 2, + "sprite": "local://sprites/sprite", + "sources": { + "geojson": { + "type": "geojson", + "data": + { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { "icon": "fav-bicycle-18"}, + "geometry": { + "type": "Point", + "coordinates": [ + -2, + 0 + ] + } + }, + { + "type": "Feature", + "properties": { "icon": "missing-icon"}, + "geometry": { + "type": "Point", + "coordinates": [ + 2, + 0 + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "circle", + "type": "circle", + "source": "geojson", + "paint": { + "circle-radius": 5, + "circle-color": [ + "step", + ["zoom"], + "black", + 1, + [ "case", + ["==", "missing", + ["to-string", ["coalesce", + ["image", ["get", "icon"]], + "missing" + ]] + ], + "red", + "blue" + ] + ] + } + } + ] +}