我正在开发一个近距离应用程序。数据库包含每个项目的经度、纬度和 geohash。客户端向服务器提供 GPS 坐标。然后,服务器将这些坐标转换为 geohash,并返回这些坐标附近的所有 GPS 点。实际上它看起来像这样:
概念验证工作顺利,但我在标记集群器实施方面遇到了困难。这是我目前得到的:
import { AdvancedMarker, Map, MapEvent } from '@vis.gl/react-google-maps';
我正在使用
"@vis.gl/react-google-maps": "^0.11.2",
此函数获取并设置数据:
async function fetchGlobalData(lng: number, lat: number) {
try {
const response = await axios.get(`${apiURL}/api/location-data?posX=${lng}&posY=${lat}`);
setItemsLocationData(response.data);
} catch (error: any) {
console.error(error);
}
}
谷歌地图组件:
<Map
mapId={mapId}
style={containerStyle}
defaultCenter={{ lat: gpsLocation.lat, lng: gpsLocation.lng }}
zoomControl={true}
defaultZoom={defaultMapZoom}
minZoom={minZoom}
gestureHandling={'greedy'}
disableDefaultUI={true}
onIdle={(e: MapEvent<unknown>) => {
let lng = e.map.getCenter()?.lng()
let lat = e.map.getCenter()?.lat()
let tempGeoHash = lng && lat && geohash.encode(lat, lng, 3)
if (currentGeoHash !== tempGeoHash && lat && lng) {
fetchGlobalData(lng, lat)
}
tempGeoHash && setCurrentGeoHash(tempGeoHash)
}}
>
<Markers coords={trainingSpotsLocationData} />
</Map>
这是我的标记组件,它呈现所有标记:
const Markers = ({ coords }: ImarkerProps) => {
return (
<>
{coords.map(x => {
return (
<AdvancedMarker
position={{ lng: x.posX, lat: x.posY }}
key={x.id}
></AdvancedMarker>
)
})}
</>
)
}
上面的代码运行没有问题。当我尝试实现 Marker Clusterer 时,问题就开始了。
我尝试使用 Leigh Halliday 的示例以及提供的源代码。我尝试按照示例中指定的方式从
MarkerClusterer
库实现 @googlemaps/markerclusterer
。
已更新:
下面是一些硬编码数据的重现错误。如果您只需使用您自己的 Google 地图 API 密钥和地图 ID 将其粘贴到您的 index.tsx 中,它就应该可以工作。
我尝试遵循这个例子。不确定这个实现是否适合标记数据可以动态的场景。
使用的封装:
"@googlemaps/markerclusterer": "^2.5.3",
"@vis.gl/react-google-maps": "^1.0.0",
import React, { useEffect, useRef, useState } from "react";
import ReactDOM from "react-dom/client";
import {
AdvancedMarker,
Map,
useMap,
APIProvider,
} from "@vis.gl/react-google-maps";
import { Marker, MarkerClusterer } from "@googlemaps/markerclusterer";
const rootElement = document.getElementById("root")!;
const root = ReactDOM.createRoot(rootElement);
const App = () => {
const [trainingSpotsLocationData, setTrainingSpotsLocationData] = useState<
IMapMarkers[]
>([]);
const dataset1 = [
{ id: "4a49d262", posX: 19.03565563713926, posY: 47.50797104332087 },
{ id: "3a432f12", posX: 19.03165563793341, posY: 47.50797104332977 },
];
const dataset2 = [
{ id: "28546515", posX: 18.913425585937492, posY: 47.51137228850743 },
{ id: "411bb9e5", posX: 18.675846240234367, posY: 47.46621059365244 },
{ id: "f832f6f1", posX: 18.579715869140617, posY: 47.478278133922885 },
];
// dataset3 is a superset of of dataset2. We can switch between dataset1 and dataset2
// without any issues, but becasue dataset3 contains items that were present in dataset1
// or dataset2 the clusterer breaks
const dataset3 = [
{ id: "4a49d262", posX: 19.03565563713926, posY: 47.50797104332087 },
{ id: "3a432f12", posX: 19.03165563793341, posY: 47.50797104332977 },
{ id: "28546515", posX: 18.913425585937492, posY: 47.51137228850743 },
{ id: "411bb9e5", posX: 18.675846240234367, posY: 47.46621059365244 },
{ id: "f832f6f1", posX: 18.579715869140617, posY: 47.478278133922885 },
];
return (
<>
<button
onClick={() => {
setTrainingSpotsLocationData(dataset1);
}}
>
Load dataset1
</button>
<button
onClick={() => {
setTrainingSpotsLocationData(dataset2);
}}
>
Load dataset2
</button>
<button
onClick={() => {
setTrainingSpotsLocationData(dataset3);
}}
>
Load dataset3
</button>
<APIProvider apiKey={"Your API key"}>
<Map
mapId={"Your map ID"}
style={{ width: "500px", height: "500px" }}
defaultCenter={{ lng: 19.0947, lat: 47.5636 }}
zoomControl={true}
defaultZoom={8}
minZoom={1}
disableDefaultUI={true}
>
<Markers coords={trainingSpotsLocationData} />
</Map>
</APIProvider>
</>
);
};
export interface IMapMarkers {
id: string;
posX: number;
posY: number;
}
export interface ImarkerProps {
coords: IMapMarkers[];
}
const Markers = ({ coords }: ImarkerProps) => {
const map = useMap();
const [markers, setMarkers] = useState<{ [key: string]: any }>({});
const clusterer = useRef<MarkerClusterer | null>(null);
useEffect(() => {
console.log(markers);
clusterer.current?.clearMarkers();
clusterer.current?.addMarkers(Object.values(markers));
}, [markers]);
useEffect(() => {
if (!map) return;
if (!clusterer.current) {
clusterer.current = new MarkerClusterer({ map });
}
}, [map]);
const setMarkerRef = (marker: Marker | null, key: string) => {
if (marker && markers[key]) return;
if (!marker && !markers[key]) return;
setMarkers((prev) => {
if (marker) {
return { ...prev, [key]: marker };
} else if (!marker) {
const newMarkers = { ...prev };
delete newMarkers[key];
return newMarkers;
}
});
};
return (
<>
{coords.map((x) => {
return (
<AdvancedMarker
position={{ lng: x.posX, lat: x.posY }}
key={x.id}
ref={(marker) => setMarkerRef(marker, x.id)}
></AdvancedMarker>
);
})}
</>
);
};
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
我的 tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
如果我从空数据集转换到数据集 3,它似乎确实有效,但是如果我从数据集 1 转换到数据集 3,我会得到
ReactJS: Maximum update depth exceeded error
。仅当新返回的数据包含状态数据中已有的一些记录时,才会发生这种情况。
我的 API 正在基于 geohash + 8 个最近邻居查询标记,因此如果下一个查询来自相邻的 geohash,则结果数据将包含上一个查询中的一些标记。因为 API 是无状态的,所以它不会跟踪以前加载的标记,因此我想在客户端处理它
我不确定为什么会发生这种情况,因为我的理解是这个 useEffect 在设置新标记之前会清除它;
useEffect(() => {
clusterer.current?.clearMarkers();
clusterer.current?.addMarkers(Object.values(markers));
}, [markers]);
如上所述,只有当新数据集至少包含先前加载的数据集中已存在的一条记录时,才会发生这种情况。
我的项目示例:
如果我进一步拖动地图以确保新旧数据之间没有重复,则它可以正常工作:
任何人都可以提供一个使用 Marker Clusterer 处理此场景的示例片段,其中地图数据可以更改,并且某些更改的数据可能是相同的?任何关于为什么会发生这种情况的见解将不胜感激!
我尝试从
MarkerClusterer
库实现 @googlemaps/markerclusterer
,如上面示例中指定的那样,我希望它的工作方式与静态数据相同,但事实并非如此。
我正在寻找一种在数据加载之间尽可能少重新渲染的解决方案。
我能够使用这个解决方案解决它
import React, { useEffect, useRef, useState } from "react";
import ReactDOM from "react-dom/client";
import {
AdvancedMarker,
Map,
useMap,
APIProvider,
useAdvancedMarkerRef,
} from "@vis.gl/react-google-maps";
import { Marker, MarkerClusterer } from "@googlemaps/markerclusterer";
const rootElement = document.getElementById("root")!;
const root = ReactDOM.createRoot(rootElement);
const App = () => {
const [trainingSpotsLocationData, setTrainingSpotsLocationData] = useState<
IMapMarkers[]
>([]);
const dataset1 = [
{ id: "4a49d262", posX: 19.03565563713926, posY: 47.50797104332087 },
{ id: "3a432f12", posX: 19.03165563793341, posY: 47.50797104332977 },
];
const dataset2 = [
{ id: "28546515", posX: 18.913425585937492, posY: 47.51137228850743 },
{ id: "411bb9e5", posX: 18.675846240234367, posY: 47.46621059365244 },
{ id: "f832f6f1", posX: 18.579715869140617, posY: 47.478278133922885 },
];
const dataset3 = [
{ id: "4a49d262", posX: 19.03565563713926, posY: 47.50797104332087 },
{ id: "3a432f12", posX: 19.03165563793341, posY: 47.50797104332977 },
{ id: "28546515", posX: 18.913425585937492, posY: 47.51137228850743 },
{ id: "411bb9e5", posX: 18.675846240234367, posY: 47.46621059365244 },
{ id: "f832f6f1", posX: 18.579715869140617, posY: 47.478278133922885 },
];
return (
<>
<button
onClick={() => {
setTrainingSpotsLocationData(dataset1);
}}
>
Load dataset1
</button>
<button
onClick={() => {
setTrainingSpotsLocationData(dataset2);
}}
>
Load dataset2
</button>
<button
onClick={() => {
setTrainingSpotsLocationData(dataset3);
}}
>
Load dataset3
</button>
<APIProvider apiKey={"your API kez"}>
<Map
mapId={"your map id"}
style={{ width: "500px", height: "500px" }}
defaultCenter={{ lng: 19.0947, lat: 47.5636 }}
zoomControl={true}
defaultZoom={8}
minZoom={1}
disableDefaultUI={true}
>
<CustomMarkers locations={trainingSpotsLocationData} />
</Map>
</APIProvider>
</>
);
};
export interface IMapMarkers {
id: string;
posX: number;
posY: number;
}
type CustomMarkerProps = {
location: IMapMarkers;
clusterer: MarkerClusterer | null;
};
const CustomMarker = ({ location, clusterer }: CustomMarkerProps) => {
// Marker Ref
const [refCallback, marker] = useAdvancedMarkerRef();
useEffect(() => {
if (!clusterer || !marker) return;
clusterer.addMarker(marker);
return () => {
clusterer.removeMarker(marker);
};
}, [clusterer, marker]);
return (
<AdvancedMarker
position={{ lng: location.posX, lat: location.posY }}
key={location.id}
ref={refCallback}
/>
);
};
const CustomMarkers = ({ locations }: { locations: IMapMarkers[] }) => {
const map = useMap();
const clusterer = useRef<MarkerClusterer | null>(null);
useEffect(() => {
if (!map) return;
if (!clusterer.current) {
clusterer.current = new MarkerClusterer({ map });
}
}, [map]);
return (
<>
{locations.map((location) => (
<CustomMarker
key={location.id}
location={location}
clusterer={clusterer.current}
/>
))}
</>
);
};
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);