マップビュー内のフィーチャをフィルター処理
ベクター・タイルレイヤー内のクエリ表示可能なフィーチャとテキスト入力からフィルターし、マップを移動します。
<!DOCTYPE html><html><head><meta charset="utf-8"><title>マップビュー内のフィーチャをフィルター処理</title><meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no"><link href="https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css" rel="stylesheet"><script src="https://api.mapbox.com/mapbox-gl-js/v2.14.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: #efefef;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_MAPBOX_ACCESS_TOKEN';const 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 filteringlet airports = []; // Create a popup, but don't add it to the map yet.const popup = new mapboxgl.Popup({closeButton: false}); const filterEl = document.getElementById('feature-filter');const listingEl = document.getElementById('feature-listing'); function renderListings(features) {const empty = document.createElement('p');// Clear any existing listingslistingEl.innerHTML = '';if (features.length) {for (const feature of features) {const itemLink = document.createElement('a');const label = `${feature.properties.name} (${feature.properties.abbrev})`;itemLink.href = feature.properties.wikipedia;itemLink.target = '_blank';itemLink.textContent = label;itemLink.addEventListener('mouseover', () => {// Highlight corresponding feature on the mappopup.setLngLat(feature.geometry.coordinates).setText(label).addTo(map);});listingEl.appendChild(itemLink);} // 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();} // Because features come from tiled vector data,// feature geometries may be split// or duplicated across tile boundaries.// As a result, features may appear// multiple times in query results.function getUniqueFeatures(features, comparatorProperty) {const uniqueIds = new Set();const uniqueFeatures = [];for (const feature of features) {const id = feature.properties[comparatorProperty];if (!uniqueIds.has(id)) {uniqueIds.add(id);uniqueFeatures.push(feature);}}return uniqueFeatures;} map.on('load', () => {map.addSource('airports', {'type': 'vector','url': 'mapbox://mapbox.04w69w5j'});map.addLayer({'id': 'airport','source': 'airports','source-layer': 'ne_10m_airports','type': 'circle','paint': {'circle-color': '#4264fb','circle-radius': 4,'circle-stroke-width': 2,'circle-stroke-color': '#ffffff'}}); map.on('movestart', () => {// reset features filter as the map starts movingmap.setFilter('airport', ['has', 'abbrev']);}); map.on('moveend', () => {const features = map.queryRenderedFeatures({ layers: ['airport'] }); if (features) {const 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', (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.const feature = e.features[0];popup.setLngLat(feature.geometry.coordinates).setText(`${feature.properties.name} (${feature.properties.abbrev})`).addTo(map);}); map.on('mouseleave', 'airport', () => {map.getCanvas().style.cursor = '';popup.remove();}); filterEl.addEventListener('keyup', (e) => {const value = normalize(e.target.value); // Filter visible features that match the input value.const filtered = [];for (const feature of airports) {const name = normalize(feature.properties.name);const code = normalize(feature.properties.abbrev);if (name.includes(value) || code.includes(value)) {filtered.push(feature);}} // 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((feature) => {return feature.properties.abbrev;}),true,false]);}}); // Call this function on initialization// passing an empty array to render an empty staterenderListings([]);});</script> </body></html>