You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 13:22:13