diff --git a/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart b/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart index fd90c9a..6978291 100644 --- a/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart +++ b/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; +import 'dart:math' as math; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; @@ -65,10 +66,16 @@ class MapSurveyController extends GetxController { Rx get gpsDateTime => _gnss.gpsDateTime; bool get gpsIsConnected => _gnss.connectionState.value == GnssConnectionState.connected; + double? get horizontalAccuracy => _gnss.horizontalAccuracy; + + RxDouble get pdop => _gnss.pdop; + RxDouble get hdop => _gnss.hdop; + RxDouble get vdop => _gnss.vdop; final wgs84CoordinateFormat = Wgs84CoordinateFormat.decimalDegrees.obs; final showWgs84Card = true.obs; final showEovCard = true.obs; + final showGnssQualityCard = true.obs; // NTRIP állapot RxBool get ntripIsConnected => _ntrip.isConnected; @@ -799,6 +806,10 @@ class MapSurveyController extends GetxController { showEovCard.value = !showEovCard.value; } + void toggleShowGnssQualityCard() { + showGnssQualityCard.value = !showGnssQualityCard.value; + } + String get latitudeText { return Wgs84CoordinateFormatter.formatLatitude( gpsLatitude.value, wgs84CoordinateFormat.value); @@ -808,4 +819,28 @@ class MapSurveyController extends GetxController { return Wgs84CoordinateFormatter.formatLongitude( gpsLongitude.value, wgs84CoordinateFormat.value); } + + String formatDop(double? value) { + if (value == null || value.isNaN || value.isInfinite) { + return '-'; + } + + return value.toStringAsFixed(2); + } + + String formatMeterCompact(double? value) { + if (value == null || value.isNaN || value.isInfinite) { + return '-'; + } + + if (value < 1.0) { + return '${value.toStringAsFixed(3)}m'; + } + + if (value < 10.0) { + return '${value.toStringAsFixed(2)}m'; + } + + return '${value.toStringAsFixed(1)}m'; + } } diff --git a/lib/pages/map_survey/presentations/views/map_survey_view.dart b/lib/pages/map_survey/presentations/views/map_survey_view.dart index 88b482d..2155a12 100644 --- a/lib/pages/map_survey/presentations/views/map_survey_view.dart +++ b/lib/pages/map_survey/presentations/views/map_survey_view.dart @@ -39,7 +39,7 @@ class MapSurveyView extends GetView { ), Positioned( - top: 300, + top: 390, right: 60, left: 8, child: CoordinatePanel( diff --git a/lib/services/gnss/gnss_service.dart b/lib/services/gnss/gnss_service.dart index 3d9bc44..8c1ab1c 100644 --- a/lib/services/gnss/gnss_service.dart +++ b/lib/services/gnss/gnss_service.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math'; import 'dart:typed_data'; import 'package:geolocator/geolocator.dart'; @@ -93,6 +94,13 @@ class GnssService extends GetxService { return activeSatelliteKeys.contains(sat.satelliteKey); } + double? get horizontalAccuracy { + final lat = latitudeError.value; + final lon = longitudeError.value; + + return sqrt(lat * lat + lon * lon); + } + // ── Belső ───────────────────────────────────────────────────────── final NmeaDecoder _decoder = NmeaDecoder(); StreamSubscription? _nmeaSub; diff --git a/lib/widgets/gnss_quality_card.dart b/lib/widgets/gnss_quality_card.dart new file mode 100644 index 0000000..9d8e765 --- /dev/null +++ b/lib/widgets/gnss_quality_card.dart @@ -0,0 +1,204 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart'; + +import 'map_info_card.dart'; +import 'quality_mini_value.dart'; + +class GnssQualityCard extends StatelessWidget { + final MapSurveyController controller; + + const GnssQualityCard({ + super.key, + required this.controller, + }); + + @override + Widget build(BuildContext context) { + return Obx(() { + final hError = controller.horizontalAccuracy; + final vError = controller.gpsAltitudeError.value; + + return MapInfoCard( + onClose: controller.toggleShowGnssQualityCard, + title: Row( + children: [ + const Icon(Icons.speed, size: 18), + const SizedBox(width: 6), + Text( + 'Minőség', + style: Theme.of(context).textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: QualityMiniValue( + label: 'PDOP', + value: controller.formatDop(controller.pdop.value), + color: qualityColorForDop( + context, + controller.pdop.value, + ), + ), + ), + Expanded( + child: QualityMiniValue( + label: 'HDOP', + value: controller.formatDop(controller.hdop.value), + color: qualityColorForDop( + context, + controller.hdop.value, + ), + ), + ), + Expanded( + child: QualityMiniValue( + label: 'VDOP', + value: controller.formatDop(controller.vdop.value), + color: qualityColorForDop( + context, + controller.vdop.value, + ), + ), + ), + ], + ), + const SizedBox(height: 3), + Row( + children: [ + Expanded( + child: QualityMiniValue( + label: 'Latσ', + value: controller.formatMeterCompact( + controller.gpsLatitudeError.value, + ), + color: qualityColorForHorizontalError( + context, + controller.gpsLatitudeError.value, + ), + ), + ), + Expanded( + child: QualityMiniValue( + label: 'Lonσ', + value: controller.formatMeterCompact( + controller.gpsLongitudeError.value, + ), + color: qualityColorForHorizontalError( + context, + controller.gpsLongitudeError.value, + ), + ), + ), + Expanded( + child: QualityMiniValue( + label: 'H', + value: controller.formatMeterCompact(hError), + color: qualityColorForHorizontalError( + context, + hError, + ), + emphasized: true, + ), + ), + Expanded( + child: QualityMiniValue( + label: 'V', + value: controller.formatMeterCompact(vError), + color: qualityColorForVerticalError( + context, + vError, + ), + emphasized: true, + ), + ), + ], + ), + ], + ), + ); + }); + } + + Color qualityColorForDop( + BuildContext context, + double? value, + ) { + final colorScheme = Theme.of(context).colorScheme; + + if (value == null || value.isNaN || value.isInfinite) { + return colorScheme.onSurface.withOpacity(0.45); + } + + if (value <= 1.0) { + return Colors.green.shade700; + } + + if (value <= 2.0) { + return Colors.lightGreen.shade700; + } + + if (value <= 5.0) { + return Colors.orange.shade800; + } + + return Colors.red.shade700; + } + + Color qualityColorForHorizontalError( + BuildContext context, + double? value, + ) { + final colorScheme = Theme.of(context).colorScheme; + + if (value == null || value.isNaN || value.isInfinite) { + return colorScheme.onSurface.withOpacity(0.45); + } + + if (value <= 0.03) { + return Colors.green.shade700; + } + + if (value <= 0.10) { + return Colors.lightGreen.shade700; + } + + if (value <= 0.50) { + return Colors.orange.shade800; + } + + return Colors.red.shade700; + } + + Color qualityColorForVerticalError( + BuildContext context, + double? value, + ) { + final colorScheme = Theme.of(context).colorScheme; + + if (value == null || value.isNaN || value.isInfinite) { + return colorScheme.onSurface.withOpacity(0.45); + } + + if (value <= 0.05) { + return Colors.green.shade700; + } + + if (value <= 0.15) { + return Colors.lightGreen.shade700; + } + + if (value <= 0.75) { + return Colors.orange.shade800; + } + + return Colors.red.shade700; + } +} diff --git a/lib/widgets/map_info_card.dart b/lib/widgets/map_info_card.dart index bd44b7b..385090f 100644 --- a/lib/widgets/map_info_card.dart +++ b/lib/widgets/map_info_card.dart @@ -45,7 +45,7 @@ class MapInfoCard extends StatelessWidget { ), ], ), - const SizedBox(height: 6), + const SizedBox(height: 1), child, ], ), diff --git a/lib/widgets/map_info_card_column.dart b/lib/widgets/map_info_card_column.dart index b1e5bde..6d9aa92 100644 --- a/lib/widgets/map_info_card_column.dart +++ b/lib/widgets/map_info_card_column.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart'; +import 'package:terepi_seged/widgets/gnss_quality_card.dart'; import 'package:terepi_seged/widgets/wgs84_coordinate_card.dart'; import 'eov_coordinate_card.dart'; @@ -27,6 +28,9 @@ class MapInfoCardColumn extends StatelessWidget { if (controller.showEovCard.value) { cards.add(EovCoordinateCard(controller: controller)); } + if (controller.showGnssQualityCard.value) { + cards.add(GnssQualityCard(controller: controller)); + } if (cards.isEmpty) { return const SizedBox.shrink(); @@ -41,7 +45,7 @@ class MapInfoCardColumn extends StatelessWidget { children: [ for (var i = 0; i < cards.length; i++) ...[ cards[i], - if (i != cards.length - 1) const SizedBox(height: 8) + if (i != cards.length - 1) const SizedBox(height: 4) ] ], ), diff --git a/lib/widgets/quality_mini_value.dart b/lib/widgets/quality_mini_value.dart new file mode 100644 index 0000000..173f71e --- /dev/null +++ b/lib/widgets/quality_mini_value.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; + +class QualityMiniValue extends StatelessWidget { + final String label; + final String value; + final Color color; + final bool emphasized; + + const QualityMiniValue({ + required this.label, + required this.value, + required this.color, + this.emphasized = false, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + label, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: colorScheme.onSurface.withOpacity(0.65), + fontWeight: FontWeight.w600, + height: 1.0, + ), + ), + const SizedBox(height: 2), + Text( + value, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: color, + fontWeight: emphasized ? FontWeight.w800 : FontWeight.w700, + height: 1.05, + fontFeatures: const [ + FontFeature.tabularFigures(), + ], + ), + ), + ], + ); + } +} diff --git a/lib/widgets/wgs84_coordinate_card.dart b/lib/widgets/wgs84_coordinate_card.dart index 2d810eb..1f9daac 100644 --- a/lib/widgets/wgs84_coordinate_card.dart +++ b/lib/widgets/wgs84_coordinate_card.dart @@ -39,7 +39,7 @@ class Wgs84CoordinateCard extends StatelessWidget { label: 'Lat', value: controller.latitudeText, ), - const SizedBox(height: 4), + const SizedBox(height: 1), CoordinateRow( label: 'Lon', value: controller.longitudeText,