321 lines
12 KiB
Dart
321 lines
12 KiB
Dart
// lib/widgets/coordinate_panel.dart
|
|
|
|
import 'dart:math';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:terepi_seged/services/ntrip_service.dart';
|
|
|
|
import '../services/gnss/gnss_service.dart';
|
|
|
|
class CoordinatePanel extends StatelessWidget {
|
|
final RxDouble eovY;
|
|
final RxDouble eovX;
|
|
final RxDouble horError;
|
|
final RxDouble vertError;
|
|
final RxDouble? altitudeMsl;
|
|
final RxDouble? geoidSeparation;
|
|
final RxBool? ntripConnected;
|
|
final RxInt? ntripBytes;
|
|
final RxInt? ntripPackets;
|
|
final RxInt? ggaPackets;
|
|
|
|
const CoordinatePanel({
|
|
super.key,
|
|
required this.eovY,
|
|
required this.eovX,
|
|
required this.horError,
|
|
required this.vertError,
|
|
this.altitudeMsl,
|
|
this.geoidSeparation,
|
|
this.ntripConnected,
|
|
this.ntripBytes,
|
|
this.ntripPackets,
|
|
this.ggaPackets,
|
|
});
|
|
|
|
/// Gyors factory — MapSurveyController mezőiből.
|
|
factory CoordinatePanel.fromController(dynamic ctrl) {
|
|
final y = RxDouble(0.0);
|
|
final x = RxDouble(0.0);
|
|
// Figyeli az eov változást és frissíti Y/X-et
|
|
ctrl.eov.listen((eov) {
|
|
y.value = (eov.Y as num).toDouble();
|
|
x.value = (eov.X as num).toDouble();
|
|
});
|
|
return CoordinatePanel(
|
|
eovY: y,
|
|
eovX: x,
|
|
horError: ctrl.gpsLatitudeError as RxDouble,
|
|
vertError: ctrl.gpsAltitudeError as RxDouble,
|
|
altitudeMsl: ctrl.gpsAltitude as RxDouble,
|
|
geoidSeparation: ctrl.gpsGeoidSeparation as RxDouble,
|
|
ntripConnected: NtripService.to.isConnected,
|
|
ntripBytes: NtripService.to.receivedBytes,
|
|
ntripPackets: NtripService.to.packetCount,
|
|
ggaPackets: NtripService.to.ggaSentCount,
|
|
);
|
|
}
|
|
|
|
static final _fmtEov = NumberFormat('###,###,##0.000', 'hu-HU');
|
|
static final _fmtEovZ = NumberFormat('###,##0.000', 'hu-HU');
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final gnss = GnssService.to;
|
|
|
|
return Obx(() {
|
|
final quality = gnss.gpsQuality.value;
|
|
final hasData = quality > 0;
|
|
final color = _qualityColor(quality);
|
|
|
|
return AnimatedContainer(
|
|
duration: const Duration(milliseconds: 300),
|
|
decoration: BoxDecoration(
|
|
color: Colors.black.withOpacity(0.78),
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(color: color.withOpacity(0.6), width: 1.5),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// ── Fejléc ──────────────────────────────────────────
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(10, 8, 10, 6),
|
|
child: Row(children: [
|
|
// Fix chip
|
|
Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(5),
|
|
border: Border.all(color: color.withOpacity(0.5)),
|
|
),
|
|
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
|
Container(
|
|
width: 7,
|
|
height: 7,
|
|
decoration:
|
|
BoxDecoration(color: color, shape: BoxShape.circle),
|
|
),
|
|
const SizedBox(width: 5),
|
|
Text(_qualityLabel(quality),
|
|
style: TextStyle(
|
|
color: color,
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w700,
|
|
)),
|
|
]),
|
|
),
|
|
const Spacer(),
|
|
// Műholdak
|
|
if (hasData) ...[
|
|
Icon(Icons.satellite_alt,
|
|
size: 11, color: Colors.white.withOpacity(0.5)),
|
|
const SizedBox(width: 3),
|
|
Text('${gnss.satelliteCount.value}',
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.55), fontSize: 11)),
|
|
const SizedBox(width: 10),
|
|
],
|
|
// UTC
|
|
Icon(Icons.access_time,
|
|
size: 11, color: Colors.white.withOpacity(0.4)),
|
|
const SizedBox(width: 3),
|
|
Text(
|
|
gnss.utcFix.value.isNotEmpty
|
|
? '${gnss.utcFix.value} UTC'
|
|
: '--:--:-- UTC',
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.45), fontSize: 11)),
|
|
]),
|
|
),
|
|
|
|
if (hasData) ...[
|
|
_Div(),
|
|
|
|
// ── EOV koordináták ──────────────────────────────
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
|
child: Obx(() {
|
|
final alt = altitudeMsl?.value ?? gnss.altitude.value;
|
|
final geoSep =
|
|
geoidSeparation?.value ?? gnss.geoidSeparation.value;
|
|
return Column(children: [
|
|
_Row('EOV Y', _fmtEov.format(eovY.value), 'm', bold: true),
|
|
const SizedBox(height: 3),
|
|
_Row('EOV X', _fmtEov.format(eovX.value), 'm', bold: true),
|
|
const SizedBox(height: 6),
|
|
_Row('H (MSL)', _fmtEovZ.format(alt), 'm'),
|
|
const SizedBox(height: 2),
|
|
_Row('h (WGS84)', _fmtEovZ.format(alt + geoSep), 'm',
|
|
dimmed: true),
|
|
]);
|
|
}),
|
|
),
|
|
|
|
_Div(),
|
|
|
|
// ── Pontosság ────────────────────────────────────
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(10, 4, 10, 6),
|
|
child: Obx(() {
|
|
final hor = max(horError.value, 0.0);
|
|
final vert = vertError.value;
|
|
final geo =
|
|
geoidSeparation?.value ?? gnss.geoidSeparation.value;
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
_Chip(Icons.open_in_full, 'Vízszintes',
|
|
'±${hor.toStringAsFixed(3)} m', _errorColor(hor)),
|
|
_Chip(Icons.height, 'Függőleges',
|
|
'±${vert.toStringAsFixed(3)} m', _errorColor(vert)),
|
|
_Chip(Icons.water, 'Geoid N',
|
|
'${geo.toStringAsFixed(2)} m', Colors.white54),
|
|
],
|
|
);
|
|
}),
|
|
),
|
|
|
|
// ── NTRIP ────────────────────────────────────────
|
|
if (ntripConnected != null)
|
|
Obx(
|
|
() => ntripConnected!.value
|
|
? Column(children: [
|
|
_Div(),
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(10, 4, 10, 6),
|
|
child: Row(children: [
|
|
Container(
|
|
width: 7,
|
|
height: 7,
|
|
decoration: const BoxDecoration(
|
|
color: Colors.greenAccent,
|
|
shape: BoxShape.circle),
|
|
),
|
|
const SizedBox(width: 6),
|
|
const Text('NTRIP',
|
|
style: TextStyle(
|
|
color: Colors.greenAccent,
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w600,
|
|
)),
|
|
const Spacer(),
|
|
Text(
|
|
'${ntripBytes?.value ?? 0} byte'
|
|
' · ${ntripPackets?.value ?? 0} cs.',
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.5),
|
|
fontSize: 10,
|
|
)),
|
|
const SizedBox(width: 8),
|
|
Text('GGA: ${ggaPackets?.value ?? 0}',
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.4),
|
|
fontSize: 10,
|
|
)),
|
|
]),
|
|
),
|
|
])
|
|
: const SizedBox.shrink(),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
// ── Mini widgetek ─────────────────────────────────────────────────────
|
|
|
|
class _Div extends StatelessWidget {
|
|
@override
|
|
Widget build(BuildContext context) =>
|
|
Container(height: 1, color: Colors.white.withOpacity(0.08));
|
|
}
|
|
|
|
class _Row extends StatelessWidget {
|
|
final String label, value, unit;
|
|
final bool bold, dimmed;
|
|
const _Row(this.label, this.value, this.unit,
|
|
{this.bold = false, this.dimmed = false});
|
|
|
|
@override
|
|
Widget build(BuildContext context) => Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(label,
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.5), fontSize: 11)),
|
|
Row(children: [
|
|
Text(value,
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(dimmed ? 0.45 : 1.0),
|
|
fontSize: bold ? 15 : 13,
|
|
fontWeight: bold ? FontWeight.w600 : FontWeight.w400,
|
|
fontFeatures: const [FontFeature.tabularFigures()],
|
|
)),
|
|
const SizedBox(width: 3),
|
|
Text(unit,
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.35), fontSize: 10)),
|
|
]),
|
|
],
|
|
);
|
|
}
|
|
|
|
class _Chip extends StatelessWidget {
|
|
final IconData icon;
|
|
final String label, value;
|
|
final Color color;
|
|
const _Chip(this.icon, this.label, this.value, this.color);
|
|
|
|
@override
|
|
Widget build(BuildContext context) => Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(icon, size: 12, color: Colors.white.withOpacity(0.4)),
|
|
const SizedBox(height: 2),
|
|
Text(value,
|
|
style: TextStyle(
|
|
color: color,
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
fontFeatures: const [FontFeature.tabularFigures()],
|
|
)),
|
|
Text(label,
|
|
style:
|
|
TextStyle(color: Colors.white.withOpacity(0.4), fontSize: 9)),
|
|
],
|
|
);
|
|
}
|
|
|
|
// ── Segédfüggvények ───────────────────────────────────────────────────
|
|
|
|
Color _qualityColor(int q) => switch (q) {
|
|
4 => Colors.greenAccent,
|
|
5 => Colors.lightGreen,
|
|
2 => Colors.blue,
|
|
1 => Colors.orange,
|
|
_ => Colors.red,
|
|
};
|
|
|
|
String _qualityLabel(int q) => switch (q) {
|
|
4 => 'RTK Fix',
|
|
5 => 'RTK Float',
|
|
2 => 'DGPS',
|
|
1 => 'GPS',
|
|
_ => 'Nincs fix',
|
|
};
|
|
|
|
Color _errorColor(double e) {
|
|
if (e < 0.05) return Colors.greenAccent;
|
|
if (e < 0.2) return Colors.green;
|
|
if (e < 1.0) return Colors.orange;
|
|
return Colors.red;
|
|
}
|