mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-07-14 21:02:03 +10:00
Refactor map initialization and zoom calculation logic in MapScreen
This commit is contained in:
+66
-28
@@ -1,8 +1,9 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import '../connector/meshcore_connector.dart';
|
import '../connector/meshcore_connector.dart';
|
||||||
import '../l10n/l10n.dart';
|
import '../l10n/l10n.dart';
|
||||||
@@ -48,6 +49,8 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
final Set<String> _hiddenMarkerIds = {};
|
final Set<String> _hiddenMarkerIds = {};
|
||||||
Set<String> _removedMarkerIds = {};
|
Set<String> _removedMarkerIds = {};
|
||||||
bool _isSelectingPoi = false;
|
bool _isSelectingPoi = false;
|
||||||
|
bool _hasInitializedMap = false;
|
||||||
|
bool _removedMarkersLoaded = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -68,6 +71,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_removedMarkerIds = ids;
|
_removedMarkerIds = ids;
|
||||||
|
_removedMarkersLoaded = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +94,16 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
return sqrt(variance);
|
return sqrt(variance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate zoom level based on the spread of points (std deviation in degrees)
|
||||||
|
double _zoomFromStdDev(double latStdDev, double lonStdDev) {
|
||||||
|
final maxSpread = max(latStdDev, lonStdDev);
|
||||||
|
if (maxSpread <= 0) return 13.0;
|
||||||
|
// Approzimate: each zoom level halves the visible area
|
||||||
|
// ~0.01 degrees spread -> zoom 13, ~0.1 -> zoom 10, ~1.0 -> zoom 7
|
||||||
|
final zoom = 10.0 - log(maxSpread * 10 + 1) / ln10 * 3;
|
||||||
|
return zoom.clamp(4.0, 15.0);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer2<MeshCoreConnector, AppSettingsService>(
|
return Consumer2<MeshCoreConnector, AppSettingsService>(
|
||||||
@@ -133,16 +147,15 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
.where((c) => c.hasLocation)
|
.where((c) => c.hasLocation)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// Calculate center of all nodes, or default to (0, 0)
|
// Calculate center and zoom of all nodes, or default to (0, 0)
|
||||||
LatLng center = const LatLng(0, 0);
|
LatLng center = const LatLng(0, 0);
|
||||||
|
double initialZoom = 10.0;
|
||||||
final hasMapContent =
|
final hasMapContent =
|
||||||
contactsWithLocation.isNotEmpty ||
|
contactsWithLocation.isNotEmpty ||
|
||||||
sharedMarkers.isNotEmpty ||
|
sharedMarkers.isNotEmpty ||
|
||||||
_isSelectingPoi ||
|
_isSelectingPoi ||
|
||||||
highlightPosition != null;
|
highlightPosition != null;
|
||||||
if (contactsWithLocation.isNotEmpty || sharedMarkers.isNotEmpty) {
|
if (contactsWithLocation.isNotEmpty || sharedMarkers.isNotEmpty) {
|
||||||
double avgLat = 0.0;
|
|
||||||
double avgLon = 0.0;
|
|
||||||
final allPoints = [
|
final allPoints = [
|
||||||
...contactsWithLocation.map(
|
...contactsWithLocation.map(
|
||||||
(c) => LatLng(c.latitude!, c.longitude!),
|
(c) => LatLng(c.latitude!, c.longitude!),
|
||||||
@@ -153,35 +166,48 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
final latValues = allPoints.map((p) => p.latitude).toList();
|
final latValues = allPoints.map((p) => p.latitude).toList();
|
||||||
final lonValues = allPoints.map((p) => p.longitude).toList();
|
final lonValues = allPoints.map((p) => p.longitude).toList();
|
||||||
|
|
||||||
|
final meanLat =
|
||||||
|
latValues.reduce((a, b) => a + b) / latValues.length;
|
||||||
|
final meanLon =
|
||||||
|
lonValues.reduce((a, b) => a + b) / lonValues.length;
|
||||||
final latStdDev = _standardDeviation(latValues);
|
final latStdDev = _standardDeviation(latValues);
|
||||||
final lonStdDev = _standardDeviation(lonValues);
|
final lonStdDev = _standardDeviation(lonValues);
|
||||||
final filteredLatValues = latValues
|
|
||||||
|
final filteredPoints = allPoints
|
||||||
.where(
|
.where(
|
||||||
(lat) =>
|
(p) =>
|
||||||
(lat -
|
(p.latitude - meanLat).abs() <= latStdDev * 2 &&
|
||||||
(latValues.reduce((a, b) => a + b) /
|
(p.longitude - meanLon).abs() <= lonStdDev * 2,
|
||||||
latValues.length))
|
|
||||||
.abs() <=
|
|
||||||
latStdDev * 2,
|
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
final filteredLonValues = lonValues
|
|
||||||
.where(
|
if (filteredPoints.isNotEmpty) {
|
||||||
(lon) =>
|
final filteredLatValues = filteredPoints
|
||||||
(lon -
|
.map((p) => p.latitude)
|
||||||
(lonValues.reduce((a, b) => a + b) /
|
.toList();
|
||||||
lonValues.length))
|
final filteredLonValues = filteredPoints
|
||||||
.abs() <=
|
.map((p) => p.longitude)
|
||||||
lonStdDev * 2,
|
.toList();
|
||||||
)
|
final avgLat = filteredLatValues.reduce((a, b) => a + b);
|
||||||
.toList();
|
final avgLon = filteredLonValues.reduce((a, b) => a + b);
|
||||||
center = LatLng(
|
center = LatLng(
|
||||||
filteredLatValues.reduce((a, b) => a + b) /
|
avgLat / filteredPoints.length,
|
||||||
filteredLatValues.length,
|
avgLon / filteredPoints.length,
|
||||||
filteredLonValues.reduce((a, b) => a + b) /
|
);
|
||||||
filteredLonValues.length,
|
// Use std deviation of filtered points for zoom
|
||||||
);
|
final filteredLatStdDev = _standardDeviation(filteredLatValues);
|
||||||
|
final filteredLonStdDev = _standardDeviation(filteredLonValues);
|
||||||
|
initialZoom = _zoomFromStdDev(
|
||||||
|
filteredLatStdDev,
|
||||||
|
filteredLonStdDev,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
center = LatLng(meanLat, meanLon);
|
||||||
|
initialZoom = _zoomFromStdDev(latStdDev, lonStdDev);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
double avgLat = 0.0;
|
||||||
|
double avgLon = 0.0;
|
||||||
for (final point in allPoints) {
|
for (final point in allPoints) {
|
||||||
avgLat += point.latitude;
|
avgLat += point.latitude;
|
||||||
avgLon += point.longitude;
|
avgLon += point.longitude;
|
||||||
@@ -190,10 +216,22 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
avgLat / allPoints.length,
|
avgLat / allPoints.length,
|
||||||
avgLon / allPoints.length,
|
avgLon / allPoints.length,
|
||||||
);
|
);
|
||||||
|
initialZoom = 12.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (highlightPosition != null) {
|
if (highlightPosition != null) {
|
||||||
center = highlightPosition;
|
center = highlightPosition;
|
||||||
|
initialZoom = widget.highlightZoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re center map after removed markers have loaded
|
||||||
|
if (!_hasInitializedMap && _removedMarkersLoaded && hasMapContent) {
|
||||||
|
_hasInitializedMap = true;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) {
|
||||||
|
_mapController.move(center, initialZoom);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final allowBack = !connector.isConnected;
|
final allowBack = !connector.isConnected;
|
||||||
@@ -232,7 +270,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
mapController: _mapController,
|
mapController: _mapController,
|
||||||
options: MapOptions(
|
options: MapOptions(
|
||||||
initialCenter: center,
|
initialCenter: center,
|
||||||
initialZoom: 10.0,
|
initialZoom: initialZoom,
|
||||||
minZoom: 2.0,
|
minZoom: 2.0,
|
||||||
maxZoom: 18.0,
|
maxZoom: 18.0,
|
||||||
onTap: (_, latLng) {
|
onTap: (_, latLng) {
|
||||||
|
|||||||
Reference in New Issue
Block a user