React项目中修改leaflet-geosearch Provider实现本地JSON搜索
解决leaflet-geosearch接入本地JSON机场数据的问题
问题说明
在React项目中使用leaflet-geosearch时,需要将现有的OpenStreetMapProvider替换为本地JSON格式的机场数据(airports.json)作为搜索数据源,但不清楚如何自定义Provider实现这一需求。
解决方案
leaflet-geosearch允许自定义Provider,只要实现核心的search方法即可。我们可以基于本地解析后的机场数据,实现一个匹配搜索关键词的自定义Provider。
步骤1:自定义本地机场数据Provider
创建一个LocalAirportProvider类,实现search方法,该方法接收用户输入的query,遍历本地机场数据,返回匹配的结果(需符合leaflet-geosearch要求的格式:包含x(经度)、y(纬度)、label(搜索结果显示文本))。
class LocalAirportProvider { constructor({ data }) { // 预先解析好经纬度的本地机场数据 this.data = data.map(location => ({ ...location, lat: parseFloat(location.coordinates.split(',')[1].trim()), lng: parseFloat(location.coordinates.split(',')[0].trim()), })); } search({ query }) { if (!query) return []; const lowerQuery = query.toLowerCase(); // 匹配机场名称、标识、城市名,可根据需求调整匹配逻辑 return this.data .filter(location => location.name.toLowerCase().includes(lowerQuery) || location.ident.toLowerCase().includes(lowerQuery) || location.municipality.toLowerCase().includes(lowerQuery) ) .map(location => ({ x: location.lng, y: location.lat, label: `${location.iso_country} - ${location.name} (${location.ident})`, })); } }
步骤2:修改Search组件
替换原有的OpenStreetMapProvider为自定义的LocalAirportProvider,并调整相关配置:
const Search = () => { const map = useMap(); useEffect(() => { // 实例化自定义Provider,传入本地机场数据 const provider = new LocalAirportProvider({ data: markerData3 }); const searchControl = new GeoSearchControl({ provider: provider, style: 'bar', showMarker: true, showPopup: false, marker: { icon: customIcon, // 改用自定义的机场标记图标 draggable: false, }, autoClose: true, searchLabel: '搜索机场...', retainZoomLevel: false, animateZoom: true, }).addTo(map); return () => { map.removeControl(searchControl); }; }, [map]); return null; };
完整修改后的代码
将自定义Provider加入到原代码中,替换原Search组件的逻辑:
import React, { useEffect } from 'react'; import { MapContainer, TileLayer, useMap } from 'react-leaflet'; import L from 'leaflet'; import 'leaflet-geosearch/dist/geosearch.css'; import { GeoSearchControl } from 'leaflet-geosearch'; // 不再导入OpenStreetMapProvider import customMarkerIcon from 'assets/img/maps/marker-icon.png'; import markerData3 from 'assets/data/json/airports.json'; const customIcon = L.icon({ iconUrl: customMarkerIcon, iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], }); // 自定义本地机场数据Provider class LocalAirportProvider { constructor({ data }) { this.data = data.map(location => ({ ...location, lat: parseFloat(location.coordinates.split(',')[1].trim()), lng: parseFloat(location.coordinates.split(',')[0].trim()), })); } search({ query }) { if (!query) return []; const lowerQuery = query.toLowerCase(); return this.data .filter(location => location.name.toLowerCase().includes(lowerQuery) || location.ident.toLowerCase().includes(lowerQuery) || location.municipality.toLowerCase().includes(lowerQuery) ) .map(location => ({ x: location.lng, y: location.lat, label: `${location.iso_country} - ${location.name} (${location.ident})`, })); } } const Search = () => { const map = useMap(); useEffect(() => { const provider = new LocalAirportProvider({ data: markerData3 }); const searchControl = new GeoSearchControl({ provider: provider, style: 'bar', showMarker: true, showPopup: false, marker: { icon: customIcon, draggable: false, }, autoClose: true, searchLabel: '搜索机场...', retainZoomLevel: false, animateZoom: true, }).addTo(map); return () => { map.removeControl(searchControl); }; }, [map]); return null; }; // MapWithMarkers和AirportsMapNewAircraftSection组件保持不变 const MapWithMarkers = ({ onMarkerClick }) => { const map = useMap(); const markerClusterGroupRef = L.markerClusterGroup(); useEffect(() => { markerClusterGroupRef.clearLayers(); markerData3.forEach(marker => { const [lng, lat] = marker.coordinates.split(',').map(coord => parseFloat(coord.trim())); const leafletMarker = L.marker([lat, lng], { icon: customIcon }); leafletMarker.on('click', () => { onMarkerClick(`${marker.iso_country} - ${marker.name} - ${marker.ident}`); }); const popupContent = `<strong>${marker.ident}</strong><br>${marker.name}`; leafletMarker.bindPopup(popupContent); markerClusterGroupRef.addLayer(leafletMarker); }); map.addLayer(markerClusterGroupRef); return () => { map.removeLayer(markerClusterGroupRef); }; }, [map, onMarkerClick]); return null; }; const AirportsMapNewAircraftSection = ({ mapType, onMarkerClick }) => { const handleMarkerClick = (basePoint) => { onMarkerClick(basePoint) }; return ( <div className="AirportsMap" style={{ width: '100%'}}> <MapContainer center={[56.9235, 23.9710]} zoom={4} maxZoom={18} style={{ height: '500px', width: '100%', borderRadius: "0px 0px 10px 10px"}}> <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> <MapWithMarkers onMarkerClick={handleMarkerClick}/> <Search /> </MapContainer> </div> ); }; export default AirportsMapNewAircraftSection;
关键说明
- 自定义Provider的
search方法是核心,需返回符合格式的结果数组,其中x对应经度,y对应纬度,label是搜索框中显示的结果文本。 - 匹配逻辑可根据需求扩展,比如增加匹配
iso_country或type字段。 - 移除了原代码中无效的
provider.search({ query: '', ... })调用,因为搜索控件会自动响应用户输入触发搜索。
内容的提问来源于stack exchange,提问作者Destroyer369
相关产品推荐
相关产品推荐

