海量点
当需要在地图上显示大量点标记时(如数万、数十万甚至百万级别的点),需要使用特殊的优化策略来保证性能。SDK 提供了聚类、LOD(细节层次)等功能来优化海量点的渲染。
聚类显示
聚类是将相近的点合并显示,减少渲染负担的有效方法。
启用聚类
javascript
// 添加带聚类的 GeoJSON 源
sdk.addGeoJSON("mass-points-source", geojson, {
cluster: true, // 启用聚类
clusterRadius: 50, // 聚类半径(像素)
clusterMaxZoom: 16, // 最大聚类缩放级别
clusterMinPoints: 3, // 最小聚类点数
});
// 或者使用 GeoJSONManager 配置
sdk.geoJSONManager.setClusteringEnabled(true);
sdk.geoJSONManager.setClusteringConfig("mass-points-source", {
enabled: true,
radius: 50,
maxZoom: 16,
minPoints: 3,
});聚类样式
javascript
// 添加聚类点图层
sdk.addLayer({
id: "clusters",
source: "mass-points-source",
type: "circle",
filter: ["has", "point_count"], // 只显示聚类点
paint: {
"circle-color": [
"step",
["get", "point_count"],
"#51bbd6", // 小于100个点:蓝色
100,
"#f1f075", // 100-750个点:黄色
750,
"#f28cb1", // 大于750个点:粉色
],
"circle-radius": [
"step",
["get", "point_count"],
20, // 小于100个点:半径20
100,
30, // 100-750个点:半径30
750,
40, // 大于750个点:半径40
],
"circle-opacity": 0.8,
"circle-stroke-width": 2,
"circle-stroke-color": "#fff",
},
});
// 添加聚类数量标签
sdk.addLayer({
id: "cluster-count",
source: "mass-points-source",
type: "symbol",
filter: ["has", "point_count"],
layout: {
"text-field": "{point_count_abbreviated}", // 显示数量
"text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
"text-size": 12,
},
paint: {
"text-color": "#fff",
},
});单个点样式
javascript
// 添加单个点图层(非聚类点)
sdk.addLayer({
id: "unclustered-point",
source: "mass-points-source",
type: "circle",
filter: ["!", ["has", "point_count"]], // 非聚类点
paint: {
"circle-radius": 8,
"circle-color": "#11b4da",
"circle-stroke-width": 2,
"circle-stroke-color": "#fff",
},
});点击聚类展开
javascript
// 点击聚类时放大并展开
sdk.on("click", "clusters", (e) => {
const features = sdk.map.queryRenderedFeatures(e.point, {
layers: ["clusters"],
});
const clusterId = features[0].properties.cluster_id;
sdk.getSource("mass-points-source").getClusterExpansionZoom(
clusterId,
(err, zoom) => {
if (err) return;
sdk.flyTo({
center: e.lngLat,
zoom: zoom,
duration: 500,
});
}
);
});LOD(细节层次)优化
LOD 根据缩放级别动态显示/隐藏图层,减少低缩放级别时的渲染负担。
启用 LOD
javascript
// 启用 LOD
sdk.layerManager.setLODEnabled(true);
// 配置特定图层的 LOD
sdk.layerManager.setLayerLODConfig("mass-points-layer", {
minZoom: 10, // 最小显示缩放级别
maxZoom: 18, // 最大显示缩放级别
minPixelSize: 10, // 最小像素大小
maxPixelSize: 100, // 最大像素大小
cullingEnabled: true, // 启用视锥剔除
});动态 LOD 配置
javascript
// 根据缩放级别动态调整
sdk.on("zoom", () => {
const zoom = sdk.getZoom();
if (zoom < 8) {
// 低缩放级别:隐藏详细点
sdk.hideLayers(["mass-points-layer"]);
} else if (zoom < 12) {
// 中等缩放级别:显示简化点
sdk.showLayers(["mass-points-layer"]);
sdk.updateLayerStyle("mass-points-layer", {
paint: {
"circle-radius": 5,
},
});
} else {
// 高缩放级别:显示完整点
sdk.updateLayerStyle("mass-points-layer", {
paint: {
"circle-radius": 8,
},
});
}
});数据分片加载
对于超大数据集,可以采用分片加载策略。
按区域分片
javascript
// 根据地图边界动态加载数据
sdk.on("moveend", () => {
const bounds = sdk.getBounds();
loadPointsInBounds(bounds);
});
async function loadPointsInBounds(bounds) {
const [minLng, minLat] = bounds[0];
const [maxLng, maxLat] = bounds[1];
// 请求当前视野范围内的数据
const data = await fetch(
`/api/points?minLng=${minLng}&minLat=${minLat}&maxLng=${maxLng}&maxLat=${maxLat}`
).then(r => r.json());
// 更新源数据
sdk.getSource("mass-points-source").setData(data);
}按缩放级别分片
javascript
// 根据缩放级别加载不同详细程度的数据
sdk.on("zoomend", () => {
const zoom = sdk.getZoom();
if (zoom < 8) {
// 低缩放级别:加载概览数据
loadOverviewData();
} else if (zoom < 12) {
// 中等缩放级别:加载中等详细数据
loadMediumDetailData();
} else {
// 高缩放级别:加载详细数据
loadDetailData();
}
});性能优化技巧
1. 简化样式
javascript
// 使用简单的样式减少渲染负担
sdk.addLayer({
id: "mass-points",
source: "mass-points-source",
type: "circle",
paint: {
"circle-radius": 4, // 较小的半径
"circle-color": "#007bff", // 单一颜色
"circle-opacity": 0.8,
// 避免使用复杂表达式
},
});2. 限制可见范围
javascript
// 只显示视野范围内的点
sdk.addLayer({
id: "mass-points",
source: "mass-points-source",
type: "circle",
filter: [
"all",
[">=", ["get", "lng"], ["-", ["get", "minLng"], 0.1]],
["<=", ["get", "lng"], ["+", ["get", "maxLng"], 0.1]],
[">=", ["get", "lat"], ["-", ["get", "minLat"], 0.1]],
["<=", ["get", "lat"], ["+", ["get", "maxLat"], 0.1]],
],
paint: {
"circle-radius": 4,
"circle-color": "#007bff",
},
});3. 使用 Canvas 渲染
对于超大数据集,可以考虑使用 Canvas 2D 渲染:
javascript
// 使用 Canvas 渲染大量点
function renderPointsToCanvas(canvas, points, bounds) {
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
points.forEach(point => {
const pixel = sdk.project([point.lng, point.lat]);
ctx.fillStyle = "#007bff";
ctx.beginPath();
ctx.arc(pixel.x, pixel.y, 4, 0, Math.PI * 2);
ctx.fill();
});
}4. 防抖更新
javascript
// 使用防抖减少更新频率
let updateTimeout;
sdk.on("moveend", () => {
clearTimeout(updateTimeout);
updateTimeout = setTimeout(() => {
updatePoints();
}, 300); // 300ms 防抖
});完整示例
javascript
let sdk;
async function initMap() {
sdk = new navMap.MapSDK({
container: "map",
center: [116.39, 39.9],
zoom: 10,
performance: {
lodEnabled: true, // 启用 LOD
clusteringEnabled: true, // 启用聚类
},
});
sdk.on("loadComplete", () => {
setupMassPoints();
});
}
async function setupMassPoints() {
// 1. 生成或加载大量点数据
const points = generateMassPoints(100000); // 10万个点
const geojson = {
type: "FeatureCollection",
features: points.map((point, index) => ({
type: "Feature",
geometry: {
type: "Point",
coordinates: [point.lng, point.lat],
},
properties: {
id: index,
name: `Point ${index}`,
},
})),
};
// 2. 添加带聚类的源
sdk.addGeoJSON("mass-points-source", geojson, {
cluster: true,
clusterRadius: 50,
clusterMaxZoom: 16,
clusterMinPoints: 3,
});
// 3. 添加聚类图层
sdk.addLayer({
id: "clusters",
source: "mass-points-source",
type: "circle",
filter: ["has", "point_count"],
paint: {
"circle-color": [
"step",
["get", "point_count"],
"#51bbd6",
100,
"#f1f075",
750,
"#f28cb1",
],
"circle-radius": [
"step",
["get", "point_count"],
20,
100,
30,
750,
40,
],
},
});
// 4. 添加聚类标签
sdk.addLayer({
id: "cluster-count",
source: "mass-points-source",
type: "symbol",
filter: ["has", "point_count"],
layout: {
"text-field": "{point_count_abbreviated}",
"text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
"text-size": 12,
},
});
// 5. 添加单个点图层
sdk.addLayer({
id: "unclustered-point",
source: "mass-points-source",
type: "circle",
filter: ["!", ["has", "point_count"]],
paint: {
"circle-radius": 4,
"circle-color": "#11b4da",
},
});
// 6. 配置 LOD
sdk.layerManager.setLayerLODConfig("mass-points-layer", {
minZoom: 8,
maxZoom: 18,
});
// 7. 点击聚类展开
sdk.on("click", "clusters", (e) => {
const features = sdk.map.queryRenderedFeatures(e.point, {
layers: ["clusters"],
});
const clusterId = features[0].properties.cluster_id;
sdk.getSource("mass-points-source").getClusterExpansionZoom(
clusterId,
(err, zoom) => {
if (err) return;
sdk.flyTo({
center: e.lngLat,
zoom: zoom,
duration: 500,
});
}
);
});
}
function generateMassPoints(count) {
const points = [];
for (let i = 0; i < count; i++) {
points.push({
lng: 116 + Math.random() * 5, // 116-121
lat: 39 + Math.random() * 5, // 39-44
});
}
return points;
}
initMap();性能监控
javascript
// 监控渲染性能
sdk.on("render", () => {
const perfStats = sdk.getPerformanceStats();
console.log("可见图层数:", perfStats.visibleLayers);
console.log("LOD启用:", perfStats.lodEnabled);
console.log("聚类启用:", perfStats.clusteringEnabled);
});注意事项
- 数据量控制:单次加载的点数建议不超过 10 万
- 聚类配置:根据实际数据密度调整聚类参数
- LOD 配置:合理设置最小/最大缩放级别
- 样式简化:避免使用复杂表达式和动态样式
- 分片加载:超大数据集必须使用分片加载
- 性能监控:定期检查渲染性能,及时优化
