diff --git a/debug/extrusion-query.html b/debug/extrusion-query.html new file mode 100644 index 00000000000..13c682a4a58 --- /dev/null +++ b/debug/extrusion-query.html @@ -0,0 +1,85 @@ + + + + Mapbox GL JS debug page + + + + + + + +
+ + + + + + diff --git a/package.json b/package.json index 5623efb5e72..36f4d69cebf 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "geojson-rewind": "^0.3.0", "geojson-vt": "^3.2.1", "gl-matrix": "^2.6.1", - "grid-index": "^1.0.0", + "grid-index": "^1.1.0", "minimist": "0.0.8", "murmurhash-js": "^1.0.0", "pbf": "^3.0.5", diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index d4867838876..37c71761571 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -112,7 +112,7 @@ class FillExtrusionBucket implements Bucket { this.addFeature(patternFeature, geometry, index, {}); } - options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); + options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index, true); } } diff --git a/src/data/feature_index.js b/src/data/feature_index.js index 0cf3f33895d..f6fc671fe0a 100644 --- a/src/data/feature_index.js +++ b/src/data/feature_index.js @@ -15,6 +15,7 @@ import { OverscaledTileID } from '../source/tile_id'; import { register } from '../util/web_worker_transfer'; import EvaluationParameters from '../style/evaluation_parameters'; import SourceFeatureState from '../source/source_state'; +import {polygonIntersectsBox} from '../util/intersection_tests'; import type StyleLayer from '../style/style_layer'; import type {FeatureFilter} from '../style-spec/feature_filter'; @@ -25,10 +26,11 @@ import { FeatureIndexArray } from './array_types'; type QueryParameters = { scale: number, - posMatrix: Float32Array, + pixelPosMatrix: Float32Array, transform: Transform, tileSize: number, - queryGeometry: Array>, + queryGeometry: Array, + cameraQueryGeometry: Array, queryPadding: number, params: { filter: FilterSpecification, @@ -42,6 +44,7 @@ class FeatureIndex { y: number; z: number; grid: Grid; + grid3D: Grid; featureIndexArray: FeatureIndexArray; rawTileData: ArrayBuffer; @@ -58,13 +61,16 @@ class FeatureIndex { this.y = tileID.canonical.y; this.z = tileID.canonical.z; this.grid = grid || new Grid(EXTENT, 16, 0); + this.grid3D = new Grid(EXTENT, 16, 0); this.featureIndexArray = featureIndexArray || new FeatureIndexArray(); } - insert(feature: VectorTileFeature, geometry: Array>, featureIndex: number, sourceLayerIndex: number, bucketIndex: number) { + insert(feature: VectorTileFeature, geometry: Array>, featureIndex: number, sourceLayerIndex: number, bucketIndex: number, is3D?: boolean) { const key = this.featureIndexArray.length; this.featureIndexArray.emplaceBack(featureIndex, sourceLayerIndex, bucketIndex); + const grid = is3D ? this.grid3D : this.grid; + for (let r = 0; r < geometry.length; r++) { const ring = geometry[r]; @@ -81,7 +87,7 @@ class FeatureIndex { bbox[1] < EXTENT && bbox[2] >= 0 && bbox[3] >= 0) { - this.grid.insert(key, bbox[0], bbox[1], bbox[2], bbox[3]); + grid.insert(key, bbox[0], bbox[1], bbox[2], bbox[3]); } } } @@ -105,23 +111,22 @@ class FeatureIndex { const queryGeometry = args.queryGeometry; const queryPadding = args.queryPadding * pixelsToTileUnits; - 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 = ring[k]; - minX = Math.min(minX, p.x); - minY = Math.min(minY, p.y); - maxX = Math.max(maxX, p.x); - maxY = Math.max(maxY, p.y); - } + const bounds = getBounds(queryGeometry); + const matching = this.grid.query(bounds.minX - queryPadding, bounds.minY - queryPadding, bounds.maxX + queryPadding, bounds.maxY + queryPadding); + + const cameraBounds = getBounds(args.cameraQueryGeometry); + const matching3D = this.grid3D.query( + cameraBounds.minX - queryPadding, cameraBounds.minY - queryPadding, cameraBounds.maxX + queryPadding, cameraBounds.maxY + queryPadding, + (bx1, by1, bx2, by2) => { + return polygonIntersectsBox(args.cameraQueryGeometry, bx1 - queryPadding, by1 - queryPadding, bx2 + queryPadding, by2 + queryPadding); + }); + + for (const key of matching3D) { + matching.push(key); } - const matching = this.grid.query(minX - queryPadding, minY - queryPadding, maxX + queryPadding, maxY + queryPadding); matching.sort(topDownFeatureComparator); + const result = {}; let previousIndex; for (let k = 0; k < matching.length; k++) { @@ -150,7 +155,7 @@ class FeatureIndex { // `feature-state` expression evaluation requires feature state to be available featureState = sourceFeatureState.getState(styleLayer.sourceLayer || '_geojsonTileLayer', feature.id); } - return styleLayer.queryIntersectsFeature(queryGeometry, feature, featureState, featureGeometry, this.z, args.transform, pixelsToTileUnits, args.posMatrix); + return styleLayer.queryIntersectsFeature(queryGeometry, feature, featureState, featureGeometry, this.z, args.transform, pixelsToTileUnits, args.pixelPosMatrix); } ); } @@ -166,7 +171,7 @@ class FeatureIndex { filter: FeatureFilter, filterLayerIDs: Array, styleLayers: {[string]: StyleLayer}, - intersectionTest?: (feature: VectorTileFeature, styleLayer: StyleLayer) => boolean) { + intersectionTest?: (feature: VectorTileFeature, styleLayer: StyleLayer) => boolean | number) { const layerIDs = this.bucketLayerIDs[bucketIndex]; if (filterLayerIDs && !arraysIntersect(filterLayerIDs, layerIDs)) @@ -189,7 +194,8 @@ class FeatureIndex { const styleLayer = styleLayers[layerID]; if (!styleLayer) continue; - if (intersectionTest && !intersectionTest(feature, styleLayer)) { + const intersectionZ = !intersectionTest || intersectionTest(feature, styleLayer); + if (!intersectionZ) { // Only applied for non-symbol features continue; } @@ -200,7 +206,7 @@ class FeatureIndex { if (layerResult === undefined) { layerResult = result[layerID] = []; } - layerResult.push({ featureIndex, feature: geojsonFeature }); + layerResult.push({ featureIndex, feature: geojsonFeature, intersectionZ }); } } @@ -251,6 +257,20 @@ register( export default FeatureIndex; +function getBounds(geometry: Array) { + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + for (const p of geometry) { + minX = Math.min(minX, p.x); + minY = Math.min(minY, p.y); + maxX = Math.max(maxX, p.x); + maxY = Math.max(maxY, p.y); + } + return { minX, minY, maxX, maxY }; +} + function topDownFeatureComparator(a, b) { return b - a; } diff --git a/src/geo/transform.js b/src/geo/transform.js index 3bc60a863ff..bccffa27774 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -592,6 +592,59 @@ class Transform { const topPoint = vec4.transformMat4(p, p, this.pixelMatrix); return topPoint[3] / this.cameraToCenterDistance; } + + /* + * The camera looks at the map from a 3D (lng, lat, altitude) location. Let's use `cameraLocation` + * as the name for the location under the camera and on the surface of the earth (lng, lat, 0). + * `cameraPoint` is the projected position of the `cameraLocation`. + * + * This point is useful to us because only fill-extrusions that are between `cameraPoint` and + * the query point on the surface of the earth can extend and intersect the query. + * + * When the map is not pitched the `cameraPoint` is equivalent to the center of the map because + * the camera is right above the center of the map. + */ + getCameraPoint() { + const pitch = this._pitch; + const yOffset = Math.tan(pitch) * (this.cameraToCenterDistance || 1); + return this.centerPoint.add(new Point(0, yOffset)); + } + + /* + * When the map is pitched, some of the 3D features that intersect a query will not intersect + * the query at the surface of the earth. Instead the feature may be closer and only intersect + * the query because it extrudes into the air. + * + * This returns a geometry that includes all of the original query as well as all possible ares of the + * screen where the *base* of a visible extrusion could be. + * - For point queries, the line from the query point to the "camera point" + * - For other geometries, the envelope of the query geometry and the "camera point" + */ + getCameraQueryGeometry(queryGeometry: Array): Array { + const c = this.getCameraPoint(); + + if (queryGeometry.length === 1) { + return [queryGeometry[0], c]; + } else { + let minX = c.x; + let minY = c.y; + let maxX = c.x; + let maxY = c.y; + for (const p of queryGeometry) { + minX = Math.min(minX, p.x); + minY = Math.min(minY, p.y); + maxX = Math.max(maxX, p.x); + maxY = Math.max(maxY, p.y); + } + return [ + new Point(minX, minY), + new Point(maxX, minY), + new Point(maxX, maxY), + new Point(minX, maxY), + new Point(minX, minY) + ]; + } + } } export default Transform; diff --git a/src/source/query_features.js b/src/source/query_features.js index ffa9eb18475..fa0cb9891d8 100644 --- a/src/source/query_features.js +++ b/src/source/query_features.js @@ -2,20 +2,52 @@ import type SourceCache from './source_cache'; import type StyleLayer from '../style/style_layer'; -import type MercatorCoordinate from '../geo/mercator_coordinate'; import type CollisionIndex from '../symbol/collision_index'; import type Transform from '../geo/transform'; import type { RetainedQueryData } from '../symbol/placement'; import type {FilterSpecification} from '../style-spec/types'; import assert from 'assert'; +import { mat4 } from 'gl-matrix'; + +/* + * Returns a matrix that can be used to convert from tile coordinates to viewport pixel coordinates. + */ +function getPixelPosMatrix(transform, tileID) { + const t = mat4.identity([]); + mat4.translate(t, t, [1, 1, 0]); + mat4.scale(t, t, [transform.width * 0.5, transform.height * 0.5, 1]); + return mat4.multiply(t, t, transform.calculatePosMatrix(tileID.toUnwrapped())); +} + +function queryIncludes3DLayer(layers?: Array, styleLayers: {[string]: StyleLayer}, sourceID: string) { + if (layers) { + for (const layerID of layers) { + const layer = styleLayers[layerID]; + if (layer && layer.source === sourceID && layer.type === 'fill-extrusion') { + return true; + } + } + } else { + for (const key in styleLayers) { + const layer = styleLayers[key]; + if (layer.source === sourceID && layer.type === 'fill-extrusion') { + return true; + } + } + } + return false; +} export function queryRenderedFeatures(sourceCache: SourceCache, styleLayers: {[string]: StyleLayer}, - queryGeometry: Array, + queryGeometry: Array, params: { filter: FilterSpecification, layers: Array }, transform: Transform) { + + const has3DLayer = queryIncludes3DLayer(params && params.layers, styleLayers, sourceCache.id); + const maxPitchScaleFactor = transform.maxPitchScaleFactor(); - const tilesIn = sourceCache.tilesIn(queryGeometry, maxPitchScaleFactor); + const tilesIn = sourceCache.tilesIn(queryGeometry, maxPitchScaleFactor, has3DLayer); tilesIn.sort(sortTilesIn); @@ -27,11 +59,12 @@ export function queryRenderedFeatures(sourceCache: SourceCache, styleLayers, sourceCache._state, tileIn.queryGeometry, + tileIn.cameraQueryGeometry, tileIn.scale, params, transform, maxPitchScaleFactor, - sourceCache.transform.calculatePosMatrix(tileIn.tileID.toUnwrapped())) + getPixelPosMatrix(sourceCache.transform, tileIn.tileID)) }); } @@ -39,7 +72,8 @@ export function queryRenderedFeatures(sourceCache: SourceCache, // Merge state from SourceCache into the results for (const layerID in result) { - result[layerID].forEach((feature) => { + result[layerID].forEach((featureWrapper) => { + const feature = featureWrapper.feature; const state = sourceCache.getFeatureState(feature.layer['source-layer'], feature.id); feature.source = feature.layer.source; if (feature.layer['source-layer']) { @@ -98,14 +132,15 @@ export function queryRenderedSymbols(styleLayers: {[string]: StyleLayer}, } }); for (const symbolFeature of layerSymbols) { - resultFeatures.push(symbolFeature.feature); + resultFeatures.push(symbolFeature); } } } // Merge state from SourceCache into the results for (const layerName in result) { - result[layerName].forEach((feature) => { + result[layerName].forEach((featureWrapper) => { + const feature = featureWrapper.feature; const layer = styleLayers[layerName]; const sourceCache = sourceCaches[layer.source]; const state = sourceCache.getFeatureState(feature.layer['source-layer'], feature.id); @@ -161,7 +196,7 @@ function mergeRenderedFeatureLayers(tiles) { for (const tileFeature of tileFeatures) { if (!wrappedIDFeatures[tileFeature.featureIndex]) { wrappedIDFeatures[tileFeature.featureIndex] = true; - resultFeatures.push(tileFeature.feature); + resultFeatures.push(tileFeature); } } } diff --git a/src/source/source_cache.js b/src/source/source_cache.js index 63c0ac9c2b7..0255f204781 100644 --- a/src/source/source_cache.js +++ b/src/source/source_cache.js @@ -731,11 +731,23 @@ class SourceCache extends Evented { /** * Search through our current tiles and attempt to find the tiles that * cover the given bounds. - * @param queryGeometry coordinates of the corners of bounding rectangle + * @param pointQueryGeometry 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, maxPitchScaleFactor: number) { + tilesIn(pointQueryGeometry: Array, maxPitchScaleFactor: number, has3DLayer: boolean) { + const tileResults = []; + + const transform = this.transform; + if (!transform) return tileResults; + + const cameraPointQueryGeometry = has3DLayer ? + transform.getCameraQueryGeometry(pointQueryGeometry) : + pointQueryGeometry; + + const queryGeometry = pointQueryGeometry.map((p) => transform.pointCoordinate(p)); + const cameraQueryGeometry = cameraPointQueryGeometry.map((p) => transform.pointCoordinate(p)); + const ids = this.getIds(); let minX = Infinity; @@ -743,15 +755,13 @@ class SourceCache extends Evented { let maxX = -Infinity; let maxY = -Infinity; - for (let k = 0; k < queryGeometry.length; k++) { - const p = queryGeometry[k]; + for (const p of cameraQueryGeometry) { minX = Math.min(minX, p.x); minY = Math.min(minY, p.y); maxX = Math.max(maxX, p.x); maxY = Math.max(maxY, p.y); } - for (let i = 0; i < ids.length; i++) { const tile = this._tiles[ids[i]]; if (tile.holdingForFade()) { @@ -759,7 +769,7 @@ class SourceCache extends Evented { continue; } const tileID = tile.tileID; - const scale = Math.pow(2, this.transform.zoom - tile.tileID.overscaledZ); + const scale = Math.pow(2, transform.zoom - tile.tileID.overscaledZ); const queryPadding = maxPitchScaleFactor * tile.queryPadding * EXTENT / tile.tileSize / scale; const tileSpaceBounds = [ @@ -770,15 +780,14 @@ class SourceCache extends Evented { 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++) { - tileSpaceQueryGeometry.push(tileID.getTilePoint(queryGeometry[j])); - } + const tileSpaceQueryGeometry: Array = queryGeometry.map((c) => tileID.getTilePoint(c)); + const tileSpaceCameraQueryGeometry = cameraQueryGeometry.map((c) => tileID.getTilePoint(c)); tileResults.push({ tile, tileID, - queryGeometry: [tileSpaceQueryGeometry], + queryGeometry: tileSpaceQueryGeometry, + cameraQueryGeometry: tileSpaceCameraQueryGeometry, scale }); } diff --git a/src/source/tile.js b/src/source/tile.js index b2eda7cf9f0..c5e80a046b9 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -258,20 +258,22 @@ class Tile { // Symbol features are queried globally queryRenderedFeatures(layers: {[string]: StyleLayer}, sourceFeatureState: SourceFeatureState, - queryGeometry: Array>, + queryGeometry: Array, + cameraQueryGeometry: Array, scale: number, params: { filter: FilterSpecification, layers: Array }, transform: Transform, maxPitchScaleFactor: number, - posMatrix: Float32Array): {[string]: Array<{ featureIndex: number, feature: GeoJSONFeature }>} { + pixelPosMatrix: Float32Array): {[string]: Array<{ featureIndex: number, feature: GeoJSONFeature }>} { if (!this.latestFeatureIndex || !this.latestFeatureIndex.rawTileData) return {}; return this.latestFeatureIndex.query({ queryGeometry, + cameraQueryGeometry, scale, tileSize: this.tileSize, - posMatrix, + pixelPosMatrix, transform, params, queryPadding: this.queryPadding * maxPitchScaleFactor diff --git a/src/style/query_utils.js b/src/style/query_utils.js index e1ab51eea4c..66cade3d00a 100644 --- a/src/style/query_utils.js +++ b/src/style/query_utils.js @@ -21,7 +21,7 @@ export function translateDistance(translate: [number, number]) { return Math.sqrt(translate[0] * translate[0] + translate[1] * translate[1]); } -export function translate(queryGeometry: Array>, +export function translate(queryGeometry: Array, translate: [number, number], translateAnchor: 'viewport' | 'map', bearing: number, @@ -38,12 +38,8 @@ export function translate(queryGeometry: Array>, const translated = []; for (let i = 0; i < queryGeometry.length; i++) { - const ring = queryGeometry[i]; - const translatedRing = []; - for (let k = 0; k < ring.length; k++) { - translatedRing.push(ring[k].sub(pt._mult(pixelsToTileUnits))); - } - translated.push(translatedRing); + const point = queryGeometry[i]; + translated.push(point.sub(pt._mult(pixelsToTileUnits))); } return translated; } diff --git a/src/style/style.js b/src/style/style.js index cb58f4e05cc..8dbfaf7a78b 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -904,19 +904,35 @@ class Style extends Evented { this._changed = true; } - _flattenRenderedFeatures(sourceResults: Array) { + _flattenAndSortRenderedFeatures(sourceResults: Array) { const features = []; + const features3D = []; for (let l = this._order.length - 1; l >= 0; l--) { const layerId = this._order[l]; for (const sourceResult of sourceResults) { const layerFeatures = sourceResult[layerId]; if (layerFeatures) { - for (const feature of layerFeatures) { - features.push(feature); + if (this._layers[layerId].type === 'fill-extrusion') { + for (const featureWrapper of layerFeatures) { + features3D.push(featureWrapper); + } + } else { + for (const featureWrapper of layerFeatures) { + features.push(featureWrapper.feature); + } } } } } + + features3D.sort((a, b) => { + return a.intersectionZ - b.intersectionZ; + }); + + for (const featureWrapper of features3D) { + features.push(featureWrapper.feature); + } + return features; } @@ -943,7 +959,6 @@ class Style extends Evented { } const sourceResults = []; - const queryCoordinates = queryGeometry.map((p) => transform.pointCoordinate(p)); for (const id in this.sourceCaches) { if (params.layers && !includedSources[id]) continue; @@ -951,7 +966,7 @@ class Style extends Evented { queryRenderedFeatures( this.sourceCaches[id], this._layers, - queryCoordinates, + queryGeometry, params, transform) ); @@ -970,7 +985,8 @@ class Style extends Evented { this.placement.retainedQueryData) ); } - return this._flattenRenderedFeatures(sourceResults); + + return this._flattenAndSortRenderedFeatures(sourceResults); } querySourceFeatures(sourceID: string, params: ?{sourceLayer: ?string, filter: ?Array}) { diff --git a/src/style/style_layer.js b/src/style/style_layer.js index 8ddef032738..131f055aedf 100644 --- a/src/style/style_layer.js +++ b/src/style/style_layer.js @@ -52,14 +52,14 @@ class StyleLayer extends Evented { _featureFilter: FeatureFilter; +queryRadius: (bucket: Bucket) => number; - +queryIntersectsFeature: (queryGeometry: Array>, + +queryIntersectsFeature: (queryGeometry: Array, feature: VectorTileFeature, featureState: FeatureState, geometry: Array>, zoom: number, transform: Transform, pixelsToTileUnits: number, - posMatrix: Float32Array) => boolean; + pixelPosMatrix: Float32Array) => boolean | number; +onAdd: ?(map: Map) => void; +onRemove: ?(map: Map) => void; diff --git a/src/style/style_layer/circle_style_layer.js b/src/style/style_layer/circle_style_layer.js index 3d35dd82ec5..439c9a77804 100644 --- a/src/style/style_layer/circle_style_layer.js +++ b/src/style/style_layer/circle_style_layer.js @@ -3,7 +3,7 @@ import StyleLayer from '../style_layer'; import CircleBucket from '../../data/bucket/circle_bucket'; -import { multiPolygonIntersectsBufferedPoint } from '../../util/intersection_tests'; +import { polygonIntersectsBufferedPoint } from '../../util/intersection_tests'; import { getMaximumPaintValue, translateDistance, translate } from '../query_utils'; import properties from './circle_style_layer_properties'; import { Transitionable, Transitioning, PossiblyEvaluated } from '../properties'; @@ -36,14 +36,14 @@ class CircleStyleLayer extends StyleLayer { translateDistance(this.paint.get('circle-translate')); } - queryIntersectsFeature(queryGeometry: Array>, + queryIntersectsFeature(queryGeometry: Array, feature: VectorTileFeature, featureState: FeatureState, geometry: Array>, zoom: number, transform: Transform, pixelsToTileUnits: number, - posMatrix: Float32Array): boolean { + pixelPosMatrix: Float32Array): boolean { const translatedPolygon = translate(queryGeometry, this.paint.get('circle-translate'), this.paint.get('circle-translate-anchor'), @@ -57,23 +57,23 @@ class CircleStyleLayer extends StyleLayer { // // 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 transformedPolygon = alignWithMap ? translatedPolygon : projectQueryGeometry(translatedPolygon, pixelPosMatrix); const transformedSize = alignWithMap ? size * pixelsToTileUnits : size; for (const ring of geometry) { for (const point of ring) { - const transformedPoint = alignWithMap ? point : projectPoint(point, posMatrix, transform); + const transformedPoint = alignWithMap ? point : projectPoint(point, pixelPosMatrix); let adjustedSize = transformedSize; - const projectedCenter = vec4.transformMat4([], [point.x, point.y, 0, 1], posMatrix); + const projectedCenter = vec4.transformMat4([], [point.x, point.y, 0, 1], pixelPosMatrix); 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; + if (polygonIntersectsBufferedPoint(transformedPolygon, transformedPoint, adjustedSize)) return true; } } @@ -81,18 +81,14 @@ class CircleStyleLayer extends StyleLayer { } } -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 projectPoint(p: Point, pixelPosMatrix: Float32Array) { + const point = vec4.transformMat4([], [p.x, p.y, 0, 1], pixelPosMatrix); + return new Point(point[0] / point[3], point[1] / point[3]); } -function projectQueryGeometry(queryGeometry: Array>, posMatrix: Float32Array, transform: Transform) { - return queryGeometry.map((r) => { - return r.map((p) => { - return projectPoint(p, posMatrix, transform); - }); +function projectQueryGeometry(queryGeometry: Array, pixelPosMatrix: Float32Array) { + return queryGeometry.map((p) => { + return projectPoint(p, pixelPosMatrix); }); } diff --git a/src/style/style_layer/fill_extrusion_style_layer.js b/src/style/style_layer/fill_extrusion_style_layer.js index a8769181a05..1bfb784d641 100644 --- a/src/style/style_layer/fill_extrusion_style_layer.js +++ b/src/style/style_layer/fill_extrusion_style_layer.js @@ -3,14 +3,15 @@ import StyleLayer from '../style_layer'; import FillExtrusionBucket from '../../data/bucket/fill_extrusion_bucket'; -import { multiPolygonIntersectsMultiPolygon } from '../../util/intersection_tests'; +import { polygonIntersectsPolygon, polygonIntersectsMultiPolygon } from '../../util/intersection_tests'; import { translateDistance, translate } from '../query_utils'; import properties from './fill_extrusion_style_layer_properties'; import { Transitionable, Transitioning, PossiblyEvaluated } from '../properties'; +import {vec4} from 'gl-matrix'; +import Point from '@mapbox/point-geometry'; import type { FeatureState } from '../../style-spec/expression'; 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'; @@ -34,18 +35,29 @@ class FillExtrusionStyleLayer extends StyleLayer { return translateDistance(this.paint.get('fill-extrusion-translate')); } - queryIntersectsFeature(queryGeometry: Array>, + queryIntersectsFeature(queryGeometry: Array, feature: VectorTileFeature, featureState: FeatureState, geometry: Array>, zoom: number, transform: Transform, - pixelsToTileUnits: number): boolean { + pixelsToTileUnits: number, + pixelPosMatrix: Float32Array): boolean | number { + const translatedPolygon = translate(queryGeometry, this.paint.get('fill-extrusion-translate'), this.paint.get('fill-extrusion-translate-anchor'), transform.angle, pixelsToTileUnits); - return multiPolygonIntersectsMultiPolygon(translatedPolygon, geometry); + + const height = this.paint.get('fill-extrusion-height').evaluate(feature, featureState); + const base = this.paint.get('fill-extrusion-base').evaluate(feature, featureState); + + const projectedQueryGeometry = projectQueryGeometry(translatedPolygon, pixelPosMatrix, transform, 0); + + const projected = projectExtrusion(geometry, base, height, pixelPosMatrix); + const projectedBase = projected[0]; + const projectedTop = projected[1]; + return checkIntersection(projectedBase, projectedTop, projectedQueryGeometry); } hasOffscreenPass() { @@ -60,4 +72,145 @@ class FillExtrusionStyleLayer extends StyleLayer { } } +function dot(a, b) { + return a.x * b.x + a.y * b.y; +} + +function getIntersectionDistance(projectedQueryGeometry: Array, projectedFace: Array) { + + if (projectedQueryGeometry.length === 1) { + // For point queries calculate the z at which the point intersects the face + // using barycentric coordinates. + + // Find the barycentric coordinates of the projected point within the first + // triangle of the face, using only the xy plane. It doesn't matter if the + // point is outside the first triangle because all the triangles in the face + // are in the same plane. + const a = projectedFace[0]; + const b = projectedFace[1]; + const c = projectedFace[3]; + const p = projectedQueryGeometry[0]; + + const ab = b.sub(a); + const ac = c.sub(a); + const ap = p.sub(a); + + const dotABAB = dot(ab, ab); + const dotABAC = dot(ab, ac); + const dotACAC = dot(ac, ac); + const dotAPAB = dot(ap, ab); + const dotAPAC = dot(ap, ac); + const denom = dotABAB * dotACAC - dotABAC * dotABAC; + const v = (dotACAC * dotAPAB - dotABAC * dotAPAC) / denom; + const w = (dotABAB * dotAPAC - dotABAC * dotAPAB) / denom; + const u = 1 - v - w; + + // Use the barycentric weighting along with the original triangle z coordinates to get the point of intersection. + return a.z * u + b.z * v + c.z * w; + + } else { + // The counts as closest is less clear when the query is a box. This + // returns the distance to the nearest point on the face, whether it is + // within the query or not. It could be more correct to return the + // distance to the closest point within the query box but this would be + // more complicated and expensive to calculate with little benefit. + let closestDistance = Infinity; + for (const p of projectedFace) { + closestDistance = Math.min(closestDistance, p.z); + } + return closestDistance; + } +} + +function checkIntersection(projectedBase: Array, projectedTop: Array, projectedQueryGeometry: Array) { + let closestDistance = Infinity; + + if (polygonIntersectsMultiPolygon(projectedQueryGeometry, projectedTop)) { + closestDistance = getIntersectionDistance(projectedQueryGeometry, projectedTop[0]); + } + + for (let r = 0; r < projectedTop.length; r++) { + const ringTop = projectedTop[r]; + const ringBase = projectedBase[r]; + for (let p = 0; p < ringTop.length - 1; p++) { + const topA = ringTop[p]; + const topB = ringTop[p + 1]; + const baseA = ringBase[p]; + const baseB = ringBase[p + 1]; + const face = [topA, topB, baseB, baseA, topA]; + if (polygonIntersectsPolygon(projectedQueryGeometry, face)) { + closestDistance = Math.min(closestDistance, getIntersectionDistance(projectedQueryGeometry, face)); + } + } + } + + return closestDistance === Infinity ? false : closestDistance; +} + +/* + * Project the geometry using matrix `m`. This is essentially doing + * `vec4.transformMat4([], [p.x, p.y, z, 1], m)` but the multiplication + * is inlined so that parts of the projection that are the same across + * different points can only be done once. This produced a measurable + * performance improvement. + */ +function projectExtrusion(geometry: Array>, zBase: number, zTop: number, m: Float32Array) { + const projectedBase = []; + const projectedTop = []; + + const baseXZ = m[8] * zBase; + const baseYZ = m[9] * zBase; + const baseZZ = m[10] * zBase; + const baseWZ = m[11] * zBase; + const topXZ = m[8] * zTop; + const topYZ = m[9] * zTop; + const topZZ = m[10] * zTop; + const topWZ = m[11] * zTop; + + for (const r of geometry) { + const ringBase = []; + const ringTop = []; + for (const p of r) { + const x = p.x; + const y = p.y; + + const sX = m[0] * x + m[4] * y + m[12]; + const sY = m[1] * x + m[5] * y + m[13]; + const sZ = m[2] * x + m[6] * y + m[14]; + const sW = m[3] * x + m[7] * y + m[15]; + + const baseX = sX + baseXZ; + const baseY = sY + baseYZ; + const baseZ = sZ + baseZZ; + const baseW = sW + baseWZ; + + const topX = sX + topXZ; + const topY = sY + topYZ; + const topZ = sZ + topZZ; + const topW = sW + topWZ; + + const b = new Point(baseX / baseW, baseY / baseW); + b.z = baseZ / baseW; + ringBase.push(b); + + const t = new Point(topX / topW, topY / topW); + t.z = topZ / topW; + ringTop.push(t); + } + projectedBase.push(ringBase); + projectedTop.push(ringTop); + } + return [projectedBase, projectedTop]; +} + +function projectQueryGeometry(queryGeometry: Array, pixelPosMatrix: Float32Array, transform: Transform, z: number) { + const projectedQueryGeometry = []; + for (const p of queryGeometry) { + const v = [p.x, p.y, z, 1]; + vec4.transformMat4(v, v, pixelPosMatrix); + projectedQueryGeometry.push(new Point(v[0] / v[3], v[1] / v[3])); + } + return projectedQueryGeometry; +} + export default FillExtrusionStyleLayer; diff --git a/src/style/style_layer/fill_style_layer.js b/src/style/style_layer/fill_style_layer.js index 8f394fbf872..9d46e9cad1b 100644 --- a/src/style/style_layer/fill_style_layer.js +++ b/src/style/style_layer/fill_style_layer.js @@ -3,7 +3,7 @@ import StyleLayer from '../style_layer'; import FillBucket from '../../data/bucket/fill_bucket'; -import { multiPolygonIntersectsMultiPolygon } from '../../util/intersection_tests'; +import { polygonIntersectsMultiPolygon } from '../../util/intersection_tests'; import { translateDistance, translate } from '../query_utils'; import properties from './fill_style_layer_properties'; import { Transitionable, Transitioning, PossiblyEvaluated } from '../properties'; @@ -42,7 +42,7 @@ class FillStyleLayer extends StyleLayer { return translateDistance(this.paint.get('fill-translate')); } - queryIntersectsFeature(queryGeometry: Array>, + queryIntersectsFeature(queryGeometry: Array, feature: VectorTileFeature, featureState: FeatureState, geometry: Array>, @@ -53,7 +53,7 @@ class FillStyleLayer extends StyleLayer { this.paint.get('fill-translate'), this.paint.get('fill-translate-anchor'), transform.angle, pixelsToTileUnits); - return multiPolygonIntersectsMultiPolygon(translatedPolygon, geometry); + return polygonIntersectsMultiPolygon(translatedPolygon, geometry); } } diff --git a/src/style/style_layer/line_style_layer.js b/src/style/style_layer/line_style_layer.js index f11b5015bf8..224836e4ebb 100644 --- a/src/style/style_layer/line_style_layer.js +++ b/src/style/style_layer/line_style_layer.js @@ -5,7 +5,7 @@ import Point from '@mapbox/point-geometry'; import StyleLayer from '../style_layer'; import LineBucket from '../../data/bucket/line_bucket'; import { RGBAImage } from '../../util/image'; -import { multiPolygonIntersectsBufferedMultiLine } from '../../util/intersection_tests'; +import { polygonIntersectsBufferedMultiLine } from '../../util/intersection_tests'; import { getMaximumPaintValue, translateDistance, translate } from '../query_utils'; import properties from './line_style_layer_properties'; import { extend } from '../../util/util'; @@ -90,7 +90,7 @@ class LineStyleLayer extends StyleLayer { return width / 2 + Math.abs(offset) + translateDistance(this.paint.get('line-translate')); } - queryIntersectsFeature(queryGeometry: Array>, + queryIntersectsFeature(queryGeometry: Array, feature: VectorTileFeature, featureState: FeatureState, geometry: Array>, @@ -108,7 +108,7 @@ class LineStyleLayer extends StyleLayer { if (lineOffset) { geometry = offsetLine(geometry, lineOffset * pixelsToTileUnits); } - return multiPolygonIntersectsBufferedMultiLine(translatedPolygon, geometry, halfWidth); + return polygonIntersectsBufferedMultiLine(translatedPolygon, geometry, halfWidth); } } diff --git a/src/util/intersection_tests.js b/src/util/intersection_tests.js index 790be48e173..6121ed54346 100644 --- a/src/util/intersection_tests.js +++ b/src/util/intersection_tests.js @@ -2,9 +2,9 @@ import { isCounterClockwise } from './util'; -import type Point from '@mapbox/point-geometry'; +import Point from '@mapbox/point-geometry'; -export { multiPolygonIntersectsBufferedPoint, multiPolygonIntersectsBufferedMultiPoint, multiPolygonIntersectsMultiPolygon, multiPolygonIntersectsBufferedMultiLine, polygonIntersectsPolygon, distToSegmentSquared }; +export { polygonIntersectsBufferedPoint, polygonIntersectsMultiPolygon, polygonIntersectsBufferedMultiLine, polygonIntersectsPolygon, distToSegmentSquared, polygonIntersectsBox }; type Line = Array; type MultiLine = Array; @@ -26,67 +26,47 @@ function polygonIntersectsPolygon(polygonA: Polygon, polygonB: Polygon) { return false; } -function multiPolygonIntersectsBufferedPoint(multiPolygon: MultiPolygon, point: Point, radius: number) { - for (let j = 0; j < multiPolygon.length; j++) { - const polygon = multiPolygon[j]; - if (polygonContainsPoint(polygon, point)) return true; - if (pointIntersectsBufferedLine(point, polygon, radius)) return true; - } +function polygonIntersectsBufferedPoint(polygon: Polygon, point: Point, radius: number) { + 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; -} - -function multiPolygonIntersectsMultiPolygon(multiPolygonA: MultiPolygon, multiPolygonB: MultiPolygon) { +function polygonIntersectsMultiPolygon(polygon: Polygon, multiPolygon: MultiPolygon) { - if (multiPolygonA.length === 1 && multiPolygonA[0].length === 1) { - return multiPolygonContainsPoint(multiPolygonB, multiPolygonA[0][0]); + if (polygon.length === 1) { + return multiPolygonContainsPoint(multiPolygon, polygon[0]); } - for (let m = 0; m < multiPolygonB.length; m++) { - const ring = multiPolygonB[m]; + for (let m = 0; m < multiPolygon.length; m++) { + const ring = multiPolygon[m]; for (let n = 0; n < ring.length; n++) { - if (multiPolygonContainsPoint(multiPolygonA, ring[n])) return true; + if (polygonContainsPoint(polygon, ring[n])) return true; } } - for (let j = 0; j < multiPolygonA.length; j++) { - const polygon = multiPolygonA[j]; - for (let i = 0; i < polygon.length; i++) { - if (multiPolygonContainsPoint(multiPolygonB, polygon[i])) return true; - } + for (let i = 0; i < polygon.length; i++) { + if (multiPolygonContainsPoint(multiPolygon, polygon[i])) return true; + } - for (let k = 0; k < multiPolygonB.length; k++) { - if (lineIntersectsLine(polygon, multiPolygonB[k])) return true; - } + for (let k = 0; k < multiPolygon.length; k++) { + if (lineIntersectsLine(polygon, multiPolygon[k])) return true; } return false; } -function multiPolygonIntersectsBufferedMultiLine(multiPolygon: MultiPolygon, multiLine: MultiLine, radius: number) { +function polygonIntersectsBufferedMultiLine(polygon: Polygon, multiLine: MultiLine, radius: number) { for (let i = 0; i < multiLine.length; i++) { const line = multiLine[i]; - for (let j = 0; j < multiPolygon.length; j++) { - const polygon = multiPolygon[j]; - - if (polygon.length >= 3) { - for (let k = 0; k < line.length; k++) { - if (polygonContainsPoint(polygon, line[k])) return true; - } + if (polygon.length >= 3) { + for (let k = 0; k < line.length; k++) { + if (polygonContainsPoint(polygon, line[k])) return true; } - - if (lineIntersectsBufferedLine(polygon, line, radius)) return true; } + + if (lineIntersectsBufferedLine(polygon, line, radius)) return true; } return false; } @@ -181,3 +161,48 @@ function polygonContainsPoint(ring: Ring, p: Point) { } return c; } + +function polygonIntersectsBox(ring: Ring, boxX1: number, boxY1: number, boxX2: number, boxY2: number) { + for (const p of ring) { + if (boxX1 <= p.x && + boxY1 <= p.y && + boxX2 >= p.x && + boxY2 >= p.y) return true; + } + + const corners = [ + new Point(boxX1, boxY1), + new Point(boxX1, boxY2), + new Point(boxX2, boxY2), + new Point(boxX2, boxY1)]; + + if (ring.length > 2) { + for (const corner of corners) { + if (polygonContainsPoint(ring, corner)) return true; + } + } + + for (let i = 0; i < ring.length - 1; i++) { + const p1 = ring[i]; + const p2 = ring[i + 1]; + if (edgeIntersectsBox(p1, p2, corners)) return true; + } + + return false; +} + +function edgeIntersectsBox(e1: Point, e2: Point, corners: Array) { + const tl = corners[0]; + const br = corners[2]; + // the edge and box do not intersect in either the x or y dimensions + if (((e1.x < tl.x) && (e2.x < tl.x)) || + ((e1.x > br.x) && (e2.x > br.x)) || + ((e1.y < tl.y) && (e2.y < tl.y)) || + ((e1.y > br.y) && (e2.y > br.y))) return false; + + // check if all corners of the box are on the same side of the edge + const dir = isCounterClockwise(e1, e2, corners[0]); + return dir !== isCounterClockwise(e1, e2, corners[1]) || + dir !== isCounterClockwise(e1, e2, corners[2]) || + dir !== isCounterClockwise(e1, e2, corners[3]); +} diff --git a/test/integration/query-tests/fill-extrusion/base-in/expected.json b/test/integration/query-tests/fill-extrusion/base-in/expected.json new file mode 100644 index 00000000000..30655dcba5b --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/base-in/expected.json @@ -0,0 +1,35 @@ +[ + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.63532972467448 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.4556999206543, + 43.642535173141056 + ], + [ + -79.4556999206543, + 43.63532972467448 + ], + [ + -79.46205139160156, + 43.63532972467448 + ] + ] + ] + }, + "type": "Feature", + "properties": {}, + "source": "zones", + "state": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/fill-extrusion/base-in/style.json b/test/integration/query-tests/fill-extrusion/base-in/style.json new file mode 100644 index 00000000000..f5ad7456511 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/base-in/style.json @@ -0,0 +1,75 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "width": 200, + "height": 400, + "queryGeometry": [ + 176, + 214 + ] + } + }, + "pitch": 60, + "center": [ + -79.46462631225589, + 43.64750394449096 + ], + "zoom": 13, + "sources": { + "zones": { + "type": "geojson", + "maxzoom": 13, + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.635329724674484 + ] + ] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "zones", + "type": "fill-extrusion", + "source": "zones", + "paint": { + "fill-extrusion-color": "#ccc", + "fill-extrusion-height": 1000, + "fill-extrusion-base": 700 + } + } + ] +} diff --git a/test/integration/query-tests/fill-extrusion/base-out/expected.json b/test/integration/query-tests/fill-extrusion/base-out/expected.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/base-out/expected.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/integration/query-tests/fill-extrusion/base-out/style.json b/test/integration/query-tests/fill-extrusion/base-out/style.json new file mode 100644 index 00000000000..a2a98f7cba3 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/base-out/style.json @@ -0,0 +1,75 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "width": 200, + "height": 400, + "queryGeometry": [ + 176, + 217 + ] + } + }, + "pitch": 60, + "center": [ + -79.46462631225589, + 43.64750394449096 + ], + "zoom": 13, + "sources": { + "zones": { + "type": "geojson", + "maxzoom": 13, + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.635329724674484 + ] + ] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "zones", + "type": "fill-extrusion", + "source": "zones", + "paint": { + "fill-extrusion-color": "#ccc", + "fill-extrusion-height": 1000, + "fill-extrusion-base": 700 + } + } + ] +} diff --git a/test/integration/query-tests/fill-extrusion/box-in/expected.json b/test/integration/query-tests/fill-extrusion/box-in/expected.json new file mode 100644 index 00000000000..27e2e01d665 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/box-in/expected.json @@ -0,0 +1,72 @@ +[ + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.63532972467448 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.4556999206543, + 43.642535173141056 + ], + [ + -79.4556999206543, + 43.63532972467448 + ], + [ + -79.46205139160156, + 43.63532972467448 + ] + ] + ] + }, + "type": "Feature", + "properties": { + "position": "bottom" + }, + "source": "zones", + "state": {} + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46462631225586, + 43.64750394449095 + ], + [ + -79.46462631225586, + 43.65458373355483 + ], + [ + -79.45793151855469, + 43.65458373355483 + ], + [ + -79.45793151855469, + 43.64750394449095 + ], + [ + -79.46462631225586, + 43.64750394449095 + ] + ] + ] + }, + "type": "Feature", + "properties": { + "position": "middle" + }, + "source": "zones", + "state": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/fill-extrusion/box-in/style.json b/test/integration/query-tests/fill-extrusion/box-in/style.json new file mode 100644 index 00000000000..34ce9acddf2 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/box-in/style.json @@ -0,0 +1,141 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "width": 200, + "height": 400, + "queryGeometry": [ + [100,195], + [136,240] + ] + } + }, + "pitch": 60, + "center": [ + -79.46462631225589, + 43.64750394449096 + ], + "zoom": 13, + "sources": { + "zones": { + "type": "geojson", + "maxzoom": 13, + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "position": "middle" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46462631225589, + 43.64750394449096 + ], + [ + -79.45793151855469, + 43.64750394449096 + ], + [ + -79.45793151855469, + 43.6545837335548 + ], + [ + -79.46462631225589, + 43.6545837335548 + ], + [ + -79.46462631225589, + 43.64750394449096 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "position": "bottom" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.635329724674484 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "position": "top" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46788787841795, + 43.65557732137429 + ], + [ + -79.45700806884764, + 43.65557732137429 + ], + [ + -79.45700806884764, + 43.66290452383666 + ], + [ + -79.46788787841795, + 43.66290452383666 + ], + [ + -79.46788787841795, + 43.65557732137429 + ] + ] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "zones", + "type": "fill-extrusion", + "source": "zones", + "paint": { + "fill-extrusion-color": "#ccc", + "fill-extrusion-height": 1000 + } + } + ] +} diff --git a/test/integration/query-tests/fill-extrusion/box-out/expected.json b/test/integration/query-tests/fill-extrusion/box-out/expected.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/box-out/expected.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/integration/query-tests/fill-extrusion/box-out/style.json b/test/integration/query-tests/fill-extrusion/box-out/style.json new file mode 100644 index 00000000000..2bda8b401b6 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/box-out/style.json @@ -0,0 +1,141 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "width": 200, + "height": 400, + "queryGeometry": [ + [100,205], + [130,240] + ] + } + }, + "pitch": 60, + "center": [ + -79.46462631225589, + 43.64750394449096 + ], + "zoom": 13, + "sources": { + "zones": { + "type": "geojson", + "maxzoom": 13, + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "position": "middle" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46462631225589, + 43.64750394449096 + ], + [ + -79.45793151855469, + 43.64750394449096 + ], + [ + -79.45793151855469, + 43.6545837335548 + ], + [ + -79.46462631225589, + 43.6545837335548 + ], + [ + -79.46462631225589, + 43.64750394449096 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "position": "bottom" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.635329724674484 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "position": "top" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46788787841795, + 43.65557732137429 + ], + [ + -79.45700806884764, + 43.65557732137429 + ], + [ + -79.45700806884764, + 43.66290452383666 + ], + [ + -79.46788787841795, + 43.66290452383666 + ], + [ + -79.46788787841795, + 43.65557732137429 + ] + ] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "zones", + "type": "fill-extrusion", + "source": "zones", + "paint": { + "fill-extrusion-color": "#ccc", + "fill-extrusion-height": 1000 + } + } + ] +} diff --git a/test/integration/query-tests/fill-extrusion/side-in/expected.json b/test/integration/query-tests/fill-extrusion/side-in/expected.json new file mode 100644 index 00000000000..30655dcba5b --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/side-in/expected.json @@ -0,0 +1,35 @@ +[ + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.63532972467448 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.4556999206543, + 43.642535173141056 + ], + [ + -79.4556999206543, + 43.63532972467448 + ], + [ + -79.46205139160156, + 43.63532972467448 + ] + ] + ] + }, + "type": "Feature", + "properties": {}, + "source": "zones", + "state": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/fill-extrusion/side-in/style.json b/test/integration/query-tests/fill-extrusion/side-in/style.json new file mode 100644 index 00000000000..6c0508cdc53 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/side-in/style.json @@ -0,0 +1,74 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "width": 200, + "height": 400, + "queryGeometry": [ + 138, + 149 + ] + } + }, + "pitch": 60, + "center": [ + -79.46462631225589, + 43.64750394449096 + ], + "zoom": 13, + "sources": { + "zones": { + "type": "geojson", + "maxzoom": 13, + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.635329724674484 + ] + ] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "zones", + "type": "fill-extrusion", + "source": "zones", + "paint": { + "fill-extrusion-color": "#ccc", + "fill-extrusion-height": 1000 + } + } + ] +} diff --git a/test/integration/query-tests/fill-extrusion/side-out/expected.json b/test/integration/query-tests/fill-extrusion/side-out/expected.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/side-out/expected.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/integration/query-tests/fill-extrusion/side-out/style.json b/test/integration/query-tests/fill-extrusion/side-out/style.json new file mode 100644 index 00000000000..0e3eedc0af5 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/side-out/style.json @@ -0,0 +1,74 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "width": 200, + "height": 400, + "queryGeometry": [ + 136, + 149 + ] + } + }, + "pitch": 60, + "center": [ + -79.46462631225589, + 43.64750394449096 + ], + "zoom": 13, + "sources": { + "zones": { + "type": "geojson", + "maxzoom": 13, + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.635329724674484 + ] + ] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "zones", + "type": "fill-extrusion", + "source": "zones", + "paint": { + "fill-extrusion-color": "#ccc", + "fill-extrusion-height": 1000 + } + } + ] +} diff --git a/test/integration/query-tests/fill-extrusion/sort-concave-inner/expected.json b/test/integration/query-tests/fill-extrusion/sort-concave-inner/expected.json new file mode 100644 index 00000000000..158c2a25462 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/sort-concave-inner/expected.json @@ -0,0 +1,86 @@ +[ + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -20.390625, + 25.16517336866393 + ], + [ + -20.390625, + 39.90973623453718 + ], + [ + 52.03125, + 39.90973623453718 + ], + [ + 52.03125, + 25.16517336866393 + ], + [ + -20.390625, + 25.16517336866393 + ] + ] + ] + }, + "type": "Feature", + "properties": {}, + "source": "zones", + "state": {} + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 89.296875, + 73.82482034613932 + ], + [ + 92.109375, + 49.38237278700956 + ], + [ + -52.03125, + 50.289339253291786 + ], + [ + -54.140625, + 11.867350911459312 + ], + [ + 16.171875, + 11.178401873711778 + ], + [ + 15.46875, + -17.308687886770016 + ], + [ + -73.125, + -15.284185114076436 + ], + [ + -71.015625, + 69.65708627301174 + ], + [ + 89.296875, + 73.82482034613932 + ] + ] + ] + }, + "type": "Feature", + "properties": { + "outside": true + }, + "source": "zones", + "state": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/fill-extrusion/sort-concave-inner/style.json b/test/integration/query-tests/fill-extrusion/sort-concave-inner/style.json new file mode 100644 index 00000000000..bd18c5fab65 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/sort-concave-inner/style.json @@ -0,0 +1,125 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "width": 200, + "height": 400, + "queryGeometry": [ + 156, + 131 + ] + } + }, + "pitch": 60, + "center": [ + 0, + 0 + ], + "zoom": 0, + "sources": { + "zones": { + "type": "geojson", + "maxzoom": 1, + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "outside": true + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 89.296875, + 73.82482034613932 + ], + [ + -71.015625, + 69.65708627301174 + ], + [ + -73.125, + -15.28418511407642 + ], + [ + 15.468749999999998, + -17.308687886770024 + ], + [ + 16.171875, + 11.178401873711785 + ], + [ + -54.140625, + 11.867350911459308 + ], + [ + -52.03125, + 50.28933925329178 + ], + [ + 92.10937499999999, + 49.38237278700955 + ], + [ + 89.296875, + 73.82482034613932 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "properties": { + "inside": true + }, + "coordinates": [ + [ + [ + -20.390625, + 25.16517336866393 + ], + [ + 52.03125, + 25.16517336866393 + ], + [ + 52.03125, + 39.90973623453719 + ], + [ + -20.390625, + 39.90973623453719 + ], + [ + -20.390625, + 25.16517336866393 + ] + ] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "zones", + "type": "fill-extrusion", + "source": "zones", + "paint": { + "fill-extrusion-color": "#ccc", + "fill-extrusion-height": 10000000 + } + } + ] +} diff --git a/test/integration/query-tests/fill-extrusion/sort-concave-outer/expected.json b/test/integration/query-tests/fill-extrusion/sort-concave-outer/expected.json new file mode 100644 index 00000000000..d9195fc86c8 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/sort-concave-outer/expected.json @@ -0,0 +1,86 @@ +[ + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 89.296875, + 73.82482034613932 + ], + [ + 92.109375, + 49.38237278700956 + ], + [ + -52.03125, + 50.289339253291786 + ], + [ + -54.140625, + 11.867350911459312 + ], + [ + 16.171875, + 11.178401873711778 + ], + [ + 15.46875, + -17.308687886770016 + ], + [ + -73.125, + -15.284185114076436 + ], + [ + -71.015625, + 69.65708627301174 + ], + [ + 89.296875, + 73.82482034613932 + ] + ] + ] + }, + "type": "Feature", + "properties": { + "outside": true + }, + "source": "zones", + "state": {} + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -20.390625, + 25.16517336866393 + ], + [ + -20.390625, + 39.90973623453718 + ], + [ + 52.03125, + 39.90973623453718 + ], + [ + 52.03125, + 25.16517336866393 + ], + [ + -20.390625, + 25.16517336866393 + ] + ] + ] + }, + "type": "Feature", + "properties": {}, + "source": "zones", + "state": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/fill-extrusion/sort-concave-outer/style.json b/test/integration/query-tests/fill-extrusion/sort-concave-outer/style.json new file mode 100644 index 00000000000..24f1e191857 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/sort-concave-outer/style.json @@ -0,0 +1,125 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "width": 200, + "height": 400, + "queryGeometry": [ + 106, + 131 + ] + } + }, + "pitch": 60, + "center": [ + 0, + 0 + ], + "zoom": 0, + "sources": { + "zones": { + "type": "geojson", + "maxzoom": 1, + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "outside": true + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 89.296875, + 73.82482034613932 + ], + [ + -71.015625, + 69.65708627301174 + ], + [ + -73.125, + -15.28418511407642 + ], + [ + 15.468749999999998, + -17.308687886770024 + ], + [ + 16.171875, + 11.178401873711785 + ], + [ + -54.140625, + 11.867350911459308 + ], + [ + -52.03125, + 50.28933925329178 + ], + [ + 92.10937499999999, + 49.38237278700955 + ], + [ + 89.296875, + 73.82482034613932 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "properties": { + "inside": true + }, + "coordinates": [ + [ + [ + -20.390625, + 25.16517336866393 + ], + [ + 52.03125, + 25.16517336866393 + ], + [ + 52.03125, + 39.90973623453719 + ], + [ + -20.390625, + 39.90973623453719 + ], + [ + -20.390625, + 25.16517336866393 + ] + ] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "zones", + "type": "fill-extrusion", + "source": "zones", + "paint": { + "fill-extrusion-color": "#ccc", + "fill-extrusion-height": 10000000 + } + } + ] +} diff --git a/test/integration/query-tests/fill-extrusion/sort-rotated/expected.json b/test/integration/query-tests/fill-extrusion/sort-rotated/expected.json new file mode 100644 index 00000000000..ce524bff662 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/sort-rotated/expected.json @@ -0,0 +1,107 @@ +[ + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46788787841797, + 43.655577321374295 + ], + [ + -79.46788787841797, + 43.66290452383666 + ], + [ + -79.45700883865356, + 43.66290452383666 + ], + [ + -79.45700883865356, + 43.655577321374295 + ], + [ + -79.46788787841797, + 43.655577321374295 + ] + ] + ] + }, + "type": "Feature", + "properties": { + "position": "top" + }, + "source": "zones", + "state": {} + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46462631225586, + 43.64750394449095 + ], + [ + -79.46462631225586, + 43.65458373355483 + ], + [ + -79.45793151855469, + 43.65458373355483 + ], + [ + -79.45793151855469, + 43.64750394449095 + ], + [ + -79.46462631225586, + 43.64750394449095 + ] + ] + ] + }, + "type": "Feature", + "properties": { + "position": "middle" + }, + "source": "zones", + "state": {} + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.63532972467448 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.4556999206543, + 43.642535173141056 + ], + [ + -79.4556999206543, + 43.63532972467448 + ], + [ + -79.46205139160156, + 43.63532972467448 + ] + ] + ] + }, + "type": "Feature", + "properties": { + "position": "bottom" + }, + "source": "zones", + "state": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/fill-extrusion/sort-rotated/style.json b/test/integration/query-tests/fill-extrusion/sort-rotated/style.json new file mode 100644 index 00000000000..338d9d1f016 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/sort-rotated/style.json @@ -0,0 +1,142 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "width": 200, + "height": 400, + "queryGeometry": [ + 56, + 131 + ] + } + }, + "bearing": 180, + "pitch": 60, + "center": [ + -79.46462631225589, + 43.64750394449096 + ], + "zoom": 13, + "sources": { + "zones": { + "type": "geojson", + "maxzoom": 13, + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "position": "middle" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46462631225589, + 43.64750394449096 + ], + [ + -79.45793151855469, + 43.64750394449096 + ], + [ + -79.45793151855469, + 43.6545837335548 + ], + [ + -79.46462631225589, + 43.6545837335548 + ], + [ + -79.46462631225589, + 43.64750394449096 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "position": "bottom" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.635329724674484 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "position": "top" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46788787841795, + 43.65557732137429 + ], + [ + -79.45700806884764, + 43.65557732137429 + ], + [ + -79.45700806884764, + 43.66290452383666 + ], + [ + -79.46788787841795, + 43.66290452383666 + ], + [ + -79.46788787841795, + 43.65557732137429 + ] + ] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "zones", + "type": "fill-extrusion", + "source": "zones", + "paint": { + "fill-extrusion-color": "#ccc", + "fill-extrusion-height": 1000 + } + } + ] +} diff --git a/test/integration/query-tests/fill-extrusion/sort/expected.json b/test/integration/query-tests/fill-extrusion/sort/expected.json new file mode 100644 index 00000000000..81de095678f --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/sort/expected.json @@ -0,0 +1,107 @@ +[ + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.63532972467448 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.4556999206543, + 43.642535173141056 + ], + [ + -79.4556999206543, + 43.63532972467448 + ], + [ + -79.46205139160156, + 43.63532972467448 + ] + ] + ] + }, + "type": "Feature", + "properties": { + "position": "bottom" + }, + "source": "zones", + "state": {} + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46462631225586, + 43.64750394449095 + ], + [ + -79.46462631225586, + 43.65458373355483 + ], + [ + -79.45793151855469, + 43.65458373355483 + ], + [ + -79.45793151855469, + 43.64750394449095 + ], + [ + -79.46462631225586, + 43.64750394449095 + ] + ] + ] + }, + "type": "Feature", + "properties": { + "position": "middle" + }, + "source": "zones", + "state": {} + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46788787841797, + 43.655577321374295 + ], + [ + -79.46788787841797, + 43.66290452383666 + ], + [ + -79.45700883865356, + 43.66290452383666 + ], + [ + -79.45700883865356, + 43.655577321374295 + ], + [ + -79.46788787841797, + 43.655577321374295 + ] + ] + ] + }, + "type": "Feature", + "properties": { + "position": "top" + }, + "source": "zones", + "state": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/fill-extrusion/sort/style.json b/test/integration/query-tests/fill-extrusion/sort/style.json new file mode 100644 index 00000000000..1de5f29d098 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/sort/style.json @@ -0,0 +1,141 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "width": 200, + "height": 400, + "queryGeometry": [ + 156, + 131 + ] + } + }, + "pitch": 60, + "center": [ + -79.46462631225589, + 43.64750394449096 + ], + "zoom": 13, + "sources": { + "zones": { + "type": "geojson", + "maxzoom": 13, + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "position": "middle" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46462631225589, + 43.64750394449096 + ], + [ + -79.45793151855469, + 43.64750394449096 + ], + [ + -79.45793151855469, + 43.6545837335548 + ], + [ + -79.46462631225589, + 43.6545837335548 + ], + [ + -79.46462631225589, + 43.64750394449096 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "position": "bottom" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.635329724674484 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "position": "top" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46788787841795, + 43.65557732137429 + ], + [ + -79.45700806884764, + 43.65557732137429 + ], + [ + -79.45700806884764, + 43.66290452383666 + ], + [ + -79.46788787841795, + 43.66290452383666 + ], + [ + -79.46788787841795, + 43.65557732137429 + ] + ] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "zones", + "type": "fill-extrusion", + "source": "zones", + "paint": { + "fill-extrusion-color": "#ccc", + "fill-extrusion-height": 1000 + } + } + ] +} diff --git a/test/integration/query-tests/fill-extrusion/top-in/expected.json b/test/integration/query-tests/fill-extrusion/top-in/expected.json new file mode 100644 index 00000000000..30655dcba5b --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/top-in/expected.json @@ -0,0 +1,35 @@ +[ + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.63532972467448 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.4556999206543, + 43.642535173141056 + ], + [ + -79.4556999206543, + 43.63532972467448 + ], + [ + -79.46205139160156, + 43.63532972467448 + ] + ] + ] + }, + "type": "Feature", + "properties": {}, + "source": "zones", + "state": {} + } +] \ No newline at end of file diff --git a/test/integration/query-tests/fill-extrusion/top-in/style.json b/test/integration/query-tests/fill-extrusion/top-in/style.json new file mode 100644 index 00000000000..f7be19471e1 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/top-in/style.json @@ -0,0 +1,74 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "width": 200, + "height": 400, + "queryGeometry": [ + 156, + 89 + ] + } + }, + "pitch": 60, + "center": [ + -79.46462631225589, + 43.64750394449096 + ], + "zoom": 13, + "sources": { + "zones": { + "type": "geojson", + "maxzoom": 13, + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.635329724674484 + ] + ] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "zones", + "type": "fill-extrusion", + "source": "zones", + "paint": { + "fill-extrusion-color": "#ccc", + "fill-extrusion-height": 1000 + } + } + ] +} diff --git a/test/integration/query-tests/fill-extrusion/top-out/expected.json b/test/integration/query-tests/fill-extrusion/top-out/expected.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/top-out/expected.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/integration/query-tests/fill-extrusion/top-out/style.json b/test/integration/query-tests/fill-extrusion/top-out/style.json new file mode 100644 index 00000000000..524174b04d5 --- /dev/null +++ b/test/integration/query-tests/fill-extrusion/top-out/style.json @@ -0,0 +1,74 @@ +{ + "version": 8, + "metadata": { + "test": { + "debug": true, + "width": 200, + "height": 400, + "queryGeometry": [ + 156, + 87 + ] + } + }, + "pitch": 60, + "center": [ + -79.46462631225589, + 43.64750394449096 + ], + "zoom": 13, + "sources": { + "zones": { + "type": "geojson", + "maxzoom": 13, + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -79.46205139160156, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.635329724674484 + ], + [ + -79.45569992065428, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.642535173141056 + ], + [ + -79.46205139160156, + 43.635329724674484 + ] + ] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "zones", + "type": "fill-extrusion", + "source": "zones", + "paint": { + "fill-extrusion-color": "#ccc", + "fill-extrusion-height": 1000 + } + } + ] +} diff --git a/test/unit/source/source_cache.test.js b/test/unit/source/source_cache.test.js index d49b050642f..67b78d15aba 100644 --- a/test/unit/source/source_cache.test.js +++ b/test/unit/source/source_cache.test.js @@ -5,10 +5,10 @@ import Tile from '../../../src/source/tile'; import { OverscaledTileID } from '../../../src/source/tile_id'; import Transform from '../../../src/geo/transform'; import LngLat from '../../../src/geo/lng_lat'; +import Point from '@mapbox/point-geometry'; import { Event, ErrorEvent, Evented } from '../../../src/util/evented'; import { extend } from '../../../src/util/util'; import browser from '../../../src/util/browser'; -import MercatorCoordinate from '../../../src/geo/mercator_coordinate'; // Add a mocked source type for use in these tests function MockSourceType(id, sourceOptions, _dispatcher, eventedParent) { @@ -1228,20 +1228,32 @@ test('SourceCache#clearTiles', (t) => { test('SourceCache#tilesIn', (t) => { t.test('graceful response before source loaded', (t) => { + const tr = new Transform(); + tr.width = 512; + tr.height = 512; + tr._calcMatrices(); const sourceCache = createSourceCache({ noLoad: true }); + sourceCache.transform = tr; sourceCache.onAdd(); t.same(sourceCache.tilesIn([ - new MercatorCoordinate(0.25, 0.125), - new MercatorCoordinate(0.75, 0.375) - ]), []); + new Point(0, 0), + new Point(512, 256) + ], 10, tr), []); t.end(); }); + function round(queryGeometry) { + return queryGeometry.map((p) => { + return p.round(); + }); + } + t.test('regular tiles', (t) => { const transform = new Transform(); - transform.resize(511, 511); + transform.resize(512, 512); transform.zoom = 1; + transform.center = new LngLat(0, 1); const sourceCache = createSourceCache({ loadTile(tile, callback) { @@ -1262,10 +1274,11 @@ test('SourceCache#tilesIn', (t) => { new OverscaledTileID(1, 0, 1, 0, 0).key ]); + transform._calcMatrices(); const tiles = sourceCache.tilesIn([ - new MercatorCoordinate(0.25, 0.125), - new MercatorCoordinate(0.75, 0.375) - ], 1); + new Point(0, 0), + new Point(512, 256) + ], 1, transform); tiles.sort((a, b) => { return a.tile.tileID.canonical.x - b.tile.tileID.canonical.x; }); tiles.forEach((result) => { delete result.tile.uid; }); @@ -1273,12 +1286,12 @@ test('SourceCache#tilesIn', (t) => { t.equal(tiles[0].tile.tileID.key, 1); t.equal(tiles[0].tile.tileSize, 512); t.equal(tiles[0].scale, 1); - t.deepEqual(tiles[0].queryGeometry, [[{x: 4096, y: 2048}, {x:12288, y: 6144}]]); + t.deepEqual(round(tiles[0].queryGeometry), [{x: 4096, y: 4050}, {x:12288, y: 8146}]); t.equal(tiles[1].tile.tileID.key, 33); t.equal(tiles[1].tile.tileSize, 512); t.equal(tiles[1].scale, 1); - t.deepEqual(tiles[1].queryGeometry, [[{x: -4096, y: 2048}, {x: 4096, y: 6144}]]); + t.deepEqual(round(tiles[1].queryGeometry), [{x: -4096, y: 4050}, {x: 4096, y: 8146}]); t.end(); } @@ -1302,8 +1315,9 @@ test('SourceCache#tilesIn', (t) => { sourceCache.on('data', (e) => { if (e.sourceDataType === 'metadata') { const transform = new Transform(); - transform.resize(512, 512); + transform.resize(1024, 1024); transform.zoom = 2.0; + transform.center = new LngLat(0, 1); sourceCache.update(transform); t.deepEqual(sourceCache.getIds(), [ @@ -1314,9 +1328,9 @@ test('SourceCache#tilesIn', (t) => { ]); const tiles = sourceCache.tilesIn([ - new MercatorCoordinate(0.25, 0.125), - new MercatorCoordinate(0.75, 0.375) - ], 1); + new Point(0, 0), + new Point(1024, 512) + ], 1, transform); tiles.sort((a, b) => { return a.tile.tileID.canonical.x - b.tile.tileID.canonical.x; }); tiles.forEach((result) => { delete result.tile.uid; }); @@ -1324,12 +1338,12 @@ test('SourceCache#tilesIn', (t) => { t.equal(tiles[0].tile.tileID.key, 2); t.equal(tiles[0].tile.tileSize, 1024); t.equal(tiles[0].scale, 1); - t.deepEqual(tiles[0].queryGeometry, [[{x: 4096, y: 2048}, {x:12288, y: 6144}]]); + t.deepEqual(round(tiles[0].queryGeometry), [{x: 4096, y: 4050}, {x:12288, y: 8146}]); t.equal(tiles[1].tile.tileID.key, 34); t.equal(tiles[1].tile.tileSize, 1024); t.equal(tiles[1].scale, 1); - t.deepEqual(tiles[1].queryGeometry, [[{x: -4096, y: 2048}, {x: 4096, y: 6144}]]); + t.deepEqual(round(tiles[1].queryGeometry), [{x: -4096, y: 4050}, {x: 4096, y: 8146}]); t.end(); } diff --git a/test/unit/style/style.test.js b/test/unit/style/style.test.js index 8facc942fcb..24f0577ad89 100644 --- a/test/unit/style/style.test.js +++ b/test/unit/style/style.test.js @@ -1778,7 +1778,7 @@ test('Style#queryRenderedFeatures', (t) => { const transform = new Transform(); transform.resize(512, 512); - function queryMapboxFeatures(layers, getFeatureState, queryGeom, scale, params) { + function queryMapboxFeatures(layers, getFeatureState, queryGeom, cameraQueryGeom, scale, params) { const features = { 'land': [{ type: 'Feature', diff --git a/yarn.lock b/yarn.lock index c4f40891da2..ed2ae7afbf7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5528,10 +5528,10 @@ gray-matter@^3.1.1: kind-of "^5.0.2" strip-bom-string "^1.0.0" -grid-index@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.0.0.tgz#ad2c5d54ce5b35437faff1d70a9aeb3d1d261110" - integrity sha1-rSxdVM5bNUN/r/HXCprrPR0mERA= +grid-index@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7" + integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA== "growl@~> 1.10.0": version "1.10.5"