Skip to content

Commit

Permalink
attempt #2: basic building querying
Browse files Browse the repository at this point in the history
  • Loading branch information
ansis committed Oct 16, 2018
1 parent 11b4228 commit ca2603e
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 17 deletions.
57 changes: 57 additions & 0 deletions debug/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,63 @@
hash: true
});

map.on('load', function() {
map.addLayer({
'id': '3d-buildings',
'source': 'composite',
'source-layer': 'building',
'filter': ['==', 'extrude', 'true'],
'type': 'fill-extrusion',
'minzoom': 15,
'paint': {
'fill-extrusion-color': ['case', ['boolean', ['feature-state', 'hovered'], false], '#f00', '#aaa'],

// use an 'interpolate' expression to add a smooth transition effect to the
// buildings as the user zooms in
'fill-extrusion-height': [
"interpolate", ["linear"], ["zoom"],
15, 0,
15.05, ["get", "height"]
],
'fill-extrusion-base': [
"interpolate", ["linear"], ["zoom"],
15, 0,
15.05, ["get", "min_height"]
],
'fill-extrusion-opacity': .6
}
});

let hovered = null;
let hoveredSet = null;
map.on('mousemove', '3d-buildings', function(e) {
//const offset = new mapboxgl.Point(10, -10);
//const features = map.queryRenderedFeatures([e.point.sub(offset), e.point.add(offset)]);//, { layers: ['3d-buildings'] });
//const features = map.queryRenderedFeatures(e.point, { layers: ['3d-buildings'] });
const features = e.features;
if (hoveredSet) {
hoveredSet.forEach((hovered) => map.setFeatureState(hovered, { hovered: false }));
hoveredSet = null;
}
if (features) {
hoveredSet = [];
console.log('here', features);
features.forEach((feature) => {
hovered = { source: 'composite', 'sourceLayer': 'building', id: feature.id };
map.setFeatureState(hovered, { hovered: true });
hoveredSet.push(hovered);
});
}
});

map.on('mouseleave', '3d-buildings', function(e) {
if (hoveredSet) {
hoveredSet.forEach((hovered) => map.setFeatureState(hovered, { hovered: false }));
hoveredSet = null;
}
});
});

</script>
</body>
</html>
7 changes: 3 additions & 4 deletions src/data/feature_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,8 @@ class FeatureIndex {
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];
for (const ring of args.cameraQueryGeometry) {
for (const p of ring) {
minX = Math.min(minX, p.x);
minY = Math.min(minY, p.y);
maxX = Math.max(maxX, p.x);
Expand All @@ -124,6 +122,7 @@ class FeatureIndex {
matching.sort(topDownFeatureComparator);
const result = {};
let previousIndex;
console.log(matching.length);
for (let k = 0; k < matching.length; k++) {
const index = matching[k];

Expand Down
35 changes: 35 additions & 0 deletions src/geo/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import tileCover from '../util/tile_cover';
import { CanonicalTileID, UnwrappedTileID } from '../source/tile_id';
import EXTENT from '../data/extent';
import { vec4, mat4, mat2 } from 'gl-matrix';
import assert from 'assert';

/**
* A single transform, generally used for a single tile to be
Expand Down Expand Up @@ -622,6 +623,40 @@ class Transform {
const topPoint = vec4.transformMat4(p, p, this.pixelMatrix);
return topPoint[3] / this.cameraToCenterDistance;
}

getCameraPoint() {
const pitch = this._pitch;
const altitude = Math.cos(pitch) * this.cameraToCenterDistance;
const latOffset = Math.tan(pitch) * this.cameraToCenterDistance;
return this.centerPoint.add(new Point(0, latOffset));
}

getCameraQueryGeometry(queryGeometry: Array<Point>) {
assert(queryGeometry.length === 1 || queryGeometry.length === 5);
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) {
let minX = Math.min(minX, p.x);
let minY = Math.min(minY, p.y);
let maxX = Math.max(maxX, p.x);
let 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;
5 changes: 3 additions & 2 deletions src/source/query_features.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import assert from 'assert';

export function queryRenderedFeatures(sourceCache: SourceCache,
styleLayers: {[string]: StyleLayer},
queryGeometry: Array<Coordinate>,
queryGeometry: Array<Point>,
params: { filter: FilterSpecification, layers: Array<string> },
transform: Transform) {
const maxPitchScaleFactor = transform.maxPitchScaleFactor();
const tilesIn = sourceCache.tilesIn(queryGeometry, maxPitchScaleFactor);
const tilesIn = sourceCache.tilesIn(queryGeometry, maxPitchScaleFactor, transform);

tilesIn.sort(sortTilesIn);

Expand All @@ -27,6 +27,7 @@ export function queryRenderedFeatures(sourceCache: SourceCache,
styleLayers,
sourceCache._state,
tileIn.queryGeometry,
tileIn.cameraQueryGeometry,
tileIn.scale,
params,
transform,
Expand Down
19 changes: 11 additions & 8 deletions src/source/source_cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,13 @@ 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>, maxPitchScaleFactor: number) {
tilesIn(pointQueryGeometry: Array<Point>, maxPitchScaleFactor: number, transform: Transform) {

const cameraPointQueryGeometry = transform.getCameraQueryGeometry(pointQueryGeometry);

const queryGeometry = pointQueryGeometry.map((p) => this.transform.pointCoordinate(p));
const cameraQueryGeometry = cameraPointQueryGeometry.map((p) => this.transform.pointCoordinate(p));

const tileResults = [];
const ids = this.getIds();

Expand All @@ -744,15 +750,13 @@ class SourceCache extends Evented {
let maxY = -Infinity;
const z = queryGeometry[0].zoom;

for (let k = 0; k < queryGeometry.length; k++) {
const p = queryGeometry[k];
for (const p of cameraQueryGeometry) {
minX = Math.min(minX, p.column);
minY = Math.min(minY, p.row);
maxX = Math.max(maxX, p.column);
maxY = Math.max(maxY, p.row);
}


for (let i = 0; i < ids.length; i++) {
const tile = this._tiles[ids[i]];
if (tile.holdingForFade()) {
Expand All @@ -771,15 +775,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(coordinateToTilePoint(tileID, queryGeometry[j]));
}
const tileSpaceQueryGeometry = queryGeometry.map((c) => coordinateToTilePoint(tileID, c));
const tileSpaceCameraQueryGeometry = cameraQueryGeometry.map((c) => coordinateToTilePoint(tileID, c));

tileResults.push({
tile: tile,
tileID: tileID,
queryGeometry: [tileSpaceQueryGeometry],
cameraQueryGeometry: [tileSpaceCameraQueryGeometry],
scale: scale
});
}
Expand Down
2 changes: 2 additions & 0 deletions src/source/tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ class Tile {
queryRenderedFeatures(layers: {[string]: StyleLayer},
sourceFeatureState: SourceFeatureState,
queryGeometry: Array<Array<Point>>,
cameraQueryGeometry: Array<Array<Point>>,
scale: number,
params: { filter: FilterSpecification, layers: Array<string> },
transform: Transform,
Expand All @@ -269,6 +270,7 @@ class Tile {

return this.latestFeatureIndex.query({
queryGeometry: queryGeometry,
cameraQueryGeometry: cameraQueryGeometry,
scale: scale,
tileSize: this.tileSize,
posMatrix: posMatrix,
Expand Down
4 changes: 3 additions & 1 deletion src/style/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -938,13 +938,14 @@ class Style extends Evented {
const sourceResults = [];
const queryCoordinates = queryGeometry.map((p) => transform.pointCoordinate(p));

console.time('query');
for (const id in this.sourceCaches) {
if (params.layers && !includedSources[id]) continue;
sourceResults.push(
queryRenderedFeatures(
this.sourceCaches[id],
this._layers,
queryCoordinates,
queryGeometry,
params,
transform)
);
Expand All @@ -963,6 +964,7 @@ class Style extends Evented {
this.placement.retainedQueryData)
);
}
console.timeEnd('query');
return this._flattenRenderedFeatures(sourceResults);
}

Expand Down
44 changes: 42 additions & 2 deletions src/style/style_layer/fill_extrusion_style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { multiPolygonIntersectsMultiPolygon } from '../../util/intersection_test
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';
Expand Down Expand Up @@ -40,12 +42,35 @@ class FillExtrusionStyleLayer extends StyleLayer {
geometry: Array<Array<Point>>,
zoom: number,
transform: Transform,
pixelsToTileUnits: number): boolean {
pixelsToTileUnits: number,
posMatrix: Float32Array): boolean {
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);
const base = this.paint.get('fill-extrusion-base').evaluate(feature);

const projectedQueryGeometry = projectQueryGeometry(translatedPolygon, posMatrix, transform, 0);
const projectedTop = projectQueryGeometry(geometry, posMatrix, transform, height);
const projectedBase = projectQueryGeometry(geometry, posMatrix, transform, base);

if (multiPolygonIntersectsMultiPolygon(projectedQueryGeometry, projectedTop)) return true;

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];
if (multiPolygonIntersectsMultiPolygon(projectedQueryGeometry, [[topA, topB, baseB, baseA, topA]])) return true;
}
}

return false;
}

hasOffscreenPass() {
Expand All @@ -60,4 +85,19 @@ class FillExtrusionStyleLayer extends StyleLayer {
}
}

function projectPoint(p: Point, posMatrix: Float32Array, transform: Transform, z: number) {
const point = vec4.transformMat4([], [p.x, p.y, z, 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, z: number) {
return queryGeometry.map((r) => {
return r.map((p) => {
return projectPoint(p, posMatrix, transform, z);
});
});
}

export default FillExtrusionStyleLayer;

0 comments on commit ca2603e

Please sign in to comment.