feat : map
This commit is contained in:
109
features/supervision/lib/data/utils/cluster_generator.dart
Normal file
109
features/supervision/lib/data/utils/cluster_generator.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
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;
|
||||
}
|
||||
57
features/supervision/lib/data/utils/marker_generator.dart
Normal file
57
features/supervision/lib/data/utils/marker_generator.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'dart:math';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
class GridGenParams {
|
||||
final LatLng center;
|
||||
final int count;
|
||||
final double spacingMeters;
|
||||
|
||||
const GridGenParams({
|
||||
required this.center,
|
||||
required this.count,
|
||||
required this.spacingMeters,
|
||||
});
|
||||
}Future<List<LatLng>> generateGridMarkersIsolate(GridGenParams params) async {
|
||||
return compute(_generateGridMarkersOptimized, params);
|
||||
}
|
||||
|
||||
List<LatLng> _generateGridMarkersOptimized(GridGenParams params) {
|
||||
final List<LatLng> result = [];
|
||||
final Distance distance = const Distance();
|
||||
|
||||
// Pre-calculate the grid dimensions
|
||||
final int gridSize = sqrt(params.count).ceil();
|
||||
final double halfWidth = (gridSize * params.spacingMeters) / 2;
|
||||
final double halfHeight = (gridSize * params.spacingMeters) / 2;
|
||||
|
||||
// Calculate top-left corner of the grid
|
||||
final LatLng topLeft = distance.offset(
|
||||
distance.offset(params.center, -halfHeight, 0), // south
|
||||
-halfWidth, 270 // west
|
||||
);
|
||||
|
||||
// Generate grid in batches for better memory management
|
||||
const int batchSize = 10000;
|
||||
for (int batch = 0; batch < (params.count / batchSize).ceil(); batch++) {
|
||||
final int startIdx = batch * batchSize;
|
||||
final int endIdx = min((batch + 1) * batchSize, params.count);
|
||||
|
||||
for (int i = startIdx; i < endIdx; i++) {
|
||||
final int row = i ~/ gridSize;
|
||||
final int col = i % gridSize;
|
||||
|
||||
final double dx = col * params.spacingMeters;
|
||||
final double dy = row * params.spacingMeters;
|
||||
|
||||
final LatLng point = distance.offset(
|
||||
distance.offset(topLeft, dy, 180), // south
|
||||
dx, 90 // east
|
||||
);
|
||||
|
||||
result.add(point);
|
||||
}
|
||||
}
|
||||
|
||||
return result.sublist(0, min(result.length, params.count));
|
||||
}
|
||||
16
features/supervision/lib/presentation/routes/app_pages.dart
Normal file
16
features/supervision/lib/presentation/routes/app_pages.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
import 'package:supervision/presentation/filter/logic.dart';
|
||||
import 'package:supervision/presentation/filter/view.dart';
|
||||
import 'package:supervision/presentation/routes/app_routes.dart';
|
||||
|
||||
sealed class SupervisionPages {
|
||||
SupervisionPages._();
|
||||
|
||||
static final pages = [
|
||||
GetPage(
|
||||
name: SupervisionRoutes.supervision,
|
||||
page: () => SupervisionFilterPage(),
|
||||
binding: BindingsBuilder.put(() => BazresiLogic()),
|
||||
),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
sealed class SupervisionRoutes {
|
||||
SupervisionRoutes._();
|
||||
|
||||
|
||||
static const supervision = '/supervision';
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnimatedClusterMarker extends StatefulWidget {
|
||||
final int count;
|
||||
|
||||
const AnimatedClusterMarker({super.key, required this.count});
|
||||
|
||||
@override
|
||||
State<AnimatedClusterMarker> createState() => _AnimatedClusterMarkerState();
|
||||
}
|
||||
|
||||
class _AnimatedClusterMarkerState extends State<AnimatedClusterMarker>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
)..forward(); // start animation
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScaleTransition(
|
||||
scale: CurvedAnimation(parent: _controller, curve: Curves.easeOutBack),
|
||||
child: Opacity(
|
||||
opacity: _controller.value,
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueAccent,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
),
|
||||
child: Text(
|
||||
widget.count.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
7
features/supervision/lib/supervision.dart
Normal file
7
features/supervision/lib/supervision.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
library;
|
||||
|
||||
export 'presentation/filter/logic.dart';
|
||||
export 'presentation/filter/view.dart';
|
||||
export 'presentation/routes/app_pages.dart';
|
||||
export 'presentation/routes/app_routes.dart';
|
||||
|
||||
17
features/supervision/pubspec.yaml
Normal file
17
features/supervision/pubspec.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
name: supervision
|
||||
description: "A new Flutter project."
|
||||
publish_to: 'none'
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: ^3.7.0
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
rasadyar_core:
|
||||
path: ./../../packages/core
|
||||
flutter_map: ^8.1.1
|
||||
location: ^8.0.0
|
||||
latlong2: ^0.9.1
|
||||
geolocator: ^13.0.4
|
||||
Reference in New Issue
Block a user