Skip to content

Commit

Permalink
fix querying circles with -pitch-alignment=map
Browse files Browse the repository at this point in the history
`"circle-alignment-scaling": "map"` draws circles on the map plane
instead of the viewport plane. This fixes querying for those circles.
  • Loading branch information
ansis committed Mar 12, 2018
1 parent ebc3de8 commit b98a9f7
Show file tree
Hide file tree
Showing 24 changed files with 296 additions and 55 deletions.
15 changes: 7 additions & 8 deletions src/data/feature_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ 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,
cameraToCenterDistance: number,
posMatrix: Float32Array,
transform: Transform,
tileSize: number,
queryGeometry: Array<Array<Point>>,
queryPadding: number,
Expand Down Expand Up @@ -119,13 +119,13 @@ class FeatureIndex {

const matching = this.grid.query(minX - queryPadding, minY - queryPadding, maxX + queryPadding, maxY + queryPadding);
matching.sort(topDownFeatureComparator);
this.filterMatching(result, matching, this.featureIndexArray, queryGeometry, filter, params.layers, styleLayers, args.bearing, pixelsToTileUnits, args.cameraToCenterDistance, args.posMatrix);
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, args.cameraToCenterDistance, args.posMatrix);
this.filterMatching(result, matchingSymbols, args.collisionBoxArray, queryGeometry, filter, params.layers, styleLayers, pixelsToTileUnits, args.posMatrix, args.transform);

return result;
}
Expand All @@ -138,10 +138,9 @@ class FeatureIndex {
filter: FeatureFilter,
filterLayerIDs: Array<string>,
styleLayers: {[string]: StyleLayer},
bearing: number,
pixelsToTileUnits: number,
cameraToCenterDistance: number,
posMatrix: Float32Array
posMatrix: Float32Array,
transform: Transform
) {
let previousIndex;
for (let k = 0; k < matching.length; k++) {
Expand Down Expand Up @@ -179,7 +178,7 @@ class FeatureIndex {
if (!geometry) {
geometry = loadGeometry(feature);
}
if (!styleLayer.queryIntersectsFeature(queryGeometry, feature, geometry, this.z, bearing, pixelsToTileUnits, cameraToCenterDistance, posMatrix)) {
if (!styleLayer.queryIntersectsFeature(queryGeometry, feature, geometry, this.z, transform, pixelsToTileUnits, posMatrix)) {
continue;
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/geo/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,10 @@ class Transform {
this._alignedPosMatrixCache = {};
}

getPitchScaleFactor() {
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);
Expand Down
13 changes: 6 additions & 7 deletions src/source/query_features.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +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<Coordinate>,
params: { filter: FilterSpecification, layers: Array<string> },
zoom: number,
bearing: number,
transform: Transform,
collisionIndex: ?CollisionIndex) {
const pitchScaleFactor = sourceCache.transform.getPitchScaleFactor();
const tilesIn = sourceCache.tilesIn(queryGeometry, pitchScaleFactor);
const maxPitchScaleFactor = transform.maxPitchScaleFactor();
const tilesIn = sourceCache.tilesIn(queryGeometry, maxPitchScaleFactor);

tilesIn.sort(sortTilesIn);

Expand All @@ -26,9 +26,8 @@ exports.rendered = function(sourceCache: SourceCache,
tileIn.queryGeometry,
tileIn.scale,
params,
bearing,
sourceCache.transform.cameraToCenterDistance,
pitchScaleFactor,
transform,
maxPitchScaleFactor,
sourceCache.transform.calculatePosMatrix(tileIn.tileID.toUnwrapped()),
sourceCache.id,
collisionIndex)
Expand Down
4 changes: 2 additions & 2 deletions src/source/source_cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@ class SourceCache extends Evented {
* @param queryGeometry coordinates of the corners of bounding rectangle
* @returns {Array<Object>} 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<Coordinate>, pitchScaleFactor: number) {
tilesIn(queryGeometry: Array<Coordinate>, maxPitchScaleFactor: number) {
const tileResults = [];
const ids = this.getIds();

Expand All @@ -705,7 +705,7 @@ class SourceCache extends Evented {
const tile = this._tiles[ids[i]];
const tileID = tile.tileID;
const scale = Math.pow(2, this.transform.zoom - tile.tileID.overscaledZ);
const queryPadding = pitchScaleFactor * tile.queryPadding * EXTENT / tile.tileSize / scale;
const queryPadding = maxPitchScaleFactor * tile.queryPadding * EXTENT / tile.tileSize / scale;

const tileSpaceBounds = [
coordinateToTilePoint(tileID, new Coordinate(minX, minY, z)),
Expand Down
11 changes: 5 additions & 6 deletions src/source/tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -241,9 +242,8 @@ class Tile {
queryGeometry: Array<Array<Point>>,
scale: number,
params: { filter: FilterSpecification, layers: Array<string> },
bearing: number,
cameraToCenterDistance: number,
pitchScaleFactor: number,
transform: Transform,
maxPitchScaleFactor: number,
posMatrix: Float32Array,
sourceID: string,
collisionIndex: ?CollisionIndex): {[string]: Array<{ featureIndex: number, feature: GeoJSONFeature }>} {
Expand All @@ -267,11 +267,10 @@ class Tile {
queryGeometry: queryGeometry,
scale: scale,
tileSize: this.tileSize,
bearing: bearing,
cameraToCenterDistance: cameraToCenterDistance,
posMatrix: posMatrix,
transform: transform,
params: params,
queryPadding: this.queryPadding * pitchScaleFactor,
queryPadding: this.queryPadding * maxPitchScaleFactor,
collisionBoxArray: this.collisionBoxArray,
sourceID: sourceID,
collisionIndex: collisionIndex,
Expand Down
4 changes: 2 additions & 2 deletions src/style/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/style/style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -48,9 +49,8 @@ class StyleLayer extends Evented {
feature: VectorTileFeature,
geometry: Array<Array<Point>>,
zoom: number,
bearing: number,
transform: Transform,
pixelsToTileUnits: number,
cameraToCenterDistance: number,
posMatrix: Float32Array) => boolean;

constructor(layer: LayerSpecification, properties: {layout?: Properties<*>, paint: Properties<*>}) {
Expand Down
50 changes: 39 additions & 11 deletions src/style/style_layer/circle_style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const {multiPolygonIntersectsBufferedPoint} = require('../../util/intersection_t
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,
Expand All @@ -14,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 {
Expand All @@ -41,33 +43,59 @@ class CircleStyleLayer extends StyleLayer {
feature: VectorTileFeature,
geometry: Array<Array<Point>>,
zoom: number,
bearing: number,
transform: Transform,
pixelsToTileUnits: number,
cameraToCenterDistance: 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;
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) {
let adjustedSize = size;

if (this.paint.get('circle-pitch-scale') === 'viewport') {
const projectedCenter = vec4.transformMat4([], [point.x, point.y, 0, 1], posMatrix);
adjustedSize *= projectedCenter[3] / cameraToCenterDistance;
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(translatedPolygon, point, adjustedSize)) return true;
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<Array<Point>>, posMatrix: Float32Array, transform: Transform) {
return queryGeometry.map((r) => {
return r.map((p) => {
return projectPoint(p, posMatrix, transform);
});
});
}

module.exports = CircleStyleLayer;
5 changes: 3 additions & 2 deletions src/style/style_layer/fill_extrusion_style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<PaintProps>;
Expand All @@ -39,12 +40,12 @@ class FillExtrusionStyleLayer extends StyleLayer {
feature: VectorTileFeature,
geometry: Array<Array<Point>>,
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);
}

Expand Down
5 changes: 3 additions & 2 deletions src/style/style_layer/fill_style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<PaintProps>;
Expand Down Expand Up @@ -47,12 +48,12 @@ class FillStyleLayer extends StyleLayer {
feature: VectorTileFeature,
geometry: Array<Array<Point>>,
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);
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/style/style_layer/line_style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<number> {
useIntegerZoom: true;
Expand Down Expand Up @@ -79,12 +80,12 @@ class LineStyleLayer extends StyleLayer {
feature: VectorTileFeature,
geometry: Array<Array<Point>>,
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));
Expand Down
3 changes: 1 addition & 2 deletions src/ui/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
{
"geometry": {
"type": "Point",
"coordinates": [
-84.3310546875,
33.92512970007199
]
},
"type": "Feature",
"properties": {}
}
]
Loading

0 comments on commit b98a9f7

Please sign in to comment.