diff --git a/src/data/feature_index.js b/src/data/feature_index.js index fed6484564b..ea015e69976 100644 --- a/src/data/feature_index.js +++ b/src/data/feature_index.js @@ -17,15 +17,17 @@ 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'; const {FeatureIndexArray} = require('./array_types'); type QueryParameters = { scale: number, - bearing: number, + posMatrix: Float32Array, + transform: Transform, tileSize: number, queryGeometry: Array>, - additionalRadius: number, + queryPadding: number, params: { filter: FilterSpecification, layers: Array, @@ -98,7 +100,7 @@ class FeatureIndex { filter = featureFilter(params.filter); const queryGeometry = args.queryGeometry; - const additionalRadius = args.additionalRadius * pixelsToTileUnits; + const queryPadding = args.queryPadding * pixelsToTileUnits; let minX = Infinity; let minY = Infinity; @@ -115,15 +117,15 @@ class FeatureIndex { } } - const matching = this.grid.query(minX - additionalRadius, minY - additionalRadius, maxX + additionalRadius, maxY + additionalRadius); + 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, args.bearing, pixelsToTileUnits); + this.filterMatching(result, matching, this.featureIndexArray, queryGeometry, filter, params.layers, styleLayers, pixelsToTileUnits, args.posMatrix, args.transform); 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, args.bearing, pixelsToTileUnits); + this.filterMatching(result, matchingSymbols, args.collisionBoxArray, queryGeometry, filter, params.layers, styleLayers, pixelsToTileUnits, args.posMatrix, args.transform); return result; } @@ -136,8 +138,9 @@ class FeatureIndex { filter: FeatureFilter, filterLayerIDs: Array, styleLayers: {[string]: StyleLayer}, - bearing: number, - pixelsToTileUnits: number + pixelsToTileUnits: number, + posMatrix: Float32Array, + transform: Transform ) { let previousIndex; for (let k = 0; k < matching.length; k++) { @@ -175,7 +178,7 @@ class FeatureIndex { if (!geometry) { geometry = loadGeometry(feature); } - if (!styleLayer.queryIntersectsFeature(queryGeometry, feature, geometry, this.z, bearing, pixelsToTileUnits)) { + if (!styleLayer.queryIntersectsFeature(queryGeometry, feature, geometry, this.z, transform, pixelsToTileUnits, posMatrix)) { continue; } } diff --git a/src/geo/transform.js b/src/geo/transform.js index b3e3e62563a..4d486251553 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -559,6 +559,16 @@ class Transform { this._posMatrixCache = {}; this._alignedPosMatrixCache = {}; } + + maxPitchScaleFactor() { + // calcMatrices hasn't run yet + if (!this.pixelMatrixInverse) return 1; + + const coord = this.pointCoordinate(new Point(0, 0)).zoomTo(this.zoom); + const p = [coord.column * this.tileSize, coord.row * this.tileSize, 0, 1]; + const topPoint = vec4.transformMat4(p, p, this.pixelMatrix); + return topPoint[3] / this.cameraToCenterDistance; + } } module.exports = Transform; diff --git a/src/source/query_features.js b/src/source/query_features.js index 9744d9f6bf8..129832b0650 100644 --- a/src/source/query_features.js +++ b/src/source/query_features.js @@ -4,15 +4,16 @@ import type SourceCache from './source_cache'; 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'; exports.rendered = function(sourceCache: SourceCache, styleLayers: {[string]: StyleLayer}, queryGeometry: Array, params: { filter: FilterSpecification, layers: Array }, - zoom: number, - bearing: number, + transform: Transform, collisionIndex: ?CollisionIndex) { - const tilesIn = sourceCache.tilesIn(queryGeometry); + const maxPitchScaleFactor = transform.maxPitchScaleFactor(); + const tilesIn = sourceCache.tilesIn(queryGeometry, maxPitchScaleFactor); tilesIn.sort(sortTilesIn); @@ -25,7 +26,9 @@ exports.rendered = function(sourceCache: SourceCache, tileIn.queryGeometry, tileIn.scale, params, - bearing, + transform, + maxPitchScaleFactor, + sourceCache.transform.calculatePosMatrix(tileIn.tileID.toUnwrapped()), sourceCache.id, collisionIndex) }); diff --git a/src/source/source_cache.js b/src/source/source_cache.js index 0c97e2fcc79..030eb578a91 100644 --- a/src/source/source_cache.js +++ b/src/source/source_cache.js @@ -682,7 +682,7 @@ class SourceCache extends Evented { * @param queryGeometry coordinates of the corners of bounding rectangle * @returns {Array} result items have {tile, minX, maxX, minY, maxY}, where min/max bounding values are the given bounds transformed in into the coordinate space of this tile. */ - tilesIn(queryGeometry: Array) { + tilesIn(queryGeometry: Array, maxPitchScaleFactor: number) { const tileResults = []; const ids = this.getIds(); @@ -704,14 +704,16 @@ class SourceCache extends Evented { for (let i = 0; i < ids.length; i++) { const tile = this._tiles[ids[i]]; const tileID = tile.tileID; + const scale = Math.pow(2, this.transform.zoom - tile.tileID.overscaledZ); + const queryPadding = maxPitchScaleFactor * tile.queryPadding * EXTENT / tile.tileSize / scale; const tileSpaceBounds = [ coordinateToTilePoint(tileID, new Coordinate(minX, minY, z)), coordinateToTilePoint(tileID, new Coordinate(maxX, maxY, z)) ]; - if (tileSpaceBounds[0].x < EXTENT && tileSpaceBounds[0].y < EXTENT && - tileSpaceBounds[1].x >= 0 && tileSpaceBounds[1].y >= 0) { + if (tileSpaceBounds[0].x - queryPadding < EXTENT && tileSpaceBounds[0].y - queryPadding < EXTENT && + tileSpaceBounds[1].x + queryPadding >= 0 && tileSpaceBounds[1].y + queryPadding >= 0) { const tileSpaceQueryGeometry = []; for (let j = 0; j < queryGeometry.length; j++) { @@ -722,7 +724,7 @@ class SourceCache extends Evented { tile: tile, tileID: tileID, queryGeometry: [tileSpaceQueryGeometry], - scale: Math.pow(2, this.transform.zoom - tile.tileID.overscaledZ) + scale: scale }); } } diff --git a/src/source/tile.js b/src/source/tile.js index e67efa40609..6dec5ba64ad 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -35,6 +35,7 @@ import type VertexBuffer from '../gl/vertex_buffer'; import type {OverscaledTileID} from './tile_id'; import type Framebuffer from '../gl/framebuffer'; import type {PerformanceResourceTiming} from '../types/performance_resource_timing'; +import type Transform from '../geo/transform'; export type TileState = | 'loading' // Tile data is in the process of loading. @@ -91,6 +92,7 @@ class Tile { refreshedUponExpiration: boolean; reloadCallback: any; resourceTiming: ?Array; + queryPadding: number; /** * @param {OverscaledTileID} tileID @@ -103,6 +105,7 @@ class Tile { this.tileSize = size; this.buckets = {}; this.expirationTime = null; + this.queryPadding = 0; // Counts the number of times a response was already expired when // received. We're using this to add a delay when making a new request @@ -166,6 +169,12 @@ class Tile { } } + this.queryPadding = 0; + for (const id in this.buckets) { + const bucket = this.buckets[id]; + this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(bucket.layerIds[0]).queryRadius(bucket)); + } + if (data.iconAtlasImage) { this.iconAtlasImage = data.iconAtlasImage; } @@ -233,20 +242,19 @@ class Tile { queryGeometry: Array>, scale: number, params: { filter: FilterSpecification, layers: Array }, - bearing: number, + transform: Transform, + maxPitchScaleFactor: number, + posMatrix: Float32Array, sourceID: string, collisionIndex: ?CollisionIndex): {[string]: Array<{ featureIndex: number, feature: GeoJSONFeature }>} { if (!this.featureIndex || !this.collisionBoxArray) return {}; - // Determine the additional radius needed factoring in property functions - let additionalRadius = 0; + // Create the set of the current bucket instance ids const bucketInstanceIds = {}; for (const id in layers) { const bucket = this.getBucket(layers[id]); if (bucket) { - additionalRadius = Math.max(additionalRadius, layers[id].queryRadius(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) { @@ -259,9 +267,10 @@ class Tile { queryGeometry: queryGeometry, scale: scale, tileSize: this.tileSize, - bearing: bearing, + posMatrix: posMatrix, + transform: transform, params: params, - additionalRadius: additionalRadius, + queryPadding: this.queryPadding * maxPitchScaleFactor, collisionBoxArray: this.collisionBoxArray, sourceID: sourceID, collisionIndex: collisionIndex, diff --git a/src/style/style.js b/src/style/style.js index 62c8191b1dc..8e257b0020e 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -796,7 +796,7 @@ class Style extends Evented { return features; } - queryRenderedFeatures(queryGeometry: any, params: any, zoom: number, bearing: number) { + queryRenderedFeatures(queryGeometry: any, params: any, transform: Transform) { if (params && params.filter) { this._validate(validateStyle.filter, 'queryRenderedFeatures.filter', params.filter); } @@ -821,7 +821,7 @@ class Style extends Evented { const sourceResults = []; for (const id in this.sourceCaches) { if (params.layers && !includedSources[id]) continue; - const results = QueryFeatures.rendered(this.sourceCaches[id], this._layers, queryGeometry, params, zoom, bearing, this.placement ? this.placement.collisionIndex : null); + const results = QueryFeatures.rendered(this.sourceCaches[id], this._layers, queryGeometry, params, transform, this.placement ? this.placement.collisionIndex : null); sourceResults.push(results); } return this._flattenRenderedFeatures(sourceResults); diff --git a/src/style/style_layer.js b/src/style/style_layer.js index 313079ad00b..2e536a86692 100644 --- a/src/style/style_layer.js +++ b/src/style/style_layer.js @@ -18,6 +18,7 @@ import type Point from '@mapbox/point-geometry'; import type {FeatureFilter} from '../style-spec/feature_filter'; import type {TransitionParameters} from './properties'; import type EvaluationParameters from './evaluation_parameters'; +import type Transform from '../geo/transform'; const TRANSITION_SUFFIX = '-transition'; @@ -48,8 +49,9 @@ class StyleLayer extends Evented { feature: VectorTileFeature, geometry: Array>, zoom: number, - bearing: number, - pixelsToTileUnits: number) => boolean; + transform: Transform, + pixelsToTileUnits: number, + posMatrix: Float32Array) => boolean; constructor(layer: LayerSpecification, properties: {layout?: Properties<*>, paint: Properties<*>}) { super(); diff --git a/src/style/style_layer/circle_style_layer.js b/src/style/style_layer/circle_style_layer.js index 6ffa8865e32..e23ff259d04 100644 --- a/src/style/style_layer/circle_style_layer.js +++ b/src/style/style_layer/circle_style_layer.js @@ -2,9 +2,13 @@ const StyleLayer = require('../style_layer'); const CircleBucket = require('../../data/bucket/circle_bucket'); -const {multiPolygonIntersectsBufferedMultiPoint} = require('../../util/intersection_tests'); +const {multiPolygonIntersectsBufferedPoint} = require('../../util/intersection_tests'); const {getMaximumPaintValue, translateDistance, translate} = require('../query_utils'); const properties = require('./circle_style_layer_properties'); +const {vec4} = require('@mapbox/gl-matrix'); +const Point = require('@mapbox/point-geometry'); + +import type Transform from '../../geo/transform'; const { Transitionable, @@ -13,7 +17,6 @@ const { } = require('../properties'); import type {Bucket, BucketParameters} from '../../data/bucket'; -import type Point from '@mapbox/point-geometry'; import type {PaintProps} from './circle_style_layer_properties'; class CircleStyleLayer extends StyleLayer { @@ -40,16 +43,59 @@ class CircleStyleLayer extends StyleLayer { feature: VectorTileFeature, geometry: Array>, zoom: number, - bearing: number, - pixelsToTileUnits: number): boolean { + transform: Transform, + pixelsToTileUnits: number, + posMatrix: Float32Array): boolean { const translatedPolygon = translate(queryGeometry, this.paint.get('circle-translate'), this.paint.get('circle-translate-anchor'), - bearing, pixelsToTileUnits); - const radius = this.paint.get('circle-radius').evaluate(feature) * pixelsToTileUnits; - const stroke = this.paint.get('circle-stroke-width').evaluate(feature) * pixelsToTileUnits; - return multiPolygonIntersectsBufferedMultiPoint(translatedPolygon, geometry, radius + stroke); + transform.angle, pixelsToTileUnits); + const radius = this.paint.get('circle-radius').evaluate(feature); + const stroke = this.paint.get('circle-stroke-width').evaluate(feature); + const size = radius + stroke; + + // For pitch-alignment: map, compare feature geometry to query geometry in the plane of the tile + // // Otherwise, compare geometry in the plane of the viewport + // // A circle with fixed scaling relative to the viewport gets larger in tile space as it moves into the distance + // // A circle with fixed scaling relative to the map gets smaller in viewport space as it moves into the distance + const alignWithMap = this.paint.get('circle-pitch-alignment') === 'map'; + const transformedPolygon = alignWithMap ? translatedPolygon : projectQueryGeometry(translatedPolygon, posMatrix, transform); + const transformedSize = alignWithMap ? size * pixelsToTileUnits : size; + + for (const ring of geometry) { + for (const point of ring) { + + const transformedPoint = alignWithMap ? point : projectPoint(point, posMatrix, transform); + + let adjustedSize = transformedSize; + const projectedCenter = vec4.transformMat4([], [point.x, point.y, 0, 1], posMatrix); + if (this.paint.get('circle-pitch-scale') === 'viewport' && this.paint.get('circle-pitch-alignment') === 'map') { + adjustedSize *= projectedCenter[3] / transform.cameraToCenterDistance; + } else if (this.paint.get('circle-pitch-scale') === 'map' && this.paint.get('circle-pitch-alignment') === 'viewport') { + adjustedSize *= transform.cameraToCenterDistance / projectedCenter[3]; + } + + if (multiPolygonIntersectsBufferedPoint(transformedPolygon, transformedPoint, adjustedSize)) return true; + } + } + + return false; } } +function projectPoint(p: Point, posMatrix: Float32Array, transform: Transform) { + const point = vec4.transformMat4([], [p.x, p.y, 0, 1], posMatrix); + return new Point( + (point[0] / point[3] + 1) * transform.width * 0.5, + (point[1] / point[3] + 1) * transform.height * 0.5); +} + +function projectQueryGeometry(queryGeometry: Array>, posMatrix: Float32Array, transform: Transform) { + return queryGeometry.map((r) => { + return r.map((p) => { + return projectPoint(p, posMatrix, transform); + }); + }); +} + module.exports = CircleStyleLayer; diff --git a/src/style/style_layer/fill_extrusion_style_layer.js b/src/style/style_layer/fill_extrusion_style_layer.js index b31c8ac5b64..5e8ff0ec88a 100644 --- a/src/style/style_layer/fill_extrusion_style_layer.js +++ b/src/style/style_layer/fill_extrusion_style_layer.js @@ -16,6 +16,7 @@ import type {BucketParameters} from '../../data/bucket'; import type Point from '@mapbox/point-geometry'; import type {PaintProps} from './fill_extrusion_style_layer_properties'; import type Framebuffer from '../../gl/framebuffer'; +import type Transform from '../../geo/transform'; class FillExtrusionStyleLayer extends StyleLayer { _transitionablePaint: Transitionable; @@ -39,12 +40,12 @@ class FillExtrusionStyleLayer extends StyleLayer { feature: VectorTileFeature, geometry: Array>, zoom: number, - bearing: number, + transform: Transform, pixelsToTileUnits: number): boolean { const translatedPolygon = translate(queryGeometry, this.paint.get('fill-extrusion-translate'), this.paint.get('fill-extrusion-translate-anchor'), - bearing, pixelsToTileUnits); + transform.angle, pixelsToTileUnits); return multiPolygonIntersectsMultiPolygon(translatedPolygon, geometry); } diff --git a/src/style/style_layer/fill_style_layer.js b/src/style/style_layer/fill_style_layer.js index 157d876ab4f..44926514dbc 100644 --- a/src/style/style_layer/fill_style_layer.js +++ b/src/style/style_layer/fill_style_layer.js @@ -16,6 +16,7 @@ import type {BucketParameters} from '../../data/bucket'; import type Point from '@mapbox/point-geometry'; import type {PaintProps} from './fill_style_layer_properties'; import type EvaluationParameters from '../evaluation_parameters'; +import type Transform from '../../geo/transform'; class FillStyleLayer extends StyleLayer { _transitionablePaint: Transitionable; @@ -47,12 +48,12 @@ class FillStyleLayer extends StyleLayer { feature: VectorTileFeature, geometry: Array>, zoom: number, - bearing: number, + transform: Transform, pixelsToTileUnits: number): boolean { const translatedPolygon = translate(queryGeometry, this.paint.get('fill-translate'), this.paint.get('fill-translate-anchor'), - bearing, pixelsToTileUnits); + transform.angle, pixelsToTileUnits); return multiPolygonIntersectsMultiPolygon(translatedPolygon, geometry); } } diff --git a/src/style/style_layer/line_style_layer.js b/src/style/style_layer/line_style_layer.js index 282a22f09b6..0ad51821982 100644 --- a/src/style/style_layer/line_style_layer.js +++ b/src/style/style_layer/line_style_layer.js @@ -20,6 +20,7 @@ const { import type {Bucket, BucketParameters} from '../../data/bucket'; import type {LayoutProps, PaintProps} from './line_style_layer_properties'; +import type Transform from '../../geo/transform'; class LineFloorwidthProperty extends DataDrivenProperty { useIntegerZoom: true; @@ -79,12 +80,12 @@ class LineStyleLayer extends StyleLayer { feature: VectorTileFeature, geometry: Array>, zoom: number, - bearing: number, + transform: Transform, pixelsToTileUnits: number): boolean { const translatedPolygon = translate(queryGeometry, this.paint.get('line-translate'), this.paint.get('line-translate-anchor'), - bearing, pixelsToTileUnits); + transform.angle, pixelsToTileUnits); const halfWidth = pixelsToTileUnits / 2 * getLineWidth( this.paint.get('line-width').evaluate(feature), this.paint.get('line-gap-width').evaluate(feature)); diff --git a/src/ui/map.js b/src/ui/map.js index 46c82e80529..c69b34aa822 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -845,8 +845,7 @@ class Map extends Camera { return this.style.queryRenderedFeatures( this._makeQueryGeometry(geometry), options, - this.transform.zoom, - this.transform.angle + this.transform ); function isPointLike(input) { diff --git a/src/util/intersection_tests.js b/src/util/intersection_tests.js index 7f680d2b9f7..1782ae5e30c 100644 --- a/src/util/intersection_tests.js +++ b/src/util/intersection_tests.js @@ -5,6 +5,7 @@ const {isCounterClockwise} = require('./util'); import type Point from '@mapbox/point-geometry'; module.exports = { + multiPolygonIntersectsBufferedPoint, multiPolygonIntersectsBufferedMultiPoint, multiPolygonIntersectsMultiPolygon, multiPolygonIntersectsBufferedMultiLine, @@ -32,16 +33,20 @@ function polygonIntersectsPolygon(polygonA: Polygon, polygonB: Polygon) { return false; } -function multiPolygonIntersectsBufferedMultiPoint(multiPolygon: MultiPolygon, rings: Array, radius: number) { +function multiPolygonIntersectsBufferedPoint(multiPolygon: MultiPolygon, point: Point, radius: number) { for (let j = 0; j < multiPolygon.length; j++) { const polygon = multiPolygon[j]; - for (let i = 0; i < rings.length; i++) { - const ring = rings[i]; - for (let k = 0; k < ring.length; k++) { - const point = ring[k]; - if (polygonContainsPoint(polygon, point)) return true; - if (pointIntersectsBufferedLine(point, polygon, radius)) return true; - } + if (polygonContainsPoint(polygon, point)) return true; + if (pointIntersectsBufferedLine(point, polygon, radius)) return true; + } + return false; +} + +function multiPolygonIntersectsBufferedMultiPoint(multiPolygon: MultiPolygon, rings: Array, radius: number) { + for (let i = 0; i < rings.length; i++) { + const ring = rings[i]; + for (let k = 0; k < ring.length; k++) { + if (multiPolygonIntersectsBufferedPoint(multiPolygon, ring[k], radius)) return true; } } return false; diff --git a/test/integration/query-tests/circle-pitch-scale/map-inside-align-map/expected.json b/test/integration/query-tests/circle-pitch-scale/map-inside-align-map/expected.json new file mode 100644 index 00000000000..51c0a144bad --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/map-inside-align-map/expected.json @@ -0,0 +1,13 @@ +[ + { + "geometry": { + "type": "Point", + "coordinates": [ + -84.3310546875, + 33.92512970007199 + ] + }, + "type": "Feature", + "properties": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/circle-pitch-scale/map-inside-align-map/style.json b/test/integration/query-tests/circle-pitch-scale/map-inside-align-map/style.json new file mode 100644 index 00000000000..91e488103ff --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/map-inside-align-map/style.json @@ -0,0 +1,45 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "queryGeometry": [ + 292, + 35 + ] + } + }, + "center": [ + -92.3780691249957, + -20 + ], + "zoom": 2, + "pitch": 60, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-84.333565, 33.925575] + } + }] + } + } + }, + "layers": [ + { + "id": "road", + "type": "circle", + "source": "geojson", + "paint": { + "circle-pitch-alignment": "map", + "circle-pitch-scale": "map", + "circle-radius": 20 + } + } + ] +} diff --git a/test/integration/query-tests/circle-pitch-scale/map-inside-align-viewport/expected.json b/test/integration/query-tests/circle-pitch-scale/map-inside-align-viewport/expected.json new file mode 100644 index 00000000000..51c0a144bad --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/map-inside-align-viewport/expected.json @@ -0,0 +1,13 @@ +[ + { + "geometry": { + "type": "Point", + "coordinates": [ + -84.3310546875, + 33.92512970007199 + ] + }, + "type": "Feature", + "properties": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/circle-pitch-scale/map-inside-align-viewport/style.json b/test/integration/query-tests/circle-pitch-scale/map-inside-align-viewport/style.json new file mode 100644 index 00000000000..d76b20e3646 --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/map-inside-align-viewport/style.json @@ -0,0 +1,45 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "queryGeometry": [ + 292, + 35 + ] + } + }, + "center": [ + -92.3780691249957, + -20 + ], + "zoom": 2, + "pitch": 60, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-84.333565, 33.925575] + } + }] + } + } + }, + "layers": [ + { + "id": "road", + "type": "circle", + "source": "geojson", + "paint": { + "circle-pitch-alignment": "viewport", + "circle-pitch-scale": "map", + "circle-radius": 20 + } + } + ] +} diff --git a/test/integration/query-tests/circle-pitch-scale/map-outside-align-map/expected.json b/test/integration/query-tests/circle-pitch-scale/map-outside-align-map/expected.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/map-outside-align-map/expected.json @@ -0,0 +1 @@ +[] diff --git a/test/integration/query-tests/circle-pitch-scale/map-outside-align-map/style.json b/test/integration/query-tests/circle-pitch-scale/map-outside-align-map/style.json new file mode 100644 index 00000000000..9908102df6b --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/map-outside-align-map/style.json @@ -0,0 +1,45 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "queryGeometry": [ + 295, + 35 + ] + } + }, + "center": [ + -92.3780691249957, + -20 + ], + "zoom": 2, + "pitch": 60, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-84.333565, 33.925575] + } + }] + } + } + }, + "layers": [ + { + "id": "road", + "type": "circle", + "source": "geojson", + "paint": { + "circle-pitch-alignment": "map", + "circle-pitch-scale": "map", + "circle-radius": 20 + } + } + ] +} diff --git a/test/integration/query-tests/circle-pitch-scale/map-outside-align-viewport/expected.json b/test/integration/query-tests/circle-pitch-scale/map-outside-align-viewport/expected.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/map-outside-align-viewport/expected.json @@ -0,0 +1 @@ +[] diff --git a/test/integration/query-tests/circle-pitch-scale/map-outside-align-viewport/style.json b/test/integration/query-tests/circle-pitch-scale/map-outside-align-viewport/style.json new file mode 100644 index 00000000000..8596caf102c --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/map-outside-align-viewport/style.json @@ -0,0 +1,45 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "queryGeometry": [ + 295, + 35 + ] + } + }, + "center": [ + -92.3780691249957, + -20 + ], + "zoom": 2, + "pitch": 60, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-84.333565, 33.925575] + } + }] + } + } + }, + "layers": [ + { + "id": "road", + "type": "circle", + "source": "geojson", + "paint": { + "circle-pitch-alignment": "viewport", + "circle-pitch-scale": "map", + "circle-radius": 20 + } + } + ] +} diff --git a/test/integration/query-tests/circle-pitch-scale/viewport-inside-align-map/expected.json b/test/integration/query-tests/circle-pitch-scale/viewport-inside-align-map/expected.json new file mode 100644 index 00000000000..51c0a144bad --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/viewport-inside-align-map/expected.json @@ -0,0 +1,13 @@ +[ + { + "geometry": { + "type": "Point", + "coordinates": [ + -84.3310546875, + 33.92512970007199 + ] + }, + "type": "Feature", + "properties": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/circle-pitch-scale/viewport-inside-align-map/style.json b/test/integration/query-tests/circle-pitch-scale/viewport-inside-align-map/style.json new file mode 100644 index 00000000000..96a880be0ba --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/viewport-inside-align-map/style.json @@ -0,0 +1,45 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "queryGeometry": [ + 300, + 35 + ] + } + }, + "center": [ + -92.3780691249957, + -20 + ], + "zoom": 2, + "pitch": 60, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-84.333565, 33.925575] + } + }] + } + } + }, + "layers": [ + { + "id": "road", + "type": "circle", + "source": "geojson", + "paint": { + "circle-pitch-alignment": "map", + "circle-pitch-scale": "viewport", + "circle-radius": 20 + } + } + ] +} diff --git a/test/integration/query-tests/circle-pitch-scale/viewport-inside-align-viewport/expected.json b/test/integration/query-tests/circle-pitch-scale/viewport-inside-align-viewport/expected.json new file mode 100644 index 00000000000..51c0a144bad --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/viewport-inside-align-viewport/expected.json @@ -0,0 +1,13 @@ +[ + { + "geometry": { + "type": "Point", + "coordinates": [ + -84.3310546875, + 33.92512970007199 + ] + }, + "type": "Feature", + "properties": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/circle-pitch-scale/viewport-inside-align-viewport/style.json b/test/integration/query-tests/circle-pitch-scale/viewport-inside-align-viewport/style.json new file mode 100644 index 00000000000..947c0065c79 --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/viewport-inside-align-viewport/style.json @@ -0,0 +1,45 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "queryGeometry": [ + 300, + 35 + ] + } + }, + "center": [ + -92.3780691249957, + -20 + ], + "zoom": 2, + "pitch": 60, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-84.333565, 33.925575] + } + }] + } + } + }, + "layers": [ + { + "id": "road", + "type": "circle", + "source": "geojson", + "paint": { + "circle-pitch-alignment": "viewport", + "circle-pitch-scale": "viewport", + "circle-radius": 20 + } + } + ] +} diff --git a/test/integration/query-tests/circle-pitch-scale/viewport-outside-align-map/expected.json b/test/integration/query-tests/circle-pitch-scale/viewport-outside-align-map/expected.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/viewport-outside-align-map/expected.json @@ -0,0 +1 @@ +[] diff --git a/test/integration/query-tests/circle-pitch-scale/viewport-outside-align-map/style.json b/test/integration/query-tests/circle-pitch-scale/viewport-outside-align-map/style.json new file mode 100644 index 00000000000..8d3897db2c3 --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/viewport-outside-align-map/style.json @@ -0,0 +1,45 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "queryGeometry": [ + 303, + 35 + ] + } + }, + "center": [ + -92.3780691249957, + -20 + ], + "zoom": 2, + "pitch": 60, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-84.333565, 33.925575] + } + }] + } + } + }, + "layers": [ + { + "id": "road", + "type": "circle", + "source": "geojson", + "paint": { + "circle-pitch-alignment": "map", + "circle-pitch-scale": "viewport", + "circle-radius": 20 + } + } + ] +} diff --git a/test/integration/query-tests/circle-pitch-scale/viewport-outside-align-viewport/expected.json b/test/integration/query-tests/circle-pitch-scale/viewport-outside-align-viewport/expected.json new file mode 100644 index 00000000000..fe51488c706 --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/viewport-outside-align-viewport/expected.json @@ -0,0 +1 @@ +[] diff --git a/test/integration/query-tests/circle-pitch-scale/viewport-outside-align-viewport/style.json b/test/integration/query-tests/circle-pitch-scale/viewport-outside-align-viewport/style.json new file mode 100644 index 00000000000..8d3897db2c3 --- /dev/null +++ b/test/integration/query-tests/circle-pitch-scale/viewport-outside-align-viewport/style.json @@ -0,0 +1,45 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "queryGeometry": [ + 303, + 35 + ] + } + }, + "center": [ + -92.3780691249957, + -20 + ], + "zoom": 2, + "pitch": 60, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-84.333565, 33.925575] + } + }] + } + } + }, + "layers": [ + { + "id": "road", + "type": "circle", + "source": "geojson", + "paint": { + "circle-pitch-alignment": "map", + "circle-pitch-scale": "viewport", + "circle-radius": 20 + } + } + ] +} diff --git a/test/integration/query-tests/circle-radius/tile-boundary/expected.json b/test/integration/query-tests/circle-radius/tile-boundary/expected.json new file mode 100644 index 00000000000..51c0a144bad --- /dev/null +++ b/test/integration/query-tests/circle-radius/tile-boundary/expected.json @@ -0,0 +1,13 @@ +[ + { + "geometry": { + "type": "Point", + "coordinates": [ + -84.3310546875, + 33.92512970007199 + ] + }, + "type": "Feature", + "properties": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/circle-radius/tile-boundary/style.json b/test/integration/query-tests/circle-radius/tile-boundary/style.json new file mode 100644 index 00000000000..d0d8bafb0b1 --- /dev/null +++ b/test/integration/query-tests/circle-radius/tile-boundary/style.json @@ -0,0 +1,43 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "height": 256, + "queryGeometry": [ + 240, + 125 + ] + } + }, + "center": [ + -92.3780691249957, + 35.94102132783915 + ], + "zoom": 2, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [-84.333565, 33.925575] + } + }] + } + } + }, + "layers": [ + { + "id": "road", + "type": "circle", + "source": "geojson", + "paint": { + "circle-radius": 100 + } + } + ] +} diff --git a/test/unit/source/query_features.test.js b/test/unit/source/query_features.test.js index 4fe87e999f8..017219e110a 100644 --- a/test/unit/source/query_features.test.js +++ b/test/unit/source/query_features.test.js @@ -3,11 +3,13 @@ const test = require('mapbox-gl-js-test').test; const QueryFeatures = require('../../../src/source/query_features.js'); const SourceCache = require('../../../src/source/source_cache.js'); +const Transform = require('../../../src/geo/transform.js'); test('QueryFeatures#rendered', (t) => { t.test('returns empty object if source returns no tiles', (t) => { - const mockSourceCache = { tilesIn: function () { return []; } }; - const result = QueryFeatures.rendered(mockSourceCache); + const mockSourceCache = { tilesIn: function () { return []; }}; + const transform = new Transform(); + const result = QueryFeatures.rendered(mockSourceCache, undefined, undefined, undefined, transform); t.deepEqual(result, []); t.end(); }); diff --git a/test/unit/source/source_cache.test.js b/test/unit/source/source_cache.test.js index 0986b0f7773..af205890bc5 100644 --- a/test/unit/source/source_cache.test.js +++ b/test/unit/source/source_cache.test.js @@ -1152,6 +1152,7 @@ test('SourceCache#tilesIn', (t) => { const sourceCache = createSourceCache({ loadTile: function(tile, callback) { tile.state = 'loaded'; + tile.additionalRadius = 0; callback(); } }); @@ -1170,7 +1171,7 @@ test('SourceCache#tilesIn', (t) => { const tiles = sourceCache.tilesIn([ new Coordinate(0.5, 0.25, 1), new Coordinate(1.5, 0.75, 1) - ]); + ], 1); tiles.sort((a, b) => { return a.tile.tileID.canonical.x - b.tile.tileID.canonical.x; }); tiles.forEach((result) => { delete result.tile.uid; }); @@ -1193,7 +1194,11 @@ test('SourceCache#tilesIn', (t) => { t.test('reparsed overscaled tiles', (t) => { const sourceCache = createSourceCache({ - loadTile: function(tile, callback) { tile.state = 'loaded'; callback(); }, + loadTile: function(tile, callback) { + tile.state = 'loaded'; + tile.additionalRadius = 0; + callback(); + }, reparseOverscaled: true, minzoom: 1, maxzoom: 1, @@ -1217,7 +1222,7 @@ test('SourceCache#tilesIn', (t) => { const tiles = sourceCache.tilesIn([ new Coordinate(0.5, 0.25, 1), new Coordinate(1.5, 0.75, 1) - ]); + ], 1); tiles.sort((a, b) => { return a.tile.tileID.canonical.x - b.tile.tileID.canonical.x; }); tiles.forEach((result) => { delete result.tile.uid; }); diff --git a/test/unit/style/style.test.js b/test/unit/style/style.test.js index 927f02e09e8..855422e7588 100644 --- a/test/unit/style/style.test.js +++ b/test/unit/style/style.test.js @@ -1838,8 +1838,10 @@ test('Style#query*Features', (t) => { let style; let onError; + let transform; t.beforeEach((callback) => { + transform = new Transform(); style = new Style(new StubMap()); style.loadJSON({ "version": 8, @@ -1862,13 +1864,13 @@ test('Style#query*Features', (t) => { }); t.test('querySourceFeatures emits an error on incorrect filter', (t) => { - t.deepEqual(style.querySourceFeatures([10, 100], {filter: 7}), []); + t.deepEqual(style.querySourceFeatures([10, 100], {filter: 7}, transform), []); t.match(onError.args[0][0].error.message, /querySourceFeatures\.filter/); t.end(); }); t.test('queryRenderedFeatures emits an error on incorrect filter', (t) => { - t.deepEqual(style.queryRenderedFeatures([10, 100], {filter: 7}), []); + t.deepEqual(style.queryRenderedFeatures([10, 100], {filter: 7}, transform), []); t.match(onError.args[0][0].error.message, /queryRenderedFeatures\.filter/); t.end(); }); diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js index 9f83caf3be5..3a5e0c62ce4 100755 --- a/test/unit/ui/map.test.js +++ b/test/unit/ui/map.test.js @@ -871,8 +871,7 @@ test('Map', (t) => { 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[1], {}); // params - t.deepEqual(args[2], 0); // bearing - t.deepEqual(args[3], 0); // zoom + t.deepEqual(args[2], map.transform); // transform t.deepEqual(output, []); t.end();