/** * Baidu Map GL Component * * React map component based on Baidu Map WebGL API, supports custom markers, zoom levels and other configurations * * Usage example: * * // Optional title component * */ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, } from "react"; /** Map context properties */ type MapContextProps = { // Address address?: string; /** Map marker address */ }; const MapContext = createContext(null); /** Default map configuration */ const defaultOption = { zoom: 15, /** Default zoom level */ lng: 116.404, /** Default longitude (Beijing Tiananmen Square) */ lat: 39.915, /** Default latitude (Beijing Tiananmen Square) */ address: "Chang'an Street, Dongcheng District, Beijing", /** Default address */ }; const loadScript = (src: string) => { return new Promise((ok, fail) => { const script = document.createElement("script"); script.onerror = (reason) => fail(reason); if (~src.indexOf("{{callback}}")) { const callbackFn = `loadscriptcallback_${(+new Date()).toString(36)}`; (window as any)[callbackFn] = () => { ok(); delete (window as any)[callbackFn]; }; src = src.replace("{{callback}}", callbackFn); } else { script.onload = () => ok(); } script.src = src; document.head.appendChild(script); }); }; const useMap = () => { const context = useContext(MapContext); if (!context) { return {}; } return context; }; /** * Map title component * @param {string} className - Custom class name */ const MapTitle = ({ className }: React.ComponentProps<"div">) => { const { address } = useMap(); if (!address) return null; return {address}; }; // Record Baidu Map SDK loading status let BMapGLLoadingPromise: Promise | null = null; /** * Baidu Map main component * @param {string} ak - Baidu Map API key, defaults to 'OeTpXHgdUrRT2pPyAPRL7pog6GlMlQzl' * @param {object} option - Map configuration options * @param {number} option.zoom - Map zoom level * @param {number} option.lng - Longitude coordinate * @param {number} option.lat - Latitude coordinate * @param {string} option.address - Marker address * @param {string} className - Container custom class name * @param {ReactNode} children - Child components, usually MapTitle */ const Map = ({ ak, option, className, children, ...props }: React.ComponentProps<"div"> & { ak: string; option?: { zoom: number; lng: number; lat: number; address: string; }; }) => { const mapRef = useRef(null); const currentRef = useRef(null); const _options = useMemo(() => { return { ...defaultOption, ...option }; }, [option]); const contextValue = useMemo( () => ({ address: option?.address, }), [option?.address] ); const initMap = useCallback(() => { if (!mapRef.current) return; let map = currentRef.current; if (!map) { // Create map instance map = new (window as any).BMapGL.Map(mapRef.current); currentRef.current = map; } // Clear overlays map.clearOverlays(); // Set map center coordinates and map level const center = new (window as any).BMapGL.Point( _options?.lng, _options?.lat ); map.centerAndZoom(center, _options?.zoom); // Add marker const marker = new (window as any).BMapGL.Marker(center); map.addOverlay(marker); }, [_options]); useEffect(() => { // Check if Baidu Map API is loaded if ((window as any).BMapGL) { initMap(); } else if (BMapGLLoadingPromise) { BMapGLLoadingPromise.then(initMap).then(() => { BMapGLLoadingPromise = null; }); } else { BMapGLLoadingPromise = loadScript( `//api.map.baidu.com/api?type=webgl&v=1.0&ak=${ak}&callback={{callback}}` ); BMapGLLoadingPromise.then(initMap).then(() => { BMapGLLoadingPromise = null; }); } }, [ak, initMap]); useEffect(() => { return () => { if (currentRef.current) { currentRef.current = null; } }; }, []); return (
{children}
); }; export { Map, MapTitle };