import React, { useEffect, useRef, useState } from 'react';
// eslint-disable-line import/no-webpack-loader-syntax
// @ts-ignore
import mapboxgl from '!mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import Image from 'next/image';
import moment from 'moment';
import { MAP_STYLES, CONFIG_MAP } from '@/components/MapContainer/config';
import styles from './styles.module.scss';
interface MapProps {
config: any;
heatmap: any;
}
interface MapInfo {
sourceId: string;
layerId: any;
data: any;
config: any;
urlObj: any;
}
export const MapContainer: React.FC<MapProps> = ({ config, heatmap }) => {
const defaultCenter = [-84.45, 33.76];
const defaultZoom = 10;
const center = heatmap?.center || defaultCenter;
const mapContainer = useRef<any>(null);
const map = useRef<mapboxgl.Map | any>(null);
const [lng, setLng] = useState(center[0]);
const [lat, setLat] = useState(center[1]);
const [zoom, setZoom] = useState(config?.zoom_level || defaultZoom);
const [mapStyle, setMapStyle] = useState(MAP_STYLES[0].style);
const [mapInfo, setMapInfo] = useState<MapInfo[]>([]);
const [isMenuOpen, setIsMenuOpen] = useState(false);
useEffect(() => {
console.log('Map instance:', map.current);
if (mapContainer.current && !map.current) {
mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN as string;
map.current = new mapboxgl.Map({
container: mapContainer.current,
style: mapStyle,
center: center,
zoom: zoom,
});
map.current.on('load', () => {
heatmap?.data?.forEach((data: any, index: any) => {
loadGeoJson(data, index);
});
map.current?.addControl(
new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true,
},
trackUserLocation: true,
showUserHeading: true,
}),
);
map.current?.addControl(new mapboxgl.NavigationControl());
});
map.current.on('move', () => {
if(map.current) {
setLng(map.current.getCenter().lng);
setLat(map.current.getCenter().lat);
setZoom(map.current.getZoom());
}
});
}
return () => {
if (map.current) {
map.current.remove();
}
};
}, []);
useEffect(() => {
if (map.current) {
map.current.setStyle(mapStyle);
}
}, [mapStyle]);
const toggleMenu = () => {
setIsMenuOpen(!isMenuOpen);
};
const handleStyleChange = (style: string) => {
setMapStyle(style);
setIsMenuOpen(false);
};
const parseDate = (inputDate: any) => {
const cleanedDate = inputDate.replace(/<br\/>/g, '').trim();
return moment(cleanedDate, 'Do MMM YYYY').format('YYYY-MM-DD');
};
const parseTime = (inputTime: any) => {
return moment(inputTime, 'h A').format('HH');
};
const loadGeoJson = async (config: any, layerId: any) => {
try {
console.log('Loading GeoJSON data:', config?.url);
const response = await fetch(config?.url);
if (!response.ok) {
new Error(`Error loading GeoJSON data: ${response.statusText}`);
}
const data = await response.json();
const {uniqueSourceId} = configureMap(map, data, layerId, config);
const _mapInfo = [...mapInfo, {
sourceId: uniqueSourceId,
layerId: layerId,
data: data,
config: config,
urlObj: config?.url,
}];
setMapInfo(_mapInfo);
} catch (error) {
console.error('Error loading GeoJSON data:', error);
}
};
const configureMap = (map: mapboxgl.Map, data: any, layerId: any, conf: any) => {
const uniqueLayerId = `cluster-layer-${layerId}`;
const uniqueSourceId = `cluster-source-${layerId}`;
config.functions.forEach((func: any) => {
const {name, layerType = "clustered", args} = func;
// @ts-ignore
CONFIG_MAP[name]({args, map, data, layerType, layerId, uniqueLayerId, uniqueSourceId}, conf);
});
return {
uniqueSourceId,
uniqueLayerId,
};
};
return (
<main className={styles['custom-map-container']}>
<div className='relative w-full h-full'>
<div ref={mapContainer} className='w-full h-full'/>
<div className='absolute bottom-4 right-4 flex items-center'>
<div className='relative'>
<button
className={`bg-[#b0b0b0] text-white p-1 flex items-center space-x-2 flex-col ${
isMenuOpen ? 'rounded-tr-md rounded-br-md' : 'rounded'
}`}
onClick={toggleMenu}
style={{fontSize: '10px'}}
>
<span>
<Image
src={MAP_STYLES[0].icon}
alt={MAP_STYLES[0].name}
className='w-10 h-10 cursor-pointer'
onClick={() => handleStyleChange(MAP_STYLES[0].style)}
/>
</span>
<span className='text-center'>Streets</span>
{isMenuOpen && (
<div
className='absolute right-full top-0 mr-2 bg-[#b0b0b0] p-1 flex space-x-2 rounded-tl-md rounded-bl-md'>
{MAP_STYLES.slice(1).map(({style, name, icon}) => (
<div key={style} className='flex flex-col items-center'>
<span className='flex items-center justify-center w-10 h-10'>
<Image
src={icon}
alt={name}
className='w-full h-full object-contain cursor-pointer'
onClick={() => handleStyleChange(style)}
/>
</span>
<span className='text-white text-center text-[10px]'>{name}</span>
</div>
))}
</div>
)}
</button>
</div>
</div>
<div className='ml-auto align-items-center flex text-sm mt-1 mb-3'>
Longitude: {lng?.toFixed(2)} | Latitude: {lat?.toFixed(2)} | Zoom: {zoom?.toFixed(2)}
</div>
</div>
</main>
);
};
以上是我的完整代码
但是,控制台中没有错误,在记录 MapInstance 时,它会记录两次,第一次获取 null,然后第二次提供地图实例。
我尝试过不同的事情。但无法解决这个问题。
我尝试修改 webpack 来处理加载工作文件,我认为无论如何这都是不必要的,但这也不起作用。
如果您有任何想法,我们将不胜感激:)
将 Mapbox 与 NextJS 结合使用没有任何问题。我建议将您的代码简化为基本的“在页面上显示地图”,然后从那里返回。
如果您提供了所看到的错误的重现或带有存储库的codesandbox的链接,那么很可能有人会花时间浏览您的代码。