I use MVT vector tiles with Openlayers and everything works fine - but now I have a conceptual problem.

I need to make features selectable and searchable. The problem is that there is no way to retrieve all features from ol.source.VectorTile, as you could do with ol.source.Vector using getFeatures() method.

So what do people do in this case? The only way I see is to maintain a extra list with all the features loaded from GeoJSON. But then using MVT vector tiles doesn't make sense any more as it is redundant to maintain the same information twice.


An array of features can be built up as the MVT tiles load (there will be a different set of features for each zoom level of the tilegrid) making a separate GeoJSON unnecessary.

 var featuresForZ = [];

 vtSource().on('tileloadend', function(evt) {
    var z = evt.tile.getTileCoord()[0];
    var features = evt.tile.getFeatures();
    if (!Array.isArray(featuresForZ[z])) { featuresForZ[z] = []; }
    featuresForZ[z] = featuresForZ[z].concat(features);

You could use the appropriate array directly for searching, or you could copy the features to a normal vector source. This code is proof of concept, however practical applications would include using vSource as the source of a cluster source, something not possible with a vector tile source.

<!DOCTYPE html>
    <title>Vector Layer from Vector Tiles</title>
    <link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" type="text/css">
    <!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
    <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
    <script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
    <div id="map" class="map"></div>

    var vtLayer = new ol.layer.VectorTile({
        source: new ol.source.VectorTile({
            format: new ol.format.MVT({ featureClass: ol.Feature }),
            url: 'https://basemaps.arcgis.com/v1/arcgis/rest/services/World_Basemap/VectorTileServer/tile/{z}/{y}/{x}.pbf',
            tileGrid: ol.tilegrid.createXYZ({ maxZoom: 13 })
        style: []   // layer must be added to map to load features, use empty style to avoid display 

    var map = new ol.Map({
        target: 'map',
        view: new ol.View({
            center: [0, 0],
            zoom: 2
        layers: [vtLayer]

    var vSource = new ol.source.Vector();
    var featuresForZ = [];
    var viewZ = vtLayer.getSource().getTileGrid().getZForResolution(map.getView().getResolution());

    function vsRefresh() {
        if (featuresForZ[viewZ]) {

    vtLayer.getSource().on('tileloadend', function(evt) {
        var z = evt.tile.getTileCoord()[0];
        var features = evt.tile.getFeatures();
        features.forEach(function (feature) {
            // each vector tile has its own set of feature ids, but duplicates are not allowed in normal vector sources
        if (!Array.isArray(featuresForZ[z])) {
            featuresForZ[z] = [];
        featuresForZ[z] = featuresForZ[z].concat(features);
        if (z === viewZ) {

    map.getView().on('change:resolution', function() {
        // use VT features from the tile z level corresponding to view resolution
        var newZ = vtLayer.getSource().getTileGrid().getZForResolution(map.getView().getResolution());
        if (newZ !== viewZ) {
            viewZ = newZ;

    var vLayer = new ol.layer.Vector({
        source: vSource,

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.