Filter features within map view
This example uses queryRenderedFeatures
to restrict a list of features in a vector tile layer to only return those that are visible in the map view.
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Filter features within map view</title><meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no"><link href="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.css" rel="stylesheet"><script src="https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.js"></script><style>body { margin: 0; padding: 0; }#map { position: absolute; top: 0; bottom: 0; width: 100%; }</style></head><body><style>#map {position: absolute;left: 25%;top: 0;bottom: 0;width: 75%;}.map-overlay {position: absolute;width: 25%;top: 0;bottom: 0;left: 0;font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;background-color: #fff;max-height: 100%;overflow: hidden;} .map-overlay fieldset {display: none;background: #ddd;border: none;padding: 10px;margin: 0;} .map-overlay input {display: block;border: none;width: 100%;border-radius: 3px;padding: 10px;margin: 0;box-sizing: border-box;} .map-overlay .listing {overflow: auto;max-height: 100%;} .map-overlay .listing > * {display: block;padding: 5px 10px;margin: 0;} .map-overlay .listing a {border-bottom: 1px solid rgba(0, 0, 0, 0.1);color: #404;text-decoration: none;} .map-overlay .listing a:last-child {border: none;} .map-overlay .listing a:hover {background: #f0f0f0;}</style> <div id="map"></div> <div class="map-overlay"><fieldset><input id="feature-filter" type="text" placeholder="Filter results by name"></fieldset><div id="feature-listing" class="listing"></div></div> <script> // TO MAKE THE MAP APPEAR YOU MUST // ADD YOUR ACCESS TOKEN FROM // https://account.mapbox.com mapboxgl.accessToken = '<your access token here>';var map = new mapboxgl.Map({container: 'map',style: 'mapbox://styles/mapbox/streets-v11',center: [-98, 38.88],maxZoom: 5,minZoom: 1,zoom: 3}); // Holds visible airport features for filteringvar airports = []; // Create a popup, but don't add it to the map yet.var popup = new mapboxgl.Popup({closeButton: false}); var filterEl = document.getElementById('feature-filter');var listingEl = document.getElementById('feature-listing'); function renderListings(features) {var empty = document.createElement('p');// Clear any existing listingslistingEl.innerHTML = '';if (features.length) {features.forEach(function (feature) {var prop = feature.properties;var item = document.createElement('a');item.href = prop.wikipedia;item.target = '_blank';item.textContent = prop.name + ' (' + prop.abbrev + ')';item.addEventListener('mouseover', function () {// Highlight corresponding feature on the mappopup.setLngLat(feature.geometry.coordinates).setText(feature.properties.name +' (' +feature.properties.abbrev +')').addTo(map);});listingEl.appendChild(item);}); // Show the filter inputfilterEl.parentNode.style.display = 'block';} else if (features.length === 0 && filterEl.value !== '') {empty.textContent = 'No results found';listingEl.appendChild(empty);} else {empty.textContent = 'Drag the map to populate results';listingEl.appendChild(empty); // Hide the filter inputfilterEl.parentNode.style.display = 'none'; // remove features filtermap.setFilter('airport', ['has', 'abbrev']);}} function normalize(string) {return string.trim().toLowerCase();} function getUniqueFeatures(array, comparatorProperty) {var existingFeatureKeys = {};// Because features come from tiled vector data, feature geometries may be split// or duplicated across tile boundaries and, as a result, features may appear// multiple times in query results.var uniqueFeatures = array.filter(function (el) {if (existingFeatureKeys[el.properties[comparatorProperty]]) {return false;} else {existingFeatureKeys[el.properties[comparatorProperty]] = true;return true;}}); return uniqueFeatures;} map.on('load', function () {map.addSource('airports', {'type': 'vector','url': 'mapbox://mapbox.04w69w5j'});map.addLayer({'id': 'airport','source': 'airports','source-layer': 'ne_10m_airports','type': 'symbol','layout': {'icon-image': 'airport-15','icon-padding': 0,'icon-allow-overlap': true}}); map.on('moveend', function () {var features = map.queryRenderedFeatures({ layers: ['airport'] }); if (features) {var uniqueFeatures = getUniqueFeatures(features, 'iata_code');// Populate features for the listing overlay.renderListings(uniqueFeatures); // Clear the input containerfilterEl.value = ''; // Store the current features in sn `airports` variable to// later use for filtering on `keyup`.airports = uniqueFeatures;}}); map.on('mousemove', 'airport', function (e) {// Change the cursor style as a UI indicator.map.getCanvas().style.cursor = 'pointer'; // Populate the popup and set its coordinates based on the feature.var feature = e.features[0];popup.setLngLat(feature.geometry.coordinates).setText(feature.properties.name +' (' +feature.properties.abbrev +')').addTo(map);}); map.on('mouseleave', 'airport', function () {map.getCanvas().style.cursor = '';popup.remove();}); filterEl.addEventListener('keyup', function (e) {var value = normalize(e.target.value); // Filter visible features that don't match the input value.var filtered = airports.filter(function (feature) {var name = normalize(feature.properties.name);var code = normalize(feature.properties.abbrev);return name.indexOf(value) > -1 || code.indexOf(value) > -1;}); // Populate the sidebar with filtered resultsrenderListings(filtered); // Set the filter to populate features into the layer.if (filtered.length) {map.setFilter('airport', ['match',['get', 'abbrev'],filtered.map(function (feature) {return feature.properties.abbrev;}),true,false]);}}); // Call this function on initialization// passing an empty array to render an empty staterenderListings([]);});</script> </body></html>