Mass Points
When you need to display large numbers of point markers on the map (such as tens of thousands, hundreds of thousands, or even millions of points), special optimization strategies are needed to ensure performance. The SDK provides clustering, LOD (Level of Detail), and other features to optimize rendering of mass points.
Clustering Display
Clustering is an effective method to merge nearby points for display, reducing rendering burden.
Enable Clustering
javascript
// Add GeoJSON source with clustering
sdk.addGeoJSON("mass-points-source", geojson, {
cluster: true, // Enable clustering
clusterRadius: 50, // Clustering radius (pixels)
clusterMaxZoom: 16, // Maximum clustering zoom level
clusterMinPoints: 3, // Minimum clustering point count
});
// Or use GeoJSONManager configuration
sdk.geoJSONManager.setClusteringEnabled(true);
sdk.geoJSONManager.setClusteringConfig("mass-points-source", {
enabled: true,
radius: 50,
maxZoom: 16,
minPoints: 3,
});Clustering Styles
javascript
// Add clustering point layer
sdk.addLayer({
id: "clusters",
source: "mass-points-source",
type: "circle",
filter: ["has", "point_count"], // Only show clustering points
paint: {
"circle-color": [
"step",
["get", "point_count"],
"#51bbd6", // Less than 100 points: blue
100,
"#f1f075", // 100-750 points: yellow
750,
"#f28cb1", // More than 750 points: pink
],
"circle-radius": [
"step",
["get", "point_count"],
20, // Less than 100 points: radius 20
100,
30, // 100-750 points: radius 30
750,
40, // More than 750 points: radius 40
],
"circle-opacity": 0.8,
"circle-stroke-width": 2,
"circle-stroke-color": "#fff",
},
});
// Add clustering count label
sdk.addLayer({
id: "cluster-count",
source: "mass-points-source",
type: "symbol",
filter: ["has", "point_count"],
layout: {
"text-field": "{point_count_abbreviated}", // Display count
"text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
"text-size": 12,
},
paint: {
"text-color": "#fff",
},
});Single Point Style
javascript
// Add single point layer (non-clustered points)
sdk.addLayer({
id: "unclustered-point",
source: "mass-points-source",
type: "circle",
filter: ["!", ["has", "point_count"]], // Non-clustered points
paint: {
"circle-radius": 8,
"circle-color": "#11b4da",
"circle-stroke-width": 2,
"circle-stroke-color": "#fff",
},
});Click Cluster to Expand
javascript
// Zoom in and expand when clicking cluster
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 (Level of Detail) Optimization
LOD dynamically shows/hides layers based on zoom level, reducing rendering burden at low zoom levels.
Enable LOD
javascript
// Enable LOD
sdk.layerManager.setLODEnabled(true);
// Configure LOD for specific layer
sdk.layerManager.setLayerLODConfig("mass-points-layer", {
minZoom: 10, // Minimum display zoom level
maxZoom: 18, // Maximum display zoom level
minPixelSize: 10, // Minimum pixel size
maxPixelSize: 100, // Maximum pixel size
cullingEnabled: true, // Enable frustum culling
});Dynamic LOD Configuration
javascript
// Dynamically adjust based on zoom level
sdk.on("zoom", () => {
const zoom = sdk.getZoom();
if (zoom < 8) {
// Low zoom level: hide detail points
sdk.hideLayers(["mass-points-layer"]);
} else if (zoom < 12) {
// Medium zoom level: show simplified points
sdk.showLayers(["mass-points-layer"]);
sdk.updateLayerStyle("mass-points-layer", {
paint: {
"circle-radius": 5,
},
});
} else {
// High zoom level: show full points
sdk.updateLayerStyle("mass-points-layer", {
paint: {
"circle-radius": 8,
},
});
}
});Data Chunking
For ultra-large datasets, chunking strategies can be adopted.
Chunk by Region
javascript
// Dynamically load data based on map bounds
sdk.on("moveend", () => {
const bounds = sdk.getBounds();
loadPointsInBounds(bounds);
});
async function loadPointsInBounds(bounds) {
const [minLng, minLat] = bounds[0];
const [maxLng, maxLat] = bounds[1];
// Request data within current view
const data = await fetch(
`/api/points?minLng=${minLng}&minLat=${minLat}&maxLng=${maxLng}&maxLat=${maxLat}`
).then(r => r.json());
// Update source data
sdk.getSource("mass-points-source").setData(data);
}Chunk by Zoom Level
javascript
// Load different detail levels based on zoom level
sdk.on("zoomend", () => {
const zoom = sdk.getZoom();
if (zoom < 8) {
// Low zoom level: load overview data
loadOverviewData();
} else if (zoom < 12) {
// Medium zoom level: load medium detail data
loadMediumDetailData();
} else {
// High zoom level: load detail data
loadDetailData();
}
});Performance Optimization Tips
1. Simplify Styles
javascript
// Use simple styles to reduce rendering burden
sdk.addLayer({
id: "mass-points",
source: "mass-points-source",
type: "circle",
paint: {
"circle-radius": 4, // Smaller radius
"circle-color": "#007bff", // Single color
"circle-opacity": 0.8,
// Avoid complex expressions
},
});2. Limit Visible Range
javascript
// Only show points within view
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. Use Canvas Rendering
For ultra-large datasets, consider using Canvas 2D rendering:
javascript
// Use Canvas to render large numbers of points
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. Debounce Updates
javascript
// Use debouncing to reduce update frequency
let updateTimeout;
sdk.on("moveend", () => {
clearTimeout(updateTimeout);
updateTimeout = setTimeout(() => {
updatePoints();
}, 300); // 300ms debounce
});Complete Example
javascript
let sdk;
async function initMap() {
sdk = new navMap.MapSDK({
container: "map",
center: [116.39, 39.9],
zoom: 10,
performance: {
lodEnabled: true, // Enable LOD
clusteringEnabled: true, // Enable clustering
},
});
sdk.on("loadComplete", () => {
setupMassPoints();
});
}
async function setupMassPoints() {
// 1. Generate or load large number of point data
const points = generateMassPoints(100000); // 100,000 points
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. Add source with clustering
sdk.addGeoJSON("mass-points-source", geojson, {
cluster: true,
clusterRadius: 50,
clusterMaxZoom: 16,
clusterMinPoints: 3,
});
// 3. Add clustering layer
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. Add clustering label
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. Add single point layer
sdk.addLayer({
id: "unclustered-point",
source: "mass-points-source",
type: "circle",
filter: ["!", ["has", "point_count"]],
paint: {
"circle-radius": 4,
"circle-color": "#11b4da",
},
});
// 6. Configure LOD
sdk.layerManager.setLayerLODConfig("mass-points-layer", {
minZoom: 8,
maxZoom: 18,
});
// 7. Click cluster to expand
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();Performance Monitoring
javascript
// Monitor rendering performance
sdk.on("render", () => {
const perfStats = sdk.getPerformanceStats();
console.log("Visible layers:", perfStats.visibleLayers);
console.log("LOD enabled:", perfStats.lodEnabled);
console.log("Clustering enabled:", perfStats.clusteringEnabled);
});Notes
- Data Volume Control: Recommended not to load more than 100,000 points at once
- Clustering Configuration: Adjust clustering parameters based on actual data density
- LOD Configuration: Reasonably set minimum/maximum zoom levels
- Style Simplification: Avoid complex expressions and dynamic styles
- Chunking: Ultra-large datasets must use chunking
- Performance Monitoring: Regularly check rendering performance and optimize promptly
