diff --git a/src/data/bucket.js b/src/data/bucket.js index bb80a2cdf49..86e3b44fcef 100644 --- a/src/data/bucket.js +++ b/src/data/bucket.js @@ -12,7 +12,8 @@ export type BucketParameters = { zoom: number, pixelRatio: number, overscaling: number, - collisionBoxArray: CollisionBoxArray + collisionBoxArray: CollisionBoxArray, + sourceLayerIndex: number } export type PopulateParameters = { diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index 50735204fa9..cc0c8934b63 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -47,6 +47,8 @@ export type CollisionArrays = { textBox?: SingleCollisionBox; iconBox?: SingleCollisionBox; textCircles?: Array; + textFeatureIndex?: number; + iconFeatureIndex?: number; }; export type SymbolFeature = {| @@ -283,6 +285,7 @@ class SymbolBucket implements Bucket { collisionBox: CollisionBuffers; collisionCircle: CollisionBuffers; uploaded: boolean; + sourceLayerIndex: number; constructor(options: BucketParameters) { this.collisionBoxArray = options.collisionBoxArray; @@ -292,6 +295,7 @@ class SymbolBucket implements Bucket { this.layerIds = this.layers.map(layer => layer.id); this.index = options.index; this.pixelRatio = options.pixelRatio; + this.sourceLayerIndex = options.sourceLayerIndex; const layer = this.layers[0]; const unevaluatedLayoutValues = layer._unevaluatedLayout._values; @@ -579,11 +583,12 @@ class SymbolBucket implements Bucket { const box: CollisionBox = (collisionBoxArray.get(k): any); if (box.radius === 0) { collisionArrays.textBox = { x1: box.x1, y1: box.y1, x2: box.x2, y2: box.y2, anchorPointX: box.anchorPointX, anchorPointY: box.anchorPointY }; - + collisionArrays.textFeatureIndex = box.featureIndex; break; // Only one box allowed per instance } else { if (!collisionArrays.textCircles) { collisionArrays.textCircles = []; + collisionArrays.textFeatureIndex = box.featureIndex; } const used = 1; // May be updated at collision detection time collisionArrays.textCircles.push(box.anchorPointX, box.anchorPointY, box.radius, box.signedDistanceFromAnchor, used); @@ -594,6 +599,7 @@ class SymbolBucket implements Bucket { const box: CollisionBox = (collisionBoxArray.get(k): any); if (box.radius === 0) { collisionArrays.iconBox = { x1: box.x1, y1: box.y1, x2: box.x2, y2: box.y2, anchorPointX: box.anchorPointX, anchorPointY: box.anchorPointY }; + collisionArrays.iconFeatureIndex = box.featureIndex; break; // Only one box allowed per instance } } diff --git a/src/data/feature_index.js b/src/data/feature_index.js index 18285a8f5c9..1cf23b23549 100644 --- a/src/data/feature_index.js +++ b/src/data/feature_index.js @@ -14,10 +14,8 @@ import { arraysIntersect } from '../util/util'; import { OverscaledTileID } from '../source/tile_id'; import { register } from '../util/web_worker_transfer'; -import type CollisionIndex from '../symbol/collision_index'; import type StyleLayer from '../style/style_layer'; import type {FeatureFilter} from '../style-spec/feature_filter'; -import type {CollisionBoxArray} from './array_types'; import type Transform from '../geo/transform'; import { FeatureIndexArray } from './array_types'; @@ -32,16 +30,11 @@ type QueryParameters = { params: { filter: FilterSpecification, layers: Array, - }, - collisionBoxArray: CollisionBoxArray, - sourceID: string, - bucketInstanceIds: { [number]: boolean }, - collisionIndex: ?CollisionIndex + } } class FeatureIndex { tileID: OverscaledTileID; - overscaling: number; x: number; y: number; z: number; @@ -55,11 +48,9 @@ class FeatureIndex { sourceLayerCoder: DictionaryCoder; constructor(tileID: OverscaledTileID, - overscaling: number, grid?: Grid, featureIndexArray?: FeatureIndexArray) { this.tileID = tileID; - this.overscaling = overscaling; this.x = tileID.canonical.x; this.y = tileID.canonical.y; this.z = tileID.canonical.z; @@ -92,15 +83,13 @@ class FeatureIndex { } } - // Finds features in this tile at a particular position. - query(args: QueryParameters, styleLayers: {[string]: StyleLayer}) { + // Finds non-symbol features in this tile at a particular position. + query(args: QueryParameters, styleLayers: {[string]: StyleLayer}): {[string]: Array<{ featureIndex: number, feature: GeoJSONFeature }>} { if (!this.vtLayers) { this.vtLayers = new vt.VectorTile(new Protobuf(this.rawTileData)).layers; this.sourceLayerCoder = new DictionaryCoder(this.vtLayers ? Object.keys(this.vtLayers).sort() : ['_geojsonTileLayer']); } - const result = {}; - const params = args.params || {}, pixelsToTileUnits = EXTENT / args.tileSize / args.scale, filter = featureFilter(params.filter); @@ -125,79 +114,112 @@ class FeatureIndex { const matching = this.grid.query(minX - queryPadding, minY - queryPadding, maxX + queryPadding, maxY + queryPadding); matching.sort(topDownFeatureComparator); - this.filterMatching(result, matching, this.featureIndexArray, queryGeometry, filter, params.layers, styleLayers, pixelsToTileUnits, args.posMatrix, args.transform); + const result = {}; + let previousIndex; + for (let k = 0; k < matching.length; k++) { + const index = matching[k]; + + // don't check the same feature more than once + if (index === previousIndex) continue; + previousIndex = index; - const matchingSymbols = args.collisionIndex ? - args.collisionIndex.queryRenderedSymbols(queryGeometry, this.tileID, args.tileSize / EXTENT, args.collisionBoxArray, args.sourceID, args.bucketInstanceIds) : - []; - matchingSymbols.sort(); - this.filterMatching(result, matchingSymbols, args.collisionBoxArray, queryGeometry, filter, params.layers, styleLayers, pixelsToTileUnits, args.posMatrix, args.transform); + const match = this.featureIndexArray.get(index); + let geometry = null; + this.generateGeoJSONFeature( + result, + match.bucketIndex, + match.sourceLayerIndex, + match.featureIndex, + filter, + params.layers, + styleLayers, + (feature: VectorTileFeature, styleLayer: StyleLayer) => { + if (!geometry) { + geometry = loadGeometry(feature); + } + return styleLayer.queryIntersectsFeature(queryGeometry, feature, geometry, this.z, args.transform, pixelsToTileUnits, args.posMatrix); + } + ); + } return result; } - filterMatching( + generateGeoJSONFeature( result: {[string]: Array<{ featureIndex: number, feature: GeoJSONFeature }>}, - matching: Array, - array: FeatureIndexArray | CollisionBoxArray, - queryGeometry: Array>, + bucketIndex: number, + sourceLayerIndex: number, + featureIndex: number, filter: FeatureFilter, filterLayerIDs: Array, styleLayers: {[string]: StyleLayer}, - pixelsToTileUnits: number, - posMatrix: Float32Array, - transform: Transform - ) { - let previousIndex; - for (let k = 0; k < matching.length; k++) { - const index = matching[k]; + intersectionTest?: (feature: VectorTileFeature, styleLayer: StyleLayer) => boolean) { - // don't check the same feature more than once - if (index === previousIndex) continue; - previousIndex = index; + const layerIDs = this.bucketLayerIDs[bucketIndex]; + if (filterLayerIDs && !arraysIntersect(filterLayerIDs, layerIDs)) + return; - const match = array.get(index); + const sourceLayerName = this.sourceLayerCoder.decode(sourceLayerIndex); + const sourceLayer = this.vtLayers[sourceLayerName]; + const feature = sourceLayer.feature(featureIndex); - const layerIDs = this.bucketLayerIDs[match.bucketIndex]; - if (filterLayerIDs && !arraysIntersect(filterLayerIDs, layerIDs)) continue; + if (!filter({zoom: this.tileID.overscaledZ}, feature)) + return; - const sourceLayerName = this.sourceLayerCoder.decode(match.sourceLayerIndex); - const sourceLayer = this.vtLayers[sourceLayerName]; - const feature = sourceLayer.feature(match.featureIndex); + for (let l = 0; l < layerIDs.length; l++) { + const layerID = layerIDs[l]; - if (!filter({zoom: this.tileID.overscaledZ}, feature)) continue; + if (filterLayerIDs && filterLayerIDs.indexOf(layerID) < 0) { + continue; + } - let geometry = null; + const styleLayer = styleLayers[layerID]; + if (!styleLayer) continue; - for (let l = 0; l < layerIDs.length; l++) { - const layerID = layerIDs[l]; + if (intersectionTest && !intersectionTest(feature, styleLayer)) { + // Only applied for non-symbol features + continue; + } - if (filterLayerIDs && filterLayerIDs.indexOf(layerID) < 0) { - continue; - } + const geojsonFeature = new GeoJSONFeature(feature, this.z, this.x, this.y); + (geojsonFeature: any).layer = styleLayer.serialize(); + let layerResult = result[layerID]; + if (layerResult === undefined) { + layerResult = result[layerID] = []; + } + layerResult.push({ featureIndex: featureIndex, feature: geojsonFeature }); + } + } + + // Given a set of symbol indexes that have already been looked up, + // return a matching set of GeoJSONFeatures + lookupSymbolFeatures(symbolFeatureIndexes: Array, + bucketIndex: number, + sourceLayerIndex: number, + filterSpec: FilterSpecification, + filterLayerIDs: Array, + styleLayers: {[string]: StyleLayer}) { + const result = {}; + if (!this.vtLayers) { + this.vtLayers = new vt.VectorTile(new Protobuf(this.rawTileData)).layers; + this.sourceLayerCoder = new DictionaryCoder(this.vtLayers ? Object.keys(this.vtLayers).sort() : ['_geojsonTileLayer']); + } - const styleLayer = styleLayers[layerID]; - if (!styleLayer) continue; + const filter = featureFilter(filterSpec); - if (styleLayer.type !== 'symbol') { - // all symbols already match the style - if (!geometry) { - geometry = loadGeometry(feature); - } - if (!styleLayer.queryIntersectsFeature(queryGeometry, feature, geometry, this.z, transform, pixelsToTileUnits, posMatrix)) { - continue; - } - } + for (const symbolFeatureIndex of symbolFeatureIndexes) { + this.generateGeoJSONFeature( + result, + bucketIndex, + sourceLayerIndex, + symbolFeatureIndex, + filter, + filterLayerIDs, + styleLayers + ); - const geojsonFeature = new GeoJSONFeature(feature, this.z, this.x, this.y); - (geojsonFeature: any).layer = styleLayer.serialize(); - let layerResult = result[layerID]; - if (layerResult === undefined) { - layerResult = result[layerID] = []; - } - layerResult.push({ featureIndex: index, feature: geojsonFeature }); - } } + return result; } hasLayer(id: string) { diff --git a/src/source/geojson_source.js b/src/source/geojson_source.js index 78cc1053eb5..b533c3ff612 100644 --- a/src/source/geojson_source.js +++ b/src/source/geojson_source.js @@ -238,7 +238,6 @@ class GeoJSONSource extends Evented implements Source { tileSize: this.tileSize, source: this.id, pixelRatio: browser.devicePixelRatio, - overscaling: tile.tileID.overscaleFactor(), showCollisionBoxes: this.map.showCollisionBoxes }; diff --git a/src/source/query_features.js b/src/source/query_features.js index 98854e13f7b..eaa6285e4df 100644 --- a/src/source/query_features.js +++ b/src/source/query_features.js @@ -5,13 +5,13 @@ import type StyleLayer from '../style/style_layer'; import type Coordinate from '../geo/coordinate'; import type CollisionIndex from '../symbol/collision_index'; import type Transform from '../geo/transform'; +import type { RetainedQueryData } from '../symbol/placement'; export function queryRenderedFeatures(sourceCache: SourceCache, styleLayers: {[string]: StyleLayer}, queryGeometry: Array, params: { filter: FilterSpecification, layers: Array }, - transform: Transform, - collisionIndex: ?CollisionIndex) { + transform: Transform) { const maxPitchScaleFactor = transform.maxPitchScaleFactor(); const tilesIn = sourceCache.tilesIn(queryGeometry, maxPitchScaleFactor); @@ -28,15 +28,44 @@ export function queryRenderedFeatures(sourceCache: SourceCache, params, transform, maxPitchScaleFactor, - sourceCache.transform.calculatePosMatrix(tileIn.tileID.toUnwrapped()), - sourceCache.id, - collisionIndex) + sourceCache.transform.calculatePosMatrix(tileIn.tileID.toUnwrapped())) }); } return mergeRenderedFeatureLayers(renderedFeatureLayers); } +export function queryRenderedSymbols(styleLayers: {[string]: StyleLayer}, + queryGeometry: Array, + params: { filter: FilterSpecification, layers: Array }, + collisionIndex: CollisionIndex, + retainedQueryData: {[number]: RetainedQueryData}) { + const result = {}; + const renderedSymbols = collisionIndex.queryRenderedSymbols(queryGeometry); + const bucketQueryData = []; + for (const bucketInstanceId of Object.keys(renderedSymbols).map(Number)) { + bucketQueryData.push(retainedQueryData[bucketInstanceId]); + } + bucketQueryData.sort(sortTilesIn); + + for (const queryData of bucketQueryData) { + const bucketSymbols = queryData.featureIndex.lookupSymbolFeatures( + renderedSymbols[queryData.bucketInstanceId], + queryData.bucketIndex, + queryData.sourceLayerIndex, + params.filter, + params.layers, + styleLayers); + for (const layerID in bucketSymbols) { + const resultFeatures = result[layerID] = result[layerID] || []; + for (const symbolFeature of bucketSymbols[layerID]) { + resultFeatures.push(symbolFeature.feature); + } + } + } + return result; +} + export function querySourceFeatures(sourceCache: SourceCache, params: any) { const tiles = sourceCache.getRenderableIds().map((id) => { return sourceCache.getTileByID(id); diff --git a/src/source/tile.js b/src/source/tile.js index f0feae19951..570e45db81a 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -7,7 +7,6 @@ import vt from '@mapbox/vector-tile'; import Protobuf from 'pbf'; import GeoJSONFeature from '../util/vectortile_to_geojson'; import featureFilter from '../style-spec/feature_filter'; -import CollisionIndex from '../symbol/collision_index'; import SymbolBucket from '../data/bucket/symbol_bucket'; import { RasterBoundsArray, CollisionBoxArray } from '../data/array_types'; import rasterBoundsAttributes from '../data/raster_bounds_attributes'; @@ -55,6 +54,8 @@ class Tile { uses: number; tileSize: number; buckets: {[string]: Bucket}; + latestFeatureIndex: ?FeatureIndex; + latestRawTileData: ?ArrayBuffer; iconAtlasImage: ?RGBAImage; iconAtlasTexture: Texture; glyphAtlasImage: ?AlphaImage; @@ -64,10 +65,7 @@ class Tile { state: TileState; timeAdded: any; fadeEndTime: any; - rawTileData: ArrayBuffer; collisionBoxArray: ?CollisionBoxArray; - collisionIndex: ?CollisionIndex; - featureIndex: ?FeatureIndex; redoWhenDone: boolean; showCollisionBoxes: boolean; placementSource: any; @@ -148,13 +146,20 @@ class Tile { return; } - if (data.rawTileData) { - // Only vector tiles have rawTileData - this.rawTileData = data.rawTileData; + if (data.featureIndex) { + this.latestFeatureIndex = data.featureIndex; + if (data.rawTileData) { + // Only vector tiles have rawTileData, and they won't update it for + // 'reloadTile' + this.latestRawTileData = data.rawTileData; + this.latestFeatureIndex.rawTileData = data.rawTileData; + } else if (this.latestRawTileData) { + // If rawTileData hasn't updated, hold onto a pointer to the last + // one we received + this.latestFeatureIndex.rawTileData = this.latestRawTileData; + } } this.collisionBoxArray = data.collisionBoxArray; - this.featureIndex = data.featureIndex; - this.featureIndex.rawTileData = this.rawTileData; this.buckets = deserializeBucket(data.buckets, painter.style); if (justReloaded) { @@ -198,8 +203,7 @@ class Tile { this.glyphAtlasTexture.destroy(); } - this.collisionBoxArray = null; - this.featureIndex = null; + this.latestFeatureIndex = null; this.state = 'unloaded'; } @@ -235,55 +239,40 @@ class Tile { } } + // Queries non-symbol features rendered for this tile. + // Symbol features are queried globally queryRenderedFeatures(layers: {[string]: StyleLayer}, queryGeometry: Array>, scale: number, params: { filter: FilterSpecification, layers: Array }, transform: Transform, maxPitchScaleFactor: number, - posMatrix: Float32Array, - sourceID: string, - collisionIndex: ?CollisionIndex): {[string]: Array<{ featureIndex: number, feature: GeoJSONFeature }>} { - if (!this.featureIndex || !this.collisionBoxArray) + posMatrix: Float32Array): {[string]: Array<{ featureIndex: number, feature: GeoJSONFeature }>} { + if (!this.latestFeatureIndex || !this.latestFeatureIndex.rawTileData) return {}; - // Create the set of the current bucket instance ids - const bucketInstanceIds = {}; - for (const id in layers) { - const bucket = this.getBucket(layers[id]); - if (bucket) { - // Add the bucket instance's id to the set of current ids. - // The query will only include results from current buckets. - if (bucket instanceof SymbolBucket && bucket.bucketInstanceId !== undefined) { - bucketInstanceIds[bucket.bucketInstanceId] = true; - } - } - } - - return this.featureIndex.query({ + return this.latestFeatureIndex.query({ queryGeometry: queryGeometry, scale: scale, tileSize: this.tileSize, posMatrix: posMatrix, transform: transform, params: params, - queryPadding: this.queryPadding * maxPitchScaleFactor, - collisionBoxArray: this.collisionBoxArray, - sourceID: sourceID, - collisionIndex: collisionIndex, - bucketInstanceIds: bucketInstanceIds + queryPadding: this.queryPadding * maxPitchScaleFactor }, layers); } querySourceFeatures(result: Array, params: any) { - if (!this.rawTileData) return; + if (!this.latestFeatureIndex || !this.latestFeatureIndex.rawTileData) return; - if (!this.vtLayers) { - this.vtLayers = new vt.VectorTile(new Protobuf(this.rawTileData)).layers; + if (!this.latestFeatureIndex.vtLayers) { + this.latestFeatureIndex.vtLayers = + new vt.VectorTile(new Protobuf(this.latestFeatureIndex.rawTileData)).layers; } + const vtLayers = this.latestFeatureIndex.vtLayers; const sourceLayer = params ? params.sourceLayer : ''; - const layer = this.vtLayers._geojsonTileLayer || this.vtLayers[sourceLayer]; + const layer = vtLayers._geojsonTileLayer || vtLayers[sourceLayer]; if (!layer) return; @@ -427,6 +416,7 @@ class Tile { } } } + } export default Tile; diff --git a/src/source/vector_tile_source.js b/src/source/vector_tile_source.js index 2583ed8da4a..daab7476184 100644 --- a/src/source/vector_tile_source.js +++ b/src/source/vector_tile_source.js @@ -93,18 +93,16 @@ class VectorTileSource extends Evented implements Source { } loadTile(tile: Tile, callback: Callback) { - const overscaling = tile.tileID.overscaleFactor(); const url = normalizeURL(tile.tileID.canonical.url(this.tiles, this.scheme), this.url); const params = { request: this.map._transformRequest(url, ResourceType.Tile), uid: tile.uid, tileID: tile.tileID, zoom: tile.tileID.overscaledZ, - tileSize: this.tileSize * overscaling, + tileSize: this.tileSize * tile.tileID.overscaleFactor(), type: this.type, source: this.id, pixelRatio: browser.devicePixelRatio, - overscaling: overscaling, showCollisionBoxes: this.map.showCollisionBoxes, }; params.request.collectResourceTiming = this._collectResourceTiming; diff --git a/src/source/worker_source.js b/src/source/worker_source.js index 713847976ce..7bf48f85ab5 100644 --- a/src/source/worker_source.js +++ b/src/source/worker_source.js @@ -21,7 +21,6 @@ export type WorkerTileParameters = TileParameters & { maxZoom: number, tileSize: number, pixelRatio: number, - overscaling: number, showCollisionBoxes: boolean, collectResourceTiming?: boolean }; diff --git a/src/source/worker_tile.js b/src/source/worker_tile.js index 0292511dea6..ded6f677767 100644 --- a/src/source/worker_tile.js +++ b/src/source/worker_tile.js @@ -50,7 +50,7 @@ class WorkerTile { this.pixelRatio = params.pixelRatio; this.tileSize = params.tileSize; this.source = params.source; - this.overscaling = params.overscaling; + this.overscaling = this.tileID.overscaleFactor(); this.showCollisionBoxes = params.showCollisionBoxes; this.collectResourceTiming = !!params.collectResourceTiming; } @@ -62,7 +62,7 @@ class WorkerTile { this.collisionBoxArray = new CollisionBoxArray(); const sourceLayerCoder = new DictionaryCoder(Object.keys(data.layers).sort()); - const featureIndex = new FeatureIndex(this.tileID, this.overscaling); + const featureIndex = new FeatureIndex(this.tileID); featureIndex.bucketLayerIDs = []; const buckets: {[string]: Bucket} = {}; @@ -108,7 +108,8 @@ class WorkerTile { zoom: this.zoom, pixelRatio: this.pixelRatio, overscaling: this.overscaling, - collisionBoxArray: this.collisionBoxArray + collisionBoxArray: this.collisionBoxArray, + sourceLayerIndex: sourceLayerIndex }); bucket.populate(features, options); diff --git a/src/style/pauseable_placement.js b/src/style/pauseable_placement.js index 531936e087c..9a7c8323602 100644 --- a/src/style/pauseable_placement.js +++ b/src/style/pauseable_placement.js @@ -2,7 +2,7 @@ import browser from '../util/browser'; -import Placement from '../symbol/placement'; +import { Placement } from '../symbol/placement'; import type Transform from '../geo/transform'; import type StyleLayer from './style_layer'; diff --git a/src/style/style.js b/src/style/style.js index efbc61c26a5..6b6ced41a0d 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -20,7 +20,7 @@ import { getType as getSourceType, setType as setSourceType } from '../source/source'; -import { queryRenderedFeatures, querySourceFeatures } from '../source/query_features'; +import { queryRenderedFeatures, queryRenderedSymbols, querySourceFeatures } from '../source/query_features'; import SourceCache from '../source/source_cache'; import GeoJSONSource from '../source/geojson_source'; import styleSpec from '../style-spec/reference/latest'; @@ -42,7 +42,7 @@ import type {StyleImage} from './style_image'; import type {StyleGlyph} from './style_glyph'; import type {Callback} from '../types/callback'; import type EvaluationParameters from './evaluation_parameters'; -import type Placement from '../symbol/placement'; +import type { Placement } from '../symbol/placement'; const supportedDiffOperations = pick(diffOperations, [ 'addLayer', @@ -832,8 +832,27 @@ class Style extends Evented { const sourceResults = []; for (const id in this.sourceCaches) { if (params.layers && !includedSources[id]) continue; - const results = queryRenderedFeatures(this.sourceCaches[id], this._layers, queryGeometry, params, transform, this.placement ? this.placement.collisionIndex : null); - sourceResults.push(results); + sourceResults.push( + queryRenderedFeatures( + this.sourceCaches[id], + this._layers, + queryGeometry.worldCoordinate, + params, + transform) + ); + } + + if (this.placement) { + // If a placement has run, query against its CollisionIndex + // for symbol results, and treat it as an extra source to merge + sourceResults.push( + queryRenderedSymbols( + this._layers, + queryGeometry.viewport, + params, + this.placement.collisionIndex, + this.placement.retainedQueryData) + ); } return this._flattenRenderedFeatures(sourceResults); } diff --git a/src/symbol/collision_index.js b/src/symbol/collision_index.js index 49326b01574..3aa0ad1aff7 100644 --- a/src/symbol/collision_index.js +++ b/src/symbol/collision_index.js @@ -11,10 +11,8 @@ const mat4 = glmatrix.mat4; import * as projection from '../symbol/projection'; import type Transform from '../geo/transform'; -import type {OverscaledTileID} from '../source/tile_id'; import type {SingleCollisionBox} from '../data/bucket/symbol_bucket'; import type { - CollisionBoxArray, GlyphOffsetArray, SymbolLineVertexArray } from '../data/array_types'; @@ -236,79 +234,42 @@ class CollisionIndex { /** * Because the geometries in the CollisionIndex are an approximation of the shape of * symbols on the map, we use the CollisionIndex to look up the symbol part of - * `queryRenderedFeatures`. Non-symbol features are looked up tile-by-tile, and - * historically collisions were handled per-tile. - * - * For this reason, `queryRenderedSymbols` still takes tile coordinate inputs and - * converts them back to viewport coordinates. The change to a viewport coordinate - * CollisionIndex means it's now possible to re-design queryRenderedSymbols to - * run entirely in viewport coordinates, saving unnecessary conversions. - * See https://github.com/mapbox/mapbox-gl-js/issues/5475 + * `queryRenderedFeatures`. * * @private */ - queryRenderedSymbols(queryGeometry: any, tileCoord: OverscaledTileID, textPixelRatio: number, collisionBoxArray: CollisionBoxArray, sourceID: string, bucketInstanceIds: {[number]: boolean}) { - const sourceLayerFeatures = {}; - const result = []; - - if (queryGeometry.length === 0 || (this.grid.keysLength() === 0 && this.ignoredGrid.keysLength() === 0)) { - return result; + queryRenderedSymbols(viewportQueryGeometry: Array) { + if (viewportQueryGeometry.length === 0 || (this.grid.keysLength() === 0 && this.ignoredGrid.keysLength() === 0)) { + return {}; } - const posMatrix = this.transform.calculatePosMatrix(tileCoord.toUnwrapped()); - const query = []; let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; - for (let i = 0; i < queryGeometry.length; i++) { - const ring = queryGeometry[i]; - for (let k = 0; k < ring.length; k++) { - const p = this.projectPoint(posMatrix, ring[k].x, ring[k].y); - minX = Math.min(minX, p.x); - minY = Math.min(minY, p.y); - maxX = Math.max(maxX, p.x); - maxY = Math.max(maxY, p.y); - query.push(p); - } + for (const point of viewportQueryGeometry) { + const gridPoint = new Point(point.x + viewportPadding, point.y + viewportPadding); + minX = Math.min(minX, gridPoint.x); + minY = Math.min(minY, gridPoint.y); + maxX = Math.max(maxX, gridPoint.x); + maxY = Math.max(maxY, gridPoint.y); + query.push(gridPoint); } - const tileID = tileCoord.key; - - const thisTileFeatures = []; - const features = this.grid.query(minX, minY, maxX, maxY); - for (let i = 0; i < features.length; i++) { - // Only include results from the matching source, tile and version of the bucket that was indexed - if (features[i].sourceID === sourceID && - features[i].tileID === tileID && - bucketInstanceIds[features[i].bucketInstanceId]) { - thisTileFeatures.push(features[i].boxIndex); - } - } - const ignoredFeatures = this.ignoredGrid.query(minX, minY, maxX, maxY); - for (let i = 0; i < ignoredFeatures.length; i++) { - if (ignoredFeatures[i].sourceID === sourceID && - ignoredFeatures[i].tileID === tileID && - bucketInstanceIds[ignoredFeatures[i].bucketInstanceId]) { - thisTileFeatures.push(ignoredFeatures[i].boxIndex); - } - } + const features = this.grid.query(minX, minY, maxX, maxY) + .concat(this.ignoredGrid.query(minX, minY, maxX, maxY)); - for (let i = 0; i < thisTileFeatures.length; i++) { - const blocking = collisionBoxArray.get(thisTileFeatures[i]); - const sourceLayer = blocking.sourceLayerIndex; - const featureIndex = blocking.featureIndex; - const bucketIndex = blocking.bucketIndex; + const seenFeatures = {}; + const result = {}; + for (const feature of features) { + const featureKey = feature.key; // Skip already seen features. - if (sourceLayerFeatures[sourceLayer] === undefined) { - sourceLayerFeatures[sourceLayer] = {}; + if (seenFeatures[featureKey.bucketInstanceId] === undefined) { + seenFeatures[featureKey.bucketInstanceId] = {}; } - if (sourceLayerFeatures[sourceLayer][featureIndex] === undefined) { - sourceLayerFeatures[sourceLayer][featureIndex] = {}; - } - if (sourceLayerFeatures[sourceLayer][featureIndex][bucketIndex]) { + if (seenFeatures[featureKey.bucketInstanceId][featureKey.featureIndex]) { continue; } @@ -317,41 +278,41 @@ class CollisionIndex { // Since there's no actual collision taking place, the circle vs. square // distinction doesn't matter as much, and box geometry is easier // to work with. - const projectedPoint = this.projectAndGetPerspectiveRatio(posMatrix, blocking.anchorPointX, blocking.anchorPointY); - const tileToViewport = textPixelRatio * projectedPoint.perspectiveRatio; - const x1 = blocking.x1 * tileToViewport + projectedPoint.point.x; - const y1 = blocking.y1 * tileToViewport + projectedPoint.point.y; - const x2 = blocking.x2 * tileToViewport + projectedPoint.point.x; - const y2 = blocking.y2 * tileToViewport + projectedPoint.point.y; const bbox = [ - new Point(x1, y1), - new Point(x2, y1), - new Point(x2, y2), - new Point(x1, y2) + new Point(feature.x1, feature.y1), + new Point(feature.x2, feature.y1), + new Point(feature.x2, feature.y2), + new Point(feature.x1, feature.y2) ]; if (!intersectionTests.polygonIntersectsPolygon(query, bbox)) { continue; } - sourceLayerFeatures[sourceLayer][featureIndex][bucketIndex] = true; - result.push(thisTileFeatures[i]); + seenFeatures[featureKey.bucketInstanceId][featureKey.featureIndex] = true; + if (result[featureKey.bucketInstanceId] === undefined) { + result[featureKey.bucketInstanceId] = []; + } + result[featureKey.bucketInstanceId].push(featureKey.featureIndex); + } + for (const bucket in result) { + result[bucket].sort(); } return result; } - insertCollisionBox(collisionBox: Array, ignorePlacement: boolean, tileID: number, sourceID: string, bucketInstanceId: number, boxStartIndex: number) { + insertCollisionBox(collisionBox: Array, ignorePlacement: boolean, bucketInstanceId: number, featureIndex: number) { const grid = ignorePlacement ? this.ignoredGrid : this.grid; - const key = { tileID: tileID, sourceID: sourceID, bucketInstanceId: bucketInstanceId, boxIndex: boxStartIndex }; + const key = { bucketInstanceId: bucketInstanceId, featureIndex: featureIndex }; grid.insert(key, collisionBox[0], collisionBox[1], collisionBox[2], collisionBox[3]); } - insertCollisionCircles(collisionCircles: Array, ignorePlacement: boolean, tileID: number, sourceID: string, bucketInstanceId: number, boxStartIndex: number) { + insertCollisionCircles(collisionCircles: Array, ignorePlacement: boolean, bucketInstanceId: number, featureIndex: number) { const grid = ignorePlacement ? this.ignoredGrid : this.grid; + const key = { bucketInstanceId: bucketInstanceId, featureIndex: featureIndex }; for (let k = 0; k < collisionCircles.length; k += 4) { - const key = { tileID: tileID, sourceID: sourceID, bucketInstanceId: bucketInstanceId, boxIndex: boxStartIndex + collisionCircles[k + 3] }; grid.insertCircle(key, collisionCircles[k], collisionCircles[k + 1], collisionCircles[k + 2]); } } diff --git a/src/symbol/cross_tile_symbol_index.js b/src/symbol/cross_tile_symbol_index.js index 3937e305837..4d7d3566d1c 100644 --- a/src/symbol/cross_tile_symbol_index.js +++ b/src/symbol/cross_tile_symbol_index.js @@ -212,11 +212,13 @@ class CrossTileSymbolIndex { layerIndexes: {[layerId: string]: CrossTileSymbolLayerIndex}; crossTileIDs: CrossTileIDs; maxBucketInstanceId: number; + bucketsInCurrentPlacement: {[number]: boolean}; constructor() { this.layerIndexes = {}; this.crossTileIDs = new CrossTileIDs(); this.maxBucketInstanceId = 0; + this.bucketsInCurrentPlacement = {}; } addLayer(styleLayer: StyleLayer, tiles: Array) { diff --git a/src/symbol/grid_index.js b/src/symbol/grid_index.js index 63f73b27011..811538a1006 100644 --- a/src/symbol/grid_index.js +++ b/src/symbol/grid_index.js @@ -91,12 +91,32 @@ class GridIndex { if (x2 < 0 || x1 > this.width || y2 < 0 || y1 > this.height) { return hitTest ? false : []; } - let result = []; + const result = []; if (x1 <= 0 && y1 <= 0 && this.width <= x2 && this.height <= y2) { - // We use `Array#slice` because `this.keys` may be a `Int32Array` and - // some browsers (Safari and IE) do not support `TypedArray#slice` - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice#Browser_compatibility - result = Array.prototype.slice.call(this.boxKeys).concat(this.circleKeys); + if (hitTest) { + return true; + } + for (let boxUid = 0; boxUid < this.boxKeys.length; boxUid++) { + result.push({ + key: this.boxKeys[boxUid], + x1: this.bboxes[boxUid * 4], + y1: this.bboxes[boxUid * 4 + 1], + x2: this.bboxes[boxUid * 4 + 2], + y2: this.bboxes[boxUid * 4 + 3] + }); + } + for (let circleUid = 0; circleUid < this.circleKeys.length; circleUid++) { + const x = this.circles[circleUid * 3]; + const y = this.circles[circleUid * 3 + 1]; + const radius = this.circles[circleUid * 3 + 2]; + result.push({ + key: this.circleKeys[circleUid], + x1: x - radius, + y1: y - radius, + x2: x + radius, + y2: y + radius + }); + } } else { const queryArgs = { hitTest, @@ -160,7 +180,13 @@ class GridIndex { result.push(true); return true; } else { - result.push(this.boxKeys[boxUid]); + result.push({ + key: this.boxKeys[boxUid], + x1: bboxes[offset], + y1: bboxes[offset + 1], + x2: bboxes[offset + 2], + y2: bboxes[offset + 3] + }); } } } @@ -185,7 +211,16 @@ class GridIndex { result.push(true); return true; } else { - result.push(this.circleKeys[circleUid]); + const x = circles[offset]; + const y = circles[offset + 1]; + const radius = circles[offset + 2]; + result.push({ + key: this.circleKeys[circleUid], + x1: x - radius, + y1: y - radius, + x2: x + radius, + y2: y + radius + }); } } } diff --git a/src/symbol/placement.js b/src/symbol/placement.js index dc1e3cee5ca..aa373dd5022 100644 --- a/src/symbol/placement.js +++ b/src/symbol/placement.js @@ -16,6 +16,8 @@ import type Tile from '../source/tile'; import type SymbolBucket from '../data/bucket/symbol_bucket'; import type mat4 from '@mapbox/gl-matrix'; import type {CollisionBoxArray, CollisionVertexArray} from '../data/array_types'; +import type FeatureIndex from '../data/feature_index'; +import type {OverscaledTileID} from '../source/tile_id'; class OpacityState { opacity: number; @@ -60,7 +62,27 @@ class JointPlacement { } } -class Placement { +export class RetainedQueryData { + bucketInstanceId: number; + featureIndex: FeatureIndex; + sourceLayerIndex: number; + bucketIndex: number; + tileID: OverscaledTileID; + + constructor(bucketInstanceId: number, + featureIndex: FeatureIndex, + sourceLayerIndex: number, + bucketIndex: number, + tileID: OverscaledTileID) { + this.bucketInstanceId = bucketInstanceId; + this.featureIndex = featureIndex; + this.sourceLayerIndex = sourceLayerIndex; + this.bucketIndex = bucketIndex; + this.tileID = tileID; + } +} + +export class Placement { transform: Transform; collisionIndex: CollisionIndex; placements: { [string | number]: JointPlacement }; @@ -69,6 +91,7 @@ class Placement { lastPlacementChangeTime: number; stale: boolean; fadeDuration: number; + retainedQueryData: {[number]: RetainedQueryData}; constructor(transform: Transform, fadeDuration: number) { this.transform = transform.clone(); @@ -77,11 +100,16 @@ class Placement { this.opacities = {}; this.stale = false; this.fadeDuration = fadeDuration; + this.retainedQueryData = {}; } placeLayerTile(styleLayer: StyleLayer, tile: Tile, showCollisionBoxes: boolean, seenCrossTileIDs: { [string | number]: boolean }) { const symbolBucket = ((tile.getBucket(styleLayer): any): SymbolBucket); - if (!symbolBucket) return; + const bucketFeatureIndex = tile.latestFeatureIndex; + if (!symbolBucket || !bucketFeatureIndex) + return; + + const collisionBoxArray = tile.collisionBoxArray; const layout = symbolBucket.layers[0].layout; @@ -102,14 +130,23 @@ class Placement { this.transform, pixelsToTileUnits(tile, 1, this.transform.zoom)); + // As long as this placement lives, we have to hold onto this bucket's + // matching FeatureIndex/data for querying purposes + this.retainedQueryData[symbolBucket.bucketInstanceId] = new RetainedQueryData( + symbolBucket.bucketInstanceId, + bucketFeatureIndex, + symbolBucket.sourceLayerIndex, + symbolBucket.index, + tile.tileID + ); + this.placeLayerBucket(symbolBucket, posMatrix, textLabelPlaneMatrix, iconLabelPlaneMatrix, scale, textPixelRatio, - showCollisionBoxes, seenCrossTileIDs, tile.collisionBoxArray, tile.tileID.key, styleLayer.source); + showCollisionBoxes, seenCrossTileIDs, collisionBoxArray); } placeLayerBucket(bucket: SymbolBucket, posMatrix: mat4, textLabelPlaneMatrix: mat4, iconLabelPlaneMatrix: mat4, scale: number, textPixelRatio: number, showCollisionBoxes: boolean, seenCrossTileIDs: { [string | number]: boolean }, - collisionBoxArray: ?CollisionBoxArray, tileKey: number, sourceID: string) { - + collisionBoxArray: ?CollisionBoxArray) { const layout = bucket.layers[0].layout; const partiallyEvaluatedTextSize = symbolSize.evaluateSizeForZoom(bucket.textSizeData, this.transform.zoom, symbolLayoutProperties.properties['text-size']); @@ -128,12 +165,18 @@ class Placement { let placedGlyphCircles = null; let placedIconBoxes = null; + let textFeatureIndex = 0; + let iconFeatureIndex = 0; + if (!symbolInstance.collisionArrays) { symbolInstance.collisionArrays = bucket.deserializeCollisionBoxes( ((collisionBoxArray: any): CollisionBoxArray), symbolInstance.textBoxStartIndex, symbolInstance.textBoxEndIndex, symbolInstance.iconBoxStartIndex, symbolInstance.iconBoxEndIndex); } + if (symbolInstance.collisionArrays.textFeatureIndex) { + textFeatureIndex = symbolInstance.collisionArrays.textFeatureIndex; + } if (symbolInstance.collisionArrays.textBox) { placedGlyphBoxes = this.collisionIndex.placeCollisionBox(symbolInstance.collisionArrays.textBox, layout.get('text-allow-overlap'), textPixelRatio, posMatrix); @@ -165,6 +208,9 @@ class Placement { offscreen = offscreen && placedGlyphCircles.offscreen; } + if (symbolInstance.collisionArrays.iconFeatureIndex) { + iconFeatureIndex = symbolInstance.collisionArrays.iconFeatureIndex; + } if (symbolInstance.collisionArrays.iconBox) { placedIconBoxes = this.collisionIndex.placeCollisionBox(symbolInstance.collisionArrays.iconBox, layout.get('icon-allow-overlap'), textPixelRatio, posMatrix); @@ -183,15 +229,15 @@ class Placement { if (placeText && placedGlyphBoxes) { this.collisionIndex.insertCollisionBox(placedGlyphBoxes.box, layout.get('text-ignore-placement'), - tileKey, sourceID, bucket.bucketInstanceId, symbolInstance.textBoxStartIndex); + bucket.bucketInstanceId, textFeatureIndex); } if (placeIcon && placedIconBoxes) { this.collisionIndex.insertCollisionBox(placedIconBoxes.box, layout.get('icon-ignore-placement'), - tileKey, sourceID, bucket.bucketInstanceId, symbolInstance.iconBoxStartIndex); + bucket.bucketInstanceId, iconFeatureIndex); } if (placeText && placedGlyphCircles) { this.collisionIndex.insertCollisionCircles(placedGlyphCircles.circles, layout.get('text-ignore-placement'), - tileKey, sourceID, bucket.bucketInstanceId, symbolInstance.textBoxStartIndex); + bucket.bucketInstanceId, textFeatureIndex); } assert(symbolInstance.crossTileID !== 0); @@ -259,7 +305,7 @@ class Placement { for (const tile of tiles) { const symbolBucket = ((tile.getBucket(styleLayer): any): SymbolBucket); - if (symbolBucket) { + if (symbolBucket && tile.latestFeatureIndex) { this.updateBucketOpacities(symbolBucket, seenCrossTileIDs, tile.collisionBoxArray); } } @@ -417,5 +463,3 @@ function packOpacity(opacityState: OpacityState): number { opacityBits * shift9 + targetBit * shift8 + opacityBits * shift1 + targetBit; } - -export default Placement; diff --git a/src/ui/map.js b/src/ui/map.js index f3a265df4f0..873b37e8d15 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -880,11 +880,12 @@ class Map extends Camera { ]; } - queryGeometry = queryGeometry.map((p) => { - return this.transform.pointCoordinate(p); - }); - - return queryGeometry; + return { + viewport: queryGeometry, + worldCoordinate: queryGeometry.map((p) => { + return this.transform.pointCoordinate(p); + }) + }; } /** diff --git a/test/unit/data/symbol_bucket.test.js b/test/unit/data/symbol_bucket.test.js index 22905c7426b..3ec8b6b583a 100644 --- a/test/unit/data/symbol_bucket.test.js +++ b/test/unit/data/symbol_bucket.test.js @@ -8,11 +8,12 @@ import { CollisionBoxArray } from '../../../src/data/array_types'; import SymbolStyleLayer from '../../../src/style/style_layer/symbol_style_layer'; import featureFilter from '../../../src/style-spec/feature_filter'; import { performSymbolLayout } from '../../../src/symbol/symbol_layout'; -import Placement from '../../../src/symbol/placement'; +import { Placement } from '../../../src/symbol/placement'; import Transform from '../../../src/geo/transform'; import { OverscaledTileID } from '../../../src/source/tile_id'; import Tile from '../../../src/source/tile'; import CrossTileSymbolIndex from '../../../src/symbol/cross_tile_symbol_index'; +import FeatureIndex from '../../../src/data/feature_index'; // Load a point feature from fixture tile. const vt = new VectorTile(new Protobuf(fs.readFileSync(path.join(__dirname, '/../../fixtures/mbsv5-6-18-23.vector.pbf')))); @@ -57,6 +58,7 @@ test('SymbolBucket', (t) => { bucketA.populate([{feature}], options); performSymbolLayout(bucketA, stacks, {}); const tileA = new Tile(tileID, 512); + tileA.latestFeatureIndex = new FeatureIndex(tileID); tileA.buckets = { test: bucketA }; tileA.collisionBoxArray = collisionBoxArray; diff --git a/test/unit/source/query_features.test.js b/test/unit/source/query_features.test.js index 8cafdaf79e1..93c32a09198 100644 --- a/test/unit/source/query_features.test.js +++ b/test/unit/source/query_features.test.js @@ -10,7 +10,7 @@ test('QueryFeatures#rendered', (t) => { t.test('returns empty object if source returns no tiles', (t) => { const mockSourceCache = { tilesIn: function () { return []; } }; const transform = new Transform(); - const result = queryRenderedFeatures(mockSourceCache, undefined, undefined, undefined, transform); + const result = queryRenderedFeatures(mockSourceCache, undefined, {}, undefined, transform); t.deepEqual(result, []); t.end(); }); diff --git a/test/unit/source/tile.test.js b/test/unit/source/tile.test.js index 7a06dd9f9f1..368e336080e 100644 --- a/test/unit/source/tile.test.js +++ b/test/unit/source/tile.test.js @@ -21,6 +21,7 @@ test('querySourceFeatures', (t) => { t.test('geojson tile', (t) => { const tile = new Tile(new OverscaledTileID(1, 0, 1, 1, 1)); + tile.latestFeatureIndex = new FeatureIndex(tile.tileID); let result; result = []; @@ -29,7 +30,7 @@ test('querySourceFeatures', (t) => { const geojsonWrapper = new GeoJSONWrapper(features); geojsonWrapper.name = '_geojsonTileLayer'; - tile.rawTileData = vtpbf({ layers: { '_geojsonTileLayer': geojsonWrapper }}); + tile.latestFeatureIndex.rawTileData = vtpbf({ layers: { '_geojsonTileLayer': geojsonWrapper }}); result = []; tile.querySourceFeatures(result); diff --git a/test/unit/style/style.test.js b/test/unit/style/style.test.js index a0576f4eaa9..d101aeaafbf 100644 --- a/test/unit/style/style.test.js +++ b/test/unit/style/style.test.js @@ -1910,7 +1910,7 @@ test('Style#query*Features', (t) => { }); t.test('queryRenderedFeatures emits an error on incorrect filter', (t) => { - t.deepEqual(style.queryRenderedFeatures([10, 100], {filter: 7}, transform), []); + t.deepEqual(style.queryRenderedFeatures({ worldCoordinate: [10, 100] }, {filter: 7}, transform), []); t.match(onError.args[0][0].error.message, /queryRenderedFeatures\.filter/); t.end(); }); diff --git a/test/unit/symbol/cross_tile_symbol_index.js b/test/unit/symbol/cross_tile_symbol_index.js index 3f6b64552d6..92d3e31dabb 100644 --- a/test/unit/symbol/cross_tile_symbol_index.js +++ b/test/unit/symbol/cross_tile_symbol_index.js @@ -20,7 +20,8 @@ function makeTile(tileID, symbolInstances) { }; return { tileID: tileID, - getBucket: () => bucket + getBucket: () => bucket, + latestFeatureIndex: {} }; } diff --git a/test/unit/symbol/grid_index.test.js b/test/unit/symbol/grid_index.test.js index 70a55f574c0..ff97d9b4d21 100644 --- a/test/unit/symbol/grid_index.test.js +++ b/test/unit/symbol/grid_index.test.js @@ -9,11 +9,11 @@ test('GridIndex', (t) => { grid.insert(1, 4, 10, 30, 12); grid.insert(2, -10, 30, 5, 35); - t.deepEqual(grid.query(4, 10, 5, 11).sort(), [0, 1]); - t.deepEqual(grid.query(24, 10, 25, 11).sort(), [1]); - t.deepEqual(grid.query(40, 40, 100, 100), []); - t.deepEqual(grid.query(-6, 0, 3, 100), [2]); - t.deepEqual(grid.query(-Infinity, -Infinity, Infinity, Infinity).sort(), [0, 1, 2]); + t.deepEqual(grid.query(4, 10, 5, 11).map(x => x.key).sort(), [0, 1]); + t.deepEqual(grid.query(24, 10, 25, 11).map(x => x.key).sort(), [1]); + t.deepEqual(grid.query(40, 40, 100, 100).map(x => x.key), []); + t.deepEqual(grid.query(-6, 0, 3, 100).map(x => x.key), [2]); + t.deepEqual(grid.query(-Infinity, -Infinity, Infinity, Infinity).map(x => x.key).sort(), [0, 1, 2]); t.end(); }); @@ -23,7 +23,7 @@ test('GridIndex', (t) => { grid.insert(key, 3, 3, 4, 4); grid.insert(key, 13, 13, 14, 14); grid.insert(key, 23, 23, 24, 24); - t.deepEqual(grid.query(0, 0, 30, 30), [key, key, key]); + t.deepEqual(grid.query(0, 0, 30, 30).map(x => x.key), [key, key, key]); t.end(); }); @@ -47,9 +47,9 @@ test('GridIndex', (t) => { grid.insertCircle(1, 60, 60, 15); grid.insertCircle(2, -10, 110, 20); - t.deepEqual(grid.query(45, 45, 55, 55), [0, 1]); - t.deepEqual(grid.query(0, 0, 30, 30), []); - t.deepEqual(grid.query(0, 80, 20, 100), [2]); + t.deepEqual(grid.query(45, 45, 55, 55).map(x => x.key), [0, 1]); + t.deepEqual(grid.query(0, 0, 30, 30).map(x => x.key), []); + t.deepEqual(grid.query(0, 80, 20, 100).map(x => x.key), [2]); t.end(); }); diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js index 6a20fb7107a..f2f7b45251b 100755 --- a/test/unit/ui/map.test.js +++ b/test/unit/ui/map.test.js @@ -868,7 +868,7 @@ test('Map', (t) => { const output = map.queryRenderedFeatures(map.project(new LngLat(0, 0))); const args = map.style.queryRenderedFeatures.getCall(0).args; - t.deepEqual(args[0].map(c => fixedCoord(c)), [{ column: 0.5, row: 0.5, zoom: 0 }]); // query geometry + t.deepEqual(args[0].worldCoordinate.map(c => fixedCoord(c)), [{ column: 0.5, row: 0.5, zoom: 0 }]); // query geometry t.deepEqual(args[1], {}); // params t.deepEqual(args[2], map.transform); // transform t.deepEqual(output, []); @@ -916,7 +916,7 @@ test('Map', (t) => { map.queryRenderedFeatures(map.project(new LngLat(360, 0))); - const coords = map.style.queryRenderedFeatures.getCall(0).args[0].map(c => fixedCoord(c)); + const coords = map.style.queryRenderedFeatures.getCall(0).args[0].worldCoordinate.map(c => fixedCoord(c)); t.equal(coords[0].column, 1.5); t.equal(coords[0].row, 0.5); t.equal(coords[0].zoom, 0);