109 lines
3.5 KiB
Dart
109 lines
3.5 KiB
Dart
import 'dart:math';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:latlong2/latlong.dart';
|
|
|
|
class ClusterParams {
|
|
final List<LatLng> points;
|
|
final double clusterRadiusMeters;
|
|
|
|
ClusterParams({
|
|
required this.points,
|
|
required this.clusterRadiusMeters,
|
|
});
|
|
}
|
|
|
|
class Cluster {
|
|
final LatLng center;
|
|
final List<LatLng> members;
|
|
|
|
Cluster(this.center, this.members);
|
|
}
|
|
// Use a more efficient quadtree-based clustering algorithm
|
|
Future<List<Cluster>> clusterMarkersQuadtreeIsolate(ClusterParams params) async {
|
|
return compute(_clusterMarkersQuadtree, params);
|
|
}
|
|
|
|
List<Cluster> _clusterMarkersQuadtree(ClusterParams params) {
|
|
final points = params.points;
|
|
final radius = params.clusterRadiusMeters;
|
|
final distance = const Distance();
|
|
final List<Cluster> clusters = [];
|
|
|
|
// Skip clustering if we have a small number of points
|
|
if (points.length < 100) {
|
|
return points.map((p) => Cluster(p, [p])).toList();
|
|
}
|
|
|
|
// Find bounds
|
|
double minLat = points[0].latitude;
|
|
double maxLat = points[0].latitude;
|
|
double minLng = points[0].longitude;
|
|
double maxLng = points[0].longitude;
|
|
|
|
for (final point in points) {
|
|
minLat = min(minLat, point.latitude);
|
|
maxLat = max(maxLat, point.latitude);
|
|
minLng = min(minLng, point.longitude);
|
|
maxLng = max(maxLng, point.longitude);
|
|
}
|
|
|
|
// Build spatial grid for faster lookups (simple spatial index)
|
|
// Convert geographic distance to approximate degrees
|
|
final double radiusDegLat = radius / 111000; // ~111km per degree latitude
|
|
final double radiusDegLng = radius / (111000 * cos(minLat * pi / 180)); // Adjust for longitude
|
|
|
|
final int gridLatSize = ((maxLat - minLat) / radiusDegLat).ceil();
|
|
final int gridLngSize = ((maxLng - minLng) / radiusDegLng).ceil();
|
|
|
|
// Create spatial grid
|
|
final List<List<List<LatLng>>> grid = List.generate(
|
|
gridLatSize + 1,
|
|
(_) => List.generate(gridLngSize + 1, (_) => <LatLng>[])
|
|
);
|
|
|
|
// Add points to grid cells
|
|
for (final point in points) {
|
|
final int latIdx = ((point.latitude - minLat) / radiusDegLat).floor();
|
|
final int lngIdx = ((point.longitude - minLng) / radiusDegLng).floor();
|
|
grid[latIdx][lngIdx].add(point);
|
|
}
|
|
|
|
// Process grid cells in batches
|
|
final Set<LatLng> processed = {};
|
|
|
|
for (int latIdx = 0; latIdx < gridLatSize; latIdx++) {
|
|
for (int lngIdx = 0; lngIdx < gridLngSize; lngIdx++) {
|
|
final cellPoints = grid[latIdx][lngIdx];
|
|
|
|
for (final point in cellPoints) {
|
|
if (processed.contains(point)) continue;
|
|
|
|
// Find nearby points
|
|
final List<LatLng> neighbors = [];
|
|
neighbors.add(point);
|
|
processed.add(point);
|
|
|
|
// Check current and adjacent cells for neighbors
|
|
for (int adjLat = max(0, latIdx - 1); adjLat <= min(gridLatSize - 1, latIdx + 1); adjLat++) {
|
|
for (int adjLng = max(0, lngIdx - 1); adjLng <= min(gridLngSize - 1, lngIdx + 1); adjLng++) {
|
|
for (final neighbor in grid[adjLat][adjLng]) {
|
|
if (!processed.contains(neighbor) && distance(point, neighbor) <= radius) {
|
|
neighbors.add(neighbor);
|
|
processed.add(neighbor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate cluster center
|
|
if (neighbors.isNotEmpty) {
|
|
final avgLat = neighbors.map((p) => p.latitude).reduce((a, b) => a + b) / neighbors.length;
|
|
final avgLng = neighbors.map((p) => p.longitude).reduce((a, b) => a + b) / neighbors.length;
|
|
clusters.add(Cluster(LatLng(avgLat, avgLng), neighbors));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return clusters;
|
|
} |