224 lines
5.5 KiB
Dart
224 lines
5.5 KiB
Dart
// lib/model/activity/activity_track.dart
|
|
import 'package:latlong2/latlong.dart';
|
|
|
|
class ActivityTrack {
|
|
final String name;
|
|
final String format;
|
|
final Set<List<LatLng>> tracks;
|
|
|
|
/// Nome percorso/attività letto dal file, se disponibile.
|
|
/// Se non disponibile, la UI può usare [name].
|
|
final String? routeName;
|
|
|
|
/// Numero punti validi letti dal file prima di eventuale semplificazione grafica.
|
|
final int pointCount;
|
|
|
|
/// Valori usati normalmente dalla UI.
|
|
final double? distanceMeters;
|
|
final double? elevationGainMeters;
|
|
|
|
/// Valori aggregati scritti nel file, ad esempio FIT totalDistance/totalAscent.
|
|
final double? fileDistanceMeters;
|
|
final double? fileElevationGainMeters;
|
|
|
|
/// Valori ricalcolati dai punti del file, quando disponibili.
|
|
/// Per l'elevazione serve che il parser mantenga/calcoli le quote: con soli
|
|
/// LatLng non è possibile ricalcolare il D+ in questa pagina.
|
|
final double? computedDistanceMeters;
|
|
final double? computedElevationGainMeters;
|
|
|
|
final Duration? duration;
|
|
final DateTime? startTime;
|
|
final String? sport;
|
|
final String? diagnostics;
|
|
|
|
const ActivityTrack({
|
|
required this.name,
|
|
required this.format,
|
|
required this.tracks,
|
|
required this.pointCount,
|
|
this.routeName,
|
|
this.distanceMeters,
|
|
this.elevationGainMeters,
|
|
this.fileDistanceMeters,
|
|
this.fileElevationGainMeters,
|
|
this.computedDistanceMeters,
|
|
this.computedElevationGainMeters,
|
|
this.duration,
|
|
this.startTime,
|
|
this.sport,
|
|
this.diagnostics,
|
|
});
|
|
|
|
bool get isEmpty => tracks.isEmpty || tracks.every((segment) => segment.isEmpty);
|
|
|
|
String get displayName {
|
|
final value = routeName?.trim();
|
|
if (value != null && value.isNotEmpty) return value;
|
|
return name;
|
|
}
|
|
|
|
Iterable<LatLng> get points sync* {
|
|
for (final segment in tracks) {
|
|
yield* segment;
|
|
}
|
|
}
|
|
|
|
List<LatLng> get validPoints {
|
|
return points
|
|
.where(
|
|
(point) =>
|
|
point.latitude.isFinite &&
|
|
point.longitude.isFinite &&
|
|
point.latitude.abs() <= 90 &&
|
|
point.longitude.abs() <= 180,
|
|
)
|
|
.toList(growable: false);
|
|
}
|
|
|
|
int get displayPointCount => validPoints.length;
|
|
|
|
LatLng? get center {
|
|
final allPoints = validPoints;
|
|
if (allPoints.isEmpty) return null;
|
|
|
|
final latitude = allPoints.fold<double>(
|
|
0,
|
|
(sum, point) => sum + point.latitude,
|
|
) /
|
|
allPoints.length;
|
|
|
|
final longitude = allPoints.fold<double>(
|
|
0,
|
|
(sum, point) => sum + point.longitude,
|
|
) /
|
|
allPoints.length;
|
|
|
|
return LatLng(latitude, longitude);
|
|
}
|
|
|
|
double? get minLatitude {
|
|
final allPoints = validPoints;
|
|
if (allPoints.isEmpty) return null;
|
|
|
|
return allPoints
|
|
.map((point) => point.latitude)
|
|
.reduce((a, b) => a < b ? a : b);
|
|
}
|
|
|
|
double? get maxLatitude {
|
|
final allPoints = validPoints;
|
|
if (allPoints.isEmpty) return null;
|
|
|
|
return allPoints
|
|
.map((point) => point.latitude)
|
|
.reduce((a, b) => a > b ? a : b);
|
|
}
|
|
|
|
double? get minLongitude {
|
|
final allPoints = validPoints;
|
|
if (allPoints.isEmpty) return null;
|
|
|
|
return allPoints
|
|
.map((point) => point.longitude)
|
|
.reduce((a, b) => a < b ? a : b);
|
|
}
|
|
|
|
double? get maxLongitude {
|
|
final allPoints = validPoints;
|
|
if (allPoints.isEmpty) return null;
|
|
|
|
return allPoints
|
|
.map((point) => point.longitude)
|
|
.reduce((a, b) => a > b ? a : b);
|
|
}
|
|
|
|
bool get hasBounds =>
|
|
minLatitude != null &&
|
|
maxLatitude != null &&
|
|
minLongitude != null &&
|
|
maxLongitude != null;
|
|
|
|
LatLng? get southWest {
|
|
final south = minLatitude;
|
|
final west = minLongitude;
|
|
|
|
if (south == null || west == null) return null;
|
|
return LatLng(south, west);
|
|
}
|
|
|
|
LatLng? get northEast {
|
|
final north = maxLatitude;
|
|
final east = maxLongitude;
|
|
|
|
if (north == null || east == null) return null;
|
|
return LatLng(north, east);
|
|
}
|
|
|
|
LatLng? get boundsCenter {
|
|
final south = minLatitude;
|
|
final north = maxLatitude;
|
|
final west = minLongitude;
|
|
final east = maxLongitude;
|
|
|
|
if (south == null || north == null || west == null || east == null) {
|
|
return null;
|
|
}
|
|
|
|
return LatLng(
|
|
(south + north) / 2,
|
|
(west + east) / 2,
|
|
);
|
|
}
|
|
|
|
String get distanceLabel {
|
|
final value = distanceMeters;
|
|
if (value == null) return '- km';
|
|
return '${(value / 1000).toStringAsFixed(2)} km';
|
|
}
|
|
|
|
String get elevationGainLabel {
|
|
final value = elevationGainMeters;
|
|
if (value == null) return 'D+ -';
|
|
return 'D+ ${value.round()} m';
|
|
}
|
|
|
|
String get dateLabel {
|
|
final value = startTime;
|
|
if (value == null) return 'Data -';
|
|
|
|
final local = value.toLocal();
|
|
final day = local.day.toString().padLeft(2, '0');
|
|
final month = local.month.toString().padLeft(2, '0');
|
|
final year = local.year.toString().padLeft(4, '0');
|
|
final hour = local.hour.toString().padLeft(2, '0');
|
|
final minute = local.minute.toString().padLeft(2, '0');
|
|
|
|
return '$day/$month/$year $hour:$minute';
|
|
}
|
|
|
|
String get durationLabel {
|
|
final value = duration;
|
|
if (value == null) return '-';
|
|
|
|
final hours = value.inHours;
|
|
final minutes = value.inMinutes.remainder(60);
|
|
final seconds = value.inSeconds.remainder(60);
|
|
|
|
if (hours > 0) {
|
|
return '${hours}h ${minutes}m';
|
|
}
|
|
|
|
if (minutes > 0) {
|
|
return '${minutes}m ${seconds}s';
|
|
}
|
|
|
|
return '${seconds}s';
|
|
}
|
|
|
|
String get sportLabel {
|
|
final value = sport;
|
|
if (value == null || value.trim().isEmpty) return 'Attività';
|
|
return value;
|
|
}
|
|
}
|