Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Implement hit detection for WebGL Mapbox Vector Tiles #15442

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
56 changes: 55 additions & 1 deletion examples/webgl-vector-tiles.js
@@ -1,11 +1,14 @@
import MVT from '../src/ol/format/MVT.js';
import Map from '../src/ol/Map.js';
import VectorLayer from '../src/ol/layer/Vector.js';
import VectorSource from '../src/ol/source/Vector.js';
import VectorTile from '../src/ol/layer/VectorTile.js';
import VectorTileSource from '../src/ol/source/VectorTile.js';
import View from '../src/ol/View.js';
import WebGLVectorTileLayerRenderer from '../src/ol/renderer/webgl/VectorTileLayer.js';
import {Fill, Icon, Stroke, Style, Text} from '../src/ol/style.js';
import {asArray} from '../src/ol/color.js';
import {log} from '../src/ol/console.js';
import {packColor, parseLiteralStyle} from '../src/ol/webgl/styleparser.js';

const key =
Expand All @@ -15,13 +18,14 @@ const result = parseLiteralStyle({
'fill-color': ['get', 'fillColor'],
'stroke-color': ['get', 'strokeColor'],
'stroke-width': ['get', 'strokeWidth'],
'circle-radius': 4,
'circle-radius': 10,
'circle-fill-color': '#777',
});

class WebGLVectorTileLayer extends VectorTile {
createRenderer() {
return new WebGLVectorTileLayerRenderer(this, {
disableHitDetection: false,
style: {
builder: result.builder,
attributes: {
Expand Down Expand Up @@ -54,6 +58,11 @@ class WebGLVectorTileLayer extends VectorTile {
}
}

const vectorSource = new VectorSource({
useSpatialIndex: false,
features: [],
});

const map = new Map({
layers: [
new WebGLVectorTileLayer({
Expand All @@ -70,10 +79,55 @@ const map = new Map({
}),
style: createMapboxStreetsV6Style(Style, Fill, Stroke, Icon, Text),
}),
new VectorLayer({
style: {
'fill-color': 'grey',
'stroke-color': 'green',
'stroke-width': 3,
'circle-fill-color': 'red',
'circle-radius': 10,
},
source: vectorSource,
}),
],
target: 'map',
view: new View({
center: [0, 0],
zoom: 2,
}),
});

let activeFeature;

map.on('pointermove', function (evt) {
if (evt.dragging) {
return;
}
const pixel = map.getEventPixel(evt.originalEvent);
const feature = map.forEachFeatureAtPixel(
pixel,
function (feature) {
return feature;
},
{
layerFilter(layer) {
return layer instanceof WebGLVectorTileLayer;
},
}
);
if (feature) {
if (activeFeature === feature) {
// skip
} else {
log(feature);
vectorSource.removeFeature(activeFeature);
vectorSource.addFeature(feature);
activeFeature = feature;
}
} else {
if (activeFeature) {
vectorSource.removeFeature(activeFeature);
activeFeature = null;
}
}
});
1 change: 1 addition & 0 deletions src/ol/layer/WebGLTile.js
Expand Up @@ -401,6 +401,7 @@ class WebGLTileLayer extends BaseTileLayer {
uniforms: parsedStyle.uniforms,
cacheSize: this.cacheSize_,
paletteTextures: parsedStyle.paletteTextures,
disableHitDetection: true,
});
}

Expand Down
2 changes: 2 additions & 0 deletions src/ol/renderer/webgl/TileLayer.js
Expand Up @@ -55,6 +55,8 @@ const attributeDescriptions = [
* made available to shaders.
* @property {Array<import("../../webgl/PaletteTexture.js").default>} [paletteTextures] Palette textures.
* @property {number} [cacheSize=512] The texture cache size.
* @property {boolean} [disableHitDetection=false] Setting this to true will provide a slight performance boost, but will
* prevent all hit detection on the layer.
*/

/**
Expand Down
94 changes: 56 additions & 38 deletions src/ol/renderer/webgl/TileLayerBase.js
Expand Up @@ -135,6 +135,8 @@ export function getCacheKey(source, tileCoord) {
* made available to shaders.
* @property {number} [cacheSize=512] The tile representation cache size.
* @property {Array<import('./Layer.js').PostProcessesOptions>} [postProcesses] Post-processes definitions.
* @property {boolean} [disableHitDetection=false] Setting this to true will provide a slight performance boost, but will
* prevent all hit detection on the layer.
*/

/**
Expand Down Expand Up @@ -215,6 +217,12 @@ class WebGLBaseTileLayerRenderer extends WebGLLayerRenderer {
* @type {import("../../proj/Projection.js").default}
*/
this.projection_ = undefined;

/**
* Whether hit detection is enabled.
* @type {boolean}
*/
this.hitDetectionEnabled_ = !options.disableHitDetection;
}

/**
Expand Down Expand Up @@ -408,9 +416,10 @@ class WebGLBaseTileLayerRenderer extends WebGLLayerRenderer {
/**
* @param {import("../../Map.js").FrameState} frameState Frame state.
* @param {boolean} tilesWithAlpha True if at least one of the rendered tiles has alpha
* @param {boolean} [forHitDetection] Whether to render for hit detection
* @protected
*/
beforeTilesRender(frameState, tilesWithAlpha) {
beforeTilesRender(frameState, tilesWithAlpha, forHitDetection) {
this.helper.prepareDraw(this.frameState, !tilesWithAlpha, true);
}

Expand All @@ -435,6 +444,7 @@ class WebGLBaseTileLayerRenderer extends WebGLLayerRenderer {
* @param {number} depth Depth
* @param {number} gutter Gutter
* @param {number} alpha Alpha
* @param {number} [forHitDetection] Whether to render for hit detection, or not
* @protected
*/
renderTile(
Expand All @@ -448,7 +458,8 @@ class WebGLBaseTileLayerRenderer extends WebGLLayerRenderer {
tileExtent,
depth,
gutter,
alpha
alpha,
forHitDetection
) {}

/**
Expand All @@ -467,7 +478,8 @@ class WebGLBaseTileLayerRenderer extends WebGLLayerRenderer {
gutter,
extent,
alphaLookup,
tileGrid
tileGrid,
forHitDetection
) {
if (!tileRepresentation.ready) {
return;
Expand Down Expand Up @@ -529,7 +541,8 @@ class WebGLBaseTileLayerRenderer extends WebGLLayerRenderer {
tileExtent,
depth,
gutter,
alpha
alpha,
forHitDetection
);
}

Expand Down Expand Up @@ -680,44 +693,49 @@ class WebGLBaseTileLayerRenderer extends WebGLLayerRenderer {
}
}

this.beforeTilesRender(frameState, blend);
(this.hitDetectionEnabled_ ? [true, false] : [false]).forEach(
(forHitDetection) => {
this.beforeTilesRender(frameState, blend, forHitDetection);
for (let j = 0, jj = zs.length; j < jj; ++j) {
const tileZ = zs[j];
for (const tileRepresentation of representationsByZ[tileZ]) {
const tileCoord = tileRepresentation.tile.tileCoord;
const tileCoordKey = getTileCoordKey(tileCoord);
if (tileCoordKey in alphaLookup) {
continue;
}

for (let j = 0, jj = zs.length; j < jj; ++j) {
const tileZ = zs[j];
for (const tileRepresentation of representationsByZ[tileZ]) {
const tileCoord = tileRepresentation.tile.tileCoord;
const tileCoordKey = getTileCoordKey(tileCoord);
if (tileCoordKey in alphaLookup) {
continue;
this.drawTile_(
frameState,
tileRepresentation,
tileZ,
gutter,
extent,
alphaLookup,
tileGrid,
forHitDetection
);
}
}

this.drawTile_(
frameState,
tileRepresentation,
tileZ,
gutter,
extent,
alphaLookup,
tileGrid
);
}
}

for (const tileRepresentation of representationsByZ[z]) {
const tileCoord = tileRepresentation.tile.tileCoord;
const tileCoordKey = getTileCoordKey(tileCoord);
if (tileCoordKey in alphaLookup) {
this.drawTile_(
frameState,
tileRepresentation,
z,
gutter,
extent,
alphaLookup,
tileGrid
);
for (const tileRepresentation of representationsByZ[z]) {
const tileCoord = tileRepresentation.tile.tileCoord;
const tileCoordKey = getTileCoordKey(tileCoord);
if (tileCoordKey in alphaLookup) {
this.drawTile_(
frameState,
tileRepresentation,
z,
gutter,
extent,
alphaLookup,
tileGrid,
forHitDetection
);
}
}
}
}
);

this.helper.finalizeDraw(
frameState,
Expand Down