114 lines
No EOL
3.1 KiB
Markdown
114 lines
No EOL
3.1 KiB
Markdown
# Virtual Marker Scrolling for Large Datasets
|
|
|
|
## Overview
|
|
|
|
When dealing with thousands of APRS markers on the map, rendering all markers at once can cause performance issues. Virtual scrolling for map markers is a technique where only visible markers within the viewport (plus a buffer zone) are rendered.
|
|
|
|
## Current Implementation
|
|
|
|
The current implementation already has some optimizations:
|
|
1. Viewport-based filtering in `get_recent_packets_optimized/3`
|
|
2. Marker state tracking to prevent unnecessary updates
|
|
3. Batch processing of historical packets
|
|
|
|
## Proposed Virtual Scrolling Enhancement
|
|
|
|
### 1. Marker Clustering at Low Zoom Levels
|
|
```typescript
|
|
// Use Leaflet.markercluster for automatic clustering
|
|
// Already included in the project but not fully utilized
|
|
if (self.map.getZoom() < 10) {
|
|
// Use marker clustering
|
|
self.clusterLayer = L.markerClusterGroup({
|
|
maxClusterRadius: 80,
|
|
spiderfyOnMaxZoom: true,
|
|
showCoverageOnHover: false,
|
|
zoomToBoundsOnClick: true
|
|
});
|
|
}
|
|
```
|
|
|
|
### 2. Quadtree-based Spatial Indexing
|
|
```elixir
|
|
defmodule AprsmeWeb.MapLive.QuadTree do
|
|
@moduledoc """
|
|
Quadtree implementation for efficient spatial queries
|
|
"""
|
|
|
|
defstruct [:bounds, :markers, :children, :max_markers]
|
|
|
|
def insert(tree, marker) do
|
|
# Insert marker into appropriate quadrant
|
|
end
|
|
|
|
def query(tree, bounds) do
|
|
# Return only markers within bounds
|
|
end
|
|
end
|
|
```
|
|
|
|
### 3. Progressive Rendering with RequestIdleCallback
|
|
```typescript
|
|
function renderMarkersProgressively(markers: MarkerData[]) {
|
|
let index = 0;
|
|
const batchSize = 50;
|
|
|
|
function renderBatch() {
|
|
const batch = markers.slice(index, index + batchSize);
|
|
batch.forEach(marker => self.addMarker(marker));
|
|
index += batchSize;
|
|
|
|
if (index < markers.length) {
|
|
requestIdleCallback(renderBatch);
|
|
}
|
|
}
|
|
|
|
requestIdleCallback(renderBatch);
|
|
}
|
|
```
|
|
|
|
### 4. Server-side Aggregation
|
|
```elixir
|
|
def aggregate_markers_for_zoom(zoom_level, bounds) do
|
|
case zoom_level do
|
|
z when z < 8 ->
|
|
# Return aggregated grid cells with counts
|
|
Packets.get_grid_aggregates(bounds, grid_size: 50)
|
|
|
|
z when z < 12 ->
|
|
# Return simplified markers (no popups)
|
|
Packets.get_simplified_markers(bounds)
|
|
|
|
_ ->
|
|
# Return full markers
|
|
Packets.get_recent_packets_optimized(bounds)
|
|
end
|
|
end
|
|
```
|
|
|
|
### 5. Viewport Buffer Strategy
|
|
```typescript
|
|
// Render markers in expanded viewport to reduce re-renders during panning
|
|
const bounds = self.map.getBounds();
|
|
const bufferedBounds = bounds.pad(0.5); // 50% buffer
|
|
```
|
|
|
|
## Implementation Priority
|
|
|
|
1. **Phase 1**: Implement marker clustering (easiest, biggest impact)
|
|
2. **Phase 2**: Add server-side aggregation for low zoom levels
|
|
3. **Phase 3**: Implement progressive rendering
|
|
4. **Phase 4**: Add quadtree spatial indexing if still needed
|
|
|
|
## Performance Metrics to Track
|
|
|
|
- Time to first marker render
|
|
- Frame rate during pan/zoom
|
|
- Memory usage with 10k+ markers
|
|
- Server query time by zoom level
|
|
|
|
## Notes
|
|
|
|
- The current implementation already handles viewport filtering well
|
|
- Virtual scrolling is most beneficial when dealing with 5000+ markers
|
|
- Consider implementing only if performance issues are reported by users |