|
|
|
@ -52,8 +52,8 @@ class PathTraceMapScreen extends StatefulWidget {
|
|
|
|
final String title;
|
|
|
|
final String title;
|
|
|
|
final Uint8List path;
|
|
|
|
final Uint8List path;
|
|
|
|
final int? repeaterId;
|
|
|
|
final int? repeaterId;
|
|
|
|
final bool flipPathRound;
|
|
|
|
final bool flipPathAround;
|
|
|
|
final bool reversePathRound;
|
|
|
|
final bool reversePathAround;
|
|
|
|
final Contact? targetContact;
|
|
|
|
final Contact? targetContact;
|
|
|
|
|
|
|
|
|
|
|
|
const PathTraceMapScreen({
|
|
|
|
const PathTraceMapScreen({
|
|
|
|
@ -61,8 +61,8 @@ class PathTraceMapScreen extends StatefulWidget {
|
|
|
|
required this.title,
|
|
|
|
required this.title,
|
|
|
|
required this.path,
|
|
|
|
required this.path,
|
|
|
|
this.repeaterId,
|
|
|
|
this.repeaterId,
|
|
|
|
this.flipPathRound = false,
|
|
|
|
this.flipPathAround = false,
|
|
|
|
this.reversePathRound = false,
|
|
|
|
this.reversePathAround = false,
|
|
|
|
this.targetContact,
|
|
|
|
this.targetContact,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
@ -93,6 +93,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|
|
|
ValueKey<String> _mapKey = const ValueKey('initial');
|
|
|
|
ValueKey<String> _mapKey = const ValueKey('initial');
|
|
|
|
double _pathDistanceMeters = 0.0;
|
|
|
|
double _pathDistanceMeters = 0.0;
|
|
|
|
bool _showNodeLabels = true;
|
|
|
|
bool _showNodeLabels = true;
|
|
|
|
|
|
|
|
Contact? target;
|
|
|
|
|
|
|
|
|
|
|
|
String _formatPathPrefixes(Uint8List pathBytes) {
|
|
|
|
String _formatPathPrefixes(Uint8List pathBytes) {
|
|
|
|
return pathBytes
|
|
|
|
return pathBytes
|
|
|
|
@ -158,21 +159,16 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
final Uint8List path;
|
|
|
|
final pathTmp = widget.reversePathAround
|
|
|
|
|
|
|
|
|
|
|
|
Uint8List pathTmp = widget.reversePathRound
|
|
|
|
|
|
|
|
? Uint8List.fromList(widget.path.reversed.toList())
|
|
|
|
? Uint8List.fromList(widget.path.reversed.toList())
|
|
|
|
: widget.path;
|
|
|
|
: widget.path;
|
|
|
|
|
|
|
|
|
|
|
|
if (widget.flipPathRound) {
|
|
|
|
final path = widget.flipPathAround ? buildPath(pathTmp) : pathTmp;
|
|
|
|
path = buildPath(pathTmp);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
path = pathTmp;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
appLogger.info(
|
|
|
|
appLogger.info(
|
|
|
|
'Initiating path trace with path: ${_formatPathPrefixes(path)}',
|
|
|
|
'Initiating path trace with path: ${_formatPathPrefixes(path)}',
|
|
|
|
tag: 'PathTraceMapScreen',
|
|
|
|
tag: 'PathTraceMapScreen',
|
|
|
|
|
|
|
|
noNotify: !mounted,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
|
|
|
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
|
|
|
@ -309,18 +305,20 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|
|
|
// Compute endpoint position for the target contact.
|
|
|
|
// Compute endpoint position for the target contact.
|
|
|
|
LatLng? targetPos;
|
|
|
|
LatLng? targetPos;
|
|
|
|
bool targetGuessed = false;
|
|
|
|
bool targetGuessed = false;
|
|
|
|
final target = widget.targetContact;
|
|
|
|
target = widget.targetContact;
|
|
|
|
|
|
|
|
|
|
|
|
if (target != null) {
|
|
|
|
if (target != null) {
|
|
|
|
if (target.hasLocation) {
|
|
|
|
if (target?.hasLocation ?? false) {
|
|
|
|
targetPos = LatLng(target.latitude!, target.longitude!);
|
|
|
|
targetPos = LatLng(target!.latitude!, target!.longitude!);
|
|
|
|
} else if (pathData.isNotEmpty) {
|
|
|
|
} else if (widget.path.length > 1) {
|
|
|
|
// Infer from the last hop: average GPS contacts sharing that hop.
|
|
|
|
// Infer from the last hop: average GPS contacts sharing that hop.
|
|
|
|
// For a round-trip path (flipPathRound), the target-side hop sits
|
|
|
|
// For a round-trip path (flipPathRound), the target-side hop sits
|
|
|
|
// in the middle of the symmetric sequence; .last is the local side.
|
|
|
|
// in the middle of the symmetric sequence; .last is the local side.
|
|
|
|
final lastHop = (widget.flipPathRound && pathData.length > 1)
|
|
|
|
final lastHop = widget.reversePathAround
|
|
|
|
? pathData[(pathData.length - 1) ~/ 2]
|
|
|
|
? widget.path.first
|
|
|
|
: pathData.last;
|
|
|
|
: widget.path.last;
|
|
|
|
final peers = connector.contacts
|
|
|
|
|
|
|
|
|
|
|
|
final peers = connector.allContacts
|
|
|
|
.where(
|
|
|
|
.where(
|
|
|
|
(c) =>
|
|
|
|
(c) =>
|
|
|
|
c.hasLocation &&
|
|
|
|
c.hasLocation &&
|
|
|
|
@ -336,12 +334,35 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|
|
|
peers.map((c) => c.longitude!).reduce((a, b) => a + b) /
|
|
|
|
peers.map((c) => c.longitude!).reduce((a, b) => a + b) /
|
|
|
|
peers.length;
|
|
|
|
peers.length;
|
|
|
|
const offsetDeg = 0.003;
|
|
|
|
const offsetDeg = 0.003;
|
|
|
|
final angle = (target.publicKey[1] / 255.0) * 2 * pi;
|
|
|
|
final angle = (target!.publicKey[1] / 255.0) * 2 * pi;
|
|
|
|
targetPos = LatLng(
|
|
|
|
targetPos = LatLng(
|
|
|
|
lat + offsetDeg * cos(angle),
|
|
|
|
lat + offsetDeg * cos(angle),
|
|
|
|
lon + offsetDeg * sin(angle),
|
|
|
|
lon + offsetDeg * sin(angle),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
targetGuessed = true;
|
|
|
|
targetGuessed = true;
|
|
|
|
|
|
|
|
} else if (inferredPositions.containsKey(lastHop)) {
|
|
|
|
|
|
|
|
final lat = inferredPositions[lastHop]!.latitude;
|
|
|
|
|
|
|
|
final lon = inferredPositions[lastHop]!.longitude;
|
|
|
|
|
|
|
|
const offsetDeg = 0.003;
|
|
|
|
|
|
|
|
final angle = (target!.publicKey[1] / 255.0) * 2 * pi;
|
|
|
|
|
|
|
|
targetPos = LatLng(
|
|
|
|
|
|
|
|
lat + offsetDeg * cos(angle),
|
|
|
|
|
|
|
|
lon + offsetDeg * sin(angle),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
targetGuessed = true;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// As a last resort, just place it at the same position as the last hop.
|
|
|
|
|
|
|
|
final contact = pathContacts[lastHop];
|
|
|
|
|
|
|
|
if (contact != null && contact.hasLocation) {
|
|
|
|
|
|
|
|
const offsetDeg = 0.003;
|
|
|
|
|
|
|
|
final angle = (target!.publicKey[1] / 255.0) * 2 * pi;
|
|
|
|
|
|
|
|
targetPos = LatLng(
|
|
|
|
|
|
|
|
contact.latitude! + offsetDeg * cos(angle),
|
|
|
|
|
|
|
|
contact.longitude! + offsetDeg * sin(angle),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
targetGuessed = true;
|
|
|
|
|
|
|
|
targetGuessed = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -350,7 +371,12 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|
|
|
|
|
|
|
|
|
|
|
_points = <LatLng>[];
|
|
|
|
_points = <LatLng>[];
|
|
|
|
_points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!));
|
|
|
|
_points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!));
|
|
|
|
|
|
|
|
int hopLast = 0;
|
|
|
|
|
|
|
|
int hopLastLast = 0;
|
|
|
|
for (final hop in _traceData!.pathData) {
|
|
|
|
for (final hop in _traceData!.pathData) {
|
|
|
|
|
|
|
|
if (hop == hopLastLast && widget.flipPathAround) {
|
|
|
|
|
|
|
|
break; //skip duplicate hops in round-trip paths
|
|
|
|
|
|
|
|
}
|
|
|
|
final contact = _traceData!.pathContacts[hop];
|
|
|
|
final contact = _traceData!.pathContacts[hop];
|
|
|
|
if (contact != null && contact.hasLocation) {
|
|
|
|
if (contact != null && contact.hasLocation) {
|
|
|
|
_points.add(LatLng(contact.latitude!, contact.longitude!));
|
|
|
|
_points.add(LatLng(contact.latitude!, contact.longitude!));
|
|
|
|
@ -358,8 +384,14 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|
|
|
final inferred = inferredPositions[hop];
|
|
|
|
final inferred = inferredPositions[hop];
|
|
|
|
if (inferred != null) _points.add(inferred);
|
|
|
|
if (inferred != null) _points.add(inferred);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hopLastLast = hopLast;
|
|
|
|
|
|
|
|
hopLast = hop;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (targetPos != null) {
|
|
|
|
|
|
|
|
if (target != null && target!.type == advTypeChat) {
|
|
|
|
|
|
|
|
_points.add(targetPos);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (targetPos != null) _points.add(targetPos);
|
|
|
|
|
|
|
|
_polylines = _points.length > 1
|
|
|
|
_polylines = _points.length > 1
|
|
|
|
? [
|
|
|
|
? [
|
|
|
|
Polyline(
|
|
|
|
Polyline(
|
|
|
|
@ -448,7 +480,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|
|
|
],
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
if (_hasData) _buildMapPathTrace(context, tileCache),
|
|
|
|
if (_hasData) _buildMapPathTrace(context, tileCache, target),
|
|
|
|
if (_points.isEmpty &&
|
|
|
|
if (_points.isEmpty &&
|
|
|
|
!_hasData &&
|
|
|
|
!_hasData &&
|
|
|
|
!_isLoading &&
|
|
|
|
!_isLoading &&
|
|
|
|
@ -477,17 +509,28 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|
|
|
List<Marker> _buildHopMarkers(
|
|
|
|
List<Marker> _buildHopMarkers(
|
|
|
|
List<int> pathData, {
|
|
|
|
List<int> pathData, {
|
|
|
|
required bool showLabels,
|
|
|
|
required bool showLabels,
|
|
|
|
|
|
|
|
required Contact? target,
|
|
|
|
}) {
|
|
|
|
}) {
|
|
|
|
final markers = <Marker>[];
|
|
|
|
final markers = <Marker>[];
|
|
|
|
|
|
|
|
int hopLast = 0;
|
|
|
|
|
|
|
|
int hopLastLast = 0;
|
|
|
|
for (final hop in pathData) {
|
|
|
|
for (final hop in pathData) {
|
|
|
|
final contact = _traceData!.pathContacts[hop];
|
|
|
|
final contact = _traceData!.pathContacts[hop];
|
|
|
|
final inferred = _inferredHopPositions[hop];
|
|
|
|
final inferred = _inferredHopPositions[hop];
|
|
|
|
final hasGps = contact != null && contact.hasLocation;
|
|
|
|
final hasGps = contact != null && contact.hasLocation;
|
|
|
|
if (!hasGps && inferred == null) continue;
|
|
|
|
if (hop == hopLastLast && widget.flipPathAround) {
|
|
|
|
|
|
|
|
continue; //skip duplicate hops in round-trip paths
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hasGps && inferred == null) {
|
|
|
|
|
|
|
|
hopLastLast = hopLast;
|
|
|
|
|
|
|
|
hopLast = hop;
|
|
|
|
|
|
|
|
continue; //skip hops with no GPS and no inferred position
|
|
|
|
|
|
|
|
}
|
|
|
|
final point = hasGps
|
|
|
|
final point = hasGps
|
|
|
|
? LatLng(contact.latitude!, contact.longitude!)
|
|
|
|
? LatLng(contact.latitude!, contact.longitude!)
|
|
|
|
: inferred!;
|
|
|
|
: inferred!;
|
|
|
|
final label = hop.toRadixString(16).padLeft(2, '0').toUpperCase();
|
|
|
|
final label = hop.toRadixString(16).padLeft(2, '0').toUpperCase();
|
|
|
|
|
|
|
|
|
|
|
|
markers.add(
|
|
|
|
markers.add(
|
|
|
|
Marker(
|
|
|
|
Marker(
|
|
|
|
point: point,
|
|
|
|
point: point,
|
|
|
|
@ -529,6 +572,8 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hopLastLast = hopLast;
|
|
|
|
|
|
|
|
hopLast = hop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
final selfLat = context.read<MeshCoreConnector>().selfLatitude;
|
|
|
|
final selfLat = context.read<MeshCoreConnector>().selfLatitude;
|
|
|
|
@ -578,9 +623,9 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|
|
|
|
|
|
|
|
|
|
|
// Add target contact endpoint marker.
|
|
|
|
// Add target contact endpoint marker.
|
|
|
|
final targetPos = _targetContactPosition;
|
|
|
|
final targetPos = _targetContactPosition;
|
|
|
|
if (targetPos != null) {
|
|
|
|
if (targetPos != null && target != null && target.type == advTypeChat) {
|
|
|
|
final isGuessed = _targetContactIsGuessed;
|
|
|
|
final isGuessed = _targetContactIsGuessed;
|
|
|
|
final targetName = widget.targetContact?.name ?? '?';
|
|
|
|
final targetName = target.name;
|
|
|
|
markers.add(
|
|
|
|
markers.add(
|
|
|
|
Marker(
|
|
|
|
Marker(
|
|
|
|
point: targetPos,
|
|
|
|
point: targetPos,
|
|
|
|
@ -716,6 +761,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|
|
|
Widget _buildMapPathTrace(
|
|
|
|
Widget _buildMapPathTrace(
|
|
|
|
BuildContext context,
|
|
|
|
BuildContext context,
|
|
|
|
MapTileCacheService tileCache,
|
|
|
|
MapTileCacheService tileCache,
|
|
|
|
|
|
|
|
Contact? target,
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
return FlutterMap(
|
|
|
|
return FlutterMap(
|
|
|
|
key: _mapKey,
|
|
|
|
key: _mapKey,
|
|
|
|
@ -754,6 +800,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|
|
|
markers: _buildHopMarkers(
|
|
|
|
markers: _buildHopMarkers(
|
|
|
|
_traceData!.pathData,
|
|
|
|
_traceData!.pathData,
|
|
|
|
showLabels: _showNodeLabels,
|
|
|
|
showLabels: _showNodeLabels,
|
|
|
|
|
|
|
|
target: target,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
],
|
|
|
|
|