Bring back vector render mode for vector tile layers

This commit is contained in:
Andreas Hocevar
2019-11-18 07:49:24 +01:00
parent 3f8164250c
commit cf6cd09f58
9 changed files with 116 additions and 31 deletions

View File

@@ -111,7 +111,7 @@ Features for `updates` must have an id set by the feature reader or `ol.Feature#
### 28 ### 28
`renderMode` must be `'image'` or `'hybrid'`. `renderMode` must be `'image'`, `'hybrid'` or `'vector'`.
### 29 ### 29

View File

@@ -5,7 +5,11 @@ shortdesc: Select features from vector tiles.
docs: > docs: >
<p> <p>
Click a rendered vector-tile feature to highlight it on the map. Click on an empty spot (ocean) to reset the selection. Click a rendered vector-tile feature to highlight it on the map. Click on an empty spot (ocean) to reset the selection.
By changing the action type to "Multi Select" you can select multiple features at a time. By changing the action type to "Multi Select" you can select multiple features at a time. With "Single Sslect on hover",
features will be higlighted when the pointer is above them.
</p><p>
The selection layer is configured with `renderMode: 'vector'` for better performance on frequent redraws,
i.e. when 'Single select on hover' is selected.
</p> </p>
tags: "vector tiles, selection" tags: "vector tiles, selection"
--- ---
@@ -15,5 +19,6 @@ tags: "vector tiles, selection"
<select id="type" class="form-control"> <select id="type" class="form-control">
<option value="singleselect" selected>Single Select</option> <option value="singleselect" selected>Single Select</option>
<option value="multiselect">Multi Select</option> <option value="multiselect">Multi Select</option>
<option value="singleselect-hover">Single Select on hover</option>
</select> </select>
</form> </form>

View File

@@ -8,6 +8,25 @@ import {Fill, Stroke, Style} from '../src/ol/style.js';
// lookup for selection objects // lookup for selection objects
let selection = {}; let selection = {};
const country = new Style({
stroke: new Stroke({
color: 'gray',
width: 1
}),
fill: new Fill({
color: 'rgba(20,20,20,0.9)'
})
});
const selectedCountry = new Style({
stroke: new Stroke({
color: 'rgba(200,20,20,0.8)',
width: 2
}),
fill: new Fill({
color: 'rgba(200,20,20,0.4)'
})
});
const vtLayer = new VectorTileLayer({ const vtLayer = new VectorTileLayer({
declutter: true, declutter: true,
source: new VectorTileSource({ source: new VectorTileSource({
@@ -18,18 +37,7 @@ const vtLayer = new VectorTileLayer({
url: 'https://ahocevar.com/geoserver/gwc/service/tms/1.0.0/' + url: 'https://ahocevar.com/geoserver/gwc/service/tms/1.0.0/' +
'ne:ne_10m_admin_0_countries@EPSG%3A900913@pbf/{z}/{x}/{-y}.pbf' 'ne:ne_10m_admin_0_countries@EPSG%3A900913@pbf/{z}/{x}/{-y}.pbf'
}), }),
style: function(feature) { style: country
const selected = !!selection[feature.getId()];
return new Style({
stroke: new Stroke({
color: selected ? 'rgba(200,20,20,0.8)' : 'gray',
width: selected ? 2 : 1
}),
fill: new Fill({
color: selected ? 'rgba(200,20,20,0.2)' : 'rgba(20,20,20,0.9)'
})
});
}
}); });
const map = new Map({ const map = new Map({
@@ -39,18 +47,34 @@ const map = new Map({
target: 'map', target: 'map',
view: new View({ view: new View({
center: [0, 0], center: [0, 0],
zoom: 2 zoom: 2,
multiWorld: true
}) })
}); });
// Selection
const selectionLayer = new VectorTileLayer({
map: map,
renderMode: 'vector',
source: vtLayer.getSource(),
style: function(feature) {
if (feature.getId() in selection) {
return selectedCountry;
}
}
});
const selectElement = document.getElementById('type'); const selectElement = document.getElementById('type');
map.on('click', function(event) { map.on(['click', 'pointermove'], function(event) {
if (selectElement.value === 'singleselect-hover' && event.type !== 'pointermove' ||
selectElement.value !== 'singleselect-hover' && event.type === 'pointermove') {
return;
}
vtLayer.getFeatures(event.pixel).then(function(features) { vtLayer.getFeatures(event.pixel).then(function(features) {
if (!features.length) { if (!features.length) {
selection = {}; selection = {};
// force redraw of layer style selectionLayer.changed();
vtLayer.setStyle(vtLayer.getStyle());
return; return;
} }
const feature = features[0]; const feature = features[0];
@@ -59,14 +83,12 @@ map.on('click', function(event) {
} }
const fid = feature.getId(); const fid = feature.getId();
if (selectElement.value === 'singleselect') { if (selectElement.value.startsWith('singleselect')) {
selection = {}; selection = {};
} }
// add selected feature to lookup // add selected feature to lookup
selection[fid] = feature; selection[fid] = feature;
// force redraw of layer style selectionLayer.changed();
vtLayer.setStyle(vtLayer.getStyle());
}); });
}); });

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

View File

@@ -0,0 +1,31 @@
import Map from '../../../src/ol/Map.js';
import View from '../../../src/ol/View.js';
import VectorTileSource from '../../../src/ol/source/VectorTile.js';
import MVT from '../../../src/ol/format/MVT.js';
import {createXYZ} from '../../../src/ol/tilegrid.js';
import VectorTileLayer from '../../../src/ol/layer/VectorTile.js';
import VectorTileRenderType from '../../../src/ol/layer/VectorTileRenderType.js';
new Map({
layers: [
new VectorTileLayer({
renderMode: VectorTileRenderType.VECTOR,
source: new VectorTileSource({
format: new MVT(),
tileGrid: createXYZ(),
url: '/data/tiles/mapbox-streets-v6/{z}/{x}/{y}.vector.pbf',
transition: 0
})
})
],
target: 'map',
view: new View({
center: [1825927.7316762917, 6143091.089223046],
zoom: 14
})
});
render({
message: 'Vector tile layer renders with vector render mode',
tolerance: 0.01
});

View File

@@ -40,7 +40,9 @@ import {assign} from '../obj.js';
* * `'hybrid'`: Polygon and line elements are rendered as images, so pixels are scaled during zoom * * `'hybrid'`: Polygon and line elements are rendered as images, so pixels are scaled during zoom
* animations. Point symbols and texts are accurately rendered as vectors and can stay upright on * animations. Point symbols and texts are accurately rendered as vectors and can stay upright on
* rotated views. * rotated views.
* * * `'vector'`: Everything is rendered as vectors. Use this mode for improved performance on vector
* tile layers with only a few rendered features (e.g. for highlighting a subset of features of
* another layer with the same source).
* @property {import("../source/VectorTile.js").default} [source] Source. * @property {import("../source/VectorTile.js").default} [source] Source.
* @property {import("../PluggableMap.js").default} [map] Sets the layer as overlay on a map. The map will not manage * @property {import("../PluggableMap.js").default} [map] Sets the layer as overlay on a map. The map will not manage
* this layer in its layers collection, and the layer will be rendered on top. This is useful for * this layer in its layers collection, and the layer will be rendered on top. This is useful for
@@ -92,8 +94,9 @@ class VectorTileLayer extends BaseVectorLayer {
const renderMode = options.renderMode || VectorTileRenderType.HYBRID; const renderMode = options.renderMode || VectorTileRenderType.HYBRID;
assert(renderMode == undefined || assert(renderMode == undefined ||
renderMode == VectorTileRenderType.IMAGE || renderMode == VectorTileRenderType.IMAGE ||
renderMode == VectorTileRenderType.HYBRID, renderMode == VectorTileRenderType.HYBRID ||
28); // `renderMode` must be `'image'` or `'hybrid'` renderMode == VectorTileRenderType.VECTOR,
28); // `renderMode` must be `'image'`, `'hybrid'` or `'vector'`.
/** /**
* @private * @private

View File

@@ -11,9 +11,14 @@
* * `'hybrid'`: Polygon and line elements are rendered as images, so pixels * * `'hybrid'`: Polygon and line elements are rendered as images, so pixels
* are scaled during zoom animations. Point symbols and texts are accurately * are scaled during zoom animations. Point symbols and texts are accurately
* rendered as vectors and can stay upright on rotated views. * rendered as vectors and can stay upright on rotated views.
* * `'vector'`: Everything is rendered as vectors. Use this mode for improved
* performance on vector tile layers with only a few rendered features (e.g.
* for highlighting a subset of features of another layer with the same
* source).
* @api * @api
*/ */
export default { export default {
IMAGE: 'image', IMAGE: 'image',
HYBRID: 'hybrid' HYBRID: 'hybrid',
VECTOR: 'vector'
}; };

View File

@@ -33,7 +33,8 @@ import {createHitDetectionImageData, hitDetect} from '../../render/canvas/hitdet
const IMAGE_REPLAYS = { const IMAGE_REPLAYS = {
'image': [ReplayType.POLYGON, ReplayType.CIRCLE, 'image': [ReplayType.POLYGON, ReplayType.CIRCLE,
ReplayType.LINE_STRING, ReplayType.IMAGE, ReplayType.TEXT], ReplayType.LINE_STRING, ReplayType.IMAGE, ReplayType.TEXT],
'hybrid': [ReplayType.POLYGON, ReplayType.LINE_STRING] 'hybrid': [ReplayType.POLYGON, ReplayType.LINE_STRING],
'vector': []
}; };
@@ -42,7 +43,8 @@ const IMAGE_REPLAYS = {
*/ */
const VECTOR_REPLAYS = { const VECTOR_REPLAYS = {
'image': [ReplayType.DEFAULT], 'image': [ReplayType.DEFAULT],
'hybrid': [ReplayType.IMAGE, ReplayType.TEXT, ReplayType.DEFAULT] 'hybrid': [ReplayType.IMAGE, ReplayType.TEXT, ReplayType.DEFAULT],
'vector': [ReplayType.POLYGON, ReplayType.CIRCLE, ReplayType.LINE_STRING, ReplayType.IMAGE, ReplayType.TEXT, ReplayType.DEFAULT]
}; };
@@ -156,7 +158,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
tile.wantedResolution = resolution; tile.wantedResolution = resolution;
} }
const render = this.prepareTile(tile, pixelRatio, projection, false); const render = this.prepareTile(tile, pixelRatio, projection, false);
if (render) { if (render && this.getLayer().getRenderMode() !== VectorTileRenderType.VECTOR) {
this.renderTileImage_(tile, frameState); this.renderTileImage_(tile, frameState);
} }
} }
@@ -167,7 +169,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
* @inheritdoc * @inheritdoc
*/ */
isDrawableTile(tile) { isDrawableTile(tile) {
return super.isDrawableTile(tile) && tile.hasContext(this.getLayer()); const layer = this.getLayer();
return super.isDrawableTile(tile) && layer.getRenderMode() === VectorTileRenderType.VECTOR || tile.hasContext(layer);
} }
/** /**
@@ -267,7 +270,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
} }
const executorGroupInstructions = builderGroup.finish(); const executorGroupInstructions = builderGroup.finish();
// no need to clip when the render tile is covered by a single source tile // no need to clip when the render tile is covered by a single source tile
const replayExtent = layer.getDeclutter() && sourceTiles.length === 1 ? const replayExtent = layer.getRenderMode() !== VectorTileRenderType.VECTOR && layer.getDeclutter() && sourceTiles.length === 1 ?
null : null :
sharedExtent; sharedExtent;
const renderingReplayGroup = new CanvasExecutorGroup(replayExtent, resolution, const renderingReplayGroup = new CanvasExecutorGroup(replayExtent, resolution,

View File

@@ -122,6 +122,22 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
spy.restore(); spy.restore();
}); });
it('does not render images for pure vector rendering', function() {
const testLayer = new VectorTileLayer({
renderMode: VectorTileRenderType.VECTOR,
source: source,
style: layerStyle
});
map.removeLayer(layer);
map.addLayer(testLayer);
const spy = sinon.spy(CanvasVectorTileLayerRenderer.prototype,
'renderTileImage_');
map.renderSync();
expect(spy.callCount).to.be(0);
spy.restore();
});
it('renders both replays and images for hybrid rendering', function() { it('renders both replays and images for hybrid rendering', function() {
const spy1 = sinon.spy(CanvasVectorTileLayerRenderer.prototype, const spy1 = sinon.spy(CanvasVectorTileLayerRenderer.prototype,
'getRenderTransform'); 'getRenderTransform');