Skip to content

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

  1. Data Volume Control: Recommended not to load more than 100,000 points at once
  2. Clustering Configuration: Adjust clustering parameters based on actual data density
  3. LOD Configuration: Reasonably set minimum/maximum zoom levels
  4. Style Simplification: Avoid complex expressions and dynamic styles
  5. Chunking: Ultra-large datasets must use chunking
  6. Performance Monitoring: Regularly check rendering performance and optimize promptly