Jármű navigáció @3h
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:terepi_seged/models/source_point.dart';
|
||||
import 'package:terepi_seged/models/vechicle_position_log.dart';
|
||||
import 'package:terepi_seged/services/app_database.dart';
|
||||
import 'package:terepi_seged/services/coord_converter_service.dart';
|
||||
import 'package:terepi_seged/services/device_identity_service.dart';
|
||||
import 'package:terepi_seged/services/gnss/gnss_service.dart';
|
||||
import 'package:terepi_seged/services/project_service.dart';
|
||||
import 'package:terepi_seged/services/vechicle_identity_service.dart';
|
||||
|
||||
/// Egyszerű navigációs panel a jelgerjesztő (vibrátor) járműhöz.
|
||||
///
|
||||
/// A pozíció a KITŰZÉSSEL AZONOS forrásból jön (GnssService — külső
|
||||
/// BT/BLE GNSS-egység a járműben), semmilyen új eszköz-integráció nem
|
||||
/// kell hozzá. A forráspontok tisztán referencia-réteg (nincs mutálható
|
||||
/// "meglőve" állapotuk); az ellenőrzés a periodikus pozíciónapló és a
|
||||
/// terv UTÓLAGOS összevetéséből adódik (lásd isVerified).
|
||||
class VibroNavController extends GetxController {
|
||||
AppDatabase get _db => AppDatabase.instance;
|
||||
|
||||
// ── Forráspontok ─────────────────────────────────────────────────
|
||||
final sourcePoints = <SourcePoint>[].obs;
|
||||
|
||||
/// Egyezés-tűrés (m) a naplózott pozíció és a terv-forráspont között.
|
||||
final tolerance = 15.0.obs;
|
||||
|
||||
// ── Élő pozíció (a GnssService-ből, EOV-ra konvertálva) ───────────
|
||||
final hasPosition = false.obs;
|
||||
final curEovY = 0.0.obs;
|
||||
final curEovX = 0.0.obs;
|
||||
final speedKmh = 0.0.obs;
|
||||
final sessionDistanceM = 0.0.obs;
|
||||
|
||||
/// Haladási irány (fok) — pozíció-előzményből számolva, ALACSONY
|
||||
/// SEBESSÉGNÉL BEFAGYASZTVA (a nyers GPS-heading álló/lassú helyzetben
|
||||
/// zajos/értelmetlen — ugyanez a minta, mint a Kitűzésnél).
|
||||
final travelHeading = Rxn<double>();
|
||||
static const _headingFreezeSpeedKmh = 5.0;
|
||||
|
||||
double? _histY, _histX;
|
||||
DateTime? _histTime;
|
||||
|
||||
// ── Legközelebbi forráspont ───────────────────────────────────────
|
||||
final nearestPoint = Rxn<SourcePoint>();
|
||||
final nearestDistance = 0.0.obs;
|
||||
|
||||
// ── Periodikus napló ───────────────────────────────────────────────
|
||||
final isLogging = false.obs;
|
||||
final logIntervalSec = 60.obs; // felhasználó által állítható
|
||||
Timer? _logTimer;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
load();
|
||||
if (Get.isRegistered<GnssService>()) {
|
||||
ever(GnssService.to.lastGgaLine, (_) => _onPosition());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_logTimer?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
Future<void> load() async {
|
||||
final projectId = ProjectService.to.activeProjectId;
|
||||
if (projectId == null) {
|
||||
sourcePoints.clear();
|
||||
return;
|
||||
}
|
||||
sourcePoints.value = await _db.listSourcePoints(projectId);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// Pozíció-feldolgozás
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
|
||||
void _onPosition() {
|
||||
final gnss = GnssService.to;
|
||||
if (gnss.gpsQuality.value <= 0 ||
|
||||
gnss.latitude.value == 0 ||
|
||||
!Get.isRegistered<CoordConverterService>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final p = CoordConverterService.to
|
||||
.wgsToEovPoint(gnss.longitude.value, gnss.latitude.value);
|
||||
final now = DateTime.now();
|
||||
|
||||
if (_histY != null && _histTime != null) {
|
||||
final dy = p.x - _histY!;
|
||||
final dx = p.y - _histX!;
|
||||
final dist = math.sqrt(dy * dy + dx * dx);
|
||||
final dtSec = now.difference(_histTime!).inMilliseconds / 1000.0;
|
||||
|
||||
if (dtSec > 0) {
|
||||
final v = dist / dtSec; // m/s
|
||||
speedKmh.value = v * 3.6;
|
||||
sessionDistanceM.value += dist;
|
||||
|
||||
// Irány csak akkor frissül, ha a sebesség a fagyasztási küszöb
|
||||
// felett van — álló/nagyon lassú helyzetben a nyers irány zajos.
|
||||
if (speedKmh.value >= _headingFreezeSpeedKmh && dist > 0.3) {
|
||||
travelHeading.value =
|
||||
(math.atan2(dy, dx) * 180 / math.pi + 360) % 360;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_histY = p.x;
|
||||
_histX = p.y;
|
||||
_histTime = now;
|
||||
|
||||
curEovY.value = p.x;
|
||||
curEovX.value = p.y;
|
||||
hasPosition.value = true;
|
||||
|
||||
_updateNearest();
|
||||
}
|
||||
|
||||
void _updateNearest() {
|
||||
if (sourcePoints.isEmpty) {
|
||||
nearestPoint.value = null;
|
||||
return;
|
||||
}
|
||||
SourcePoint? best;
|
||||
var bestDist = double.infinity;
|
||||
for (final sp in sourcePoints) {
|
||||
final dy = sp.planEovY - curEovY.value;
|
||||
final dx = sp.planEovX - curEovX.value;
|
||||
final d = math.sqrt(dy * dy + dx * dx);
|
||||
if (d < bestDist) {
|
||||
bestDist = d;
|
||||
best = sp;
|
||||
}
|
||||
}
|
||||
nearestPoint.value = best;
|
||||
nearestDistance.value = bestDist;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// Terv/napló összevetés — melyik forráspont "igazolt"
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Egyszerű, szinkron ellenőrzés a már betöltött [logs] lista alapján
|
||||
/// (a nézet előre lekéri, hogy ne fusson adatbázis-lekérdezés minden
|
||||
/// egyes marker kirajzolásakor).
|
||||
bool isVerified(SourcePoint sp, List<VehiclePositionLog> logs) {
|
||||
for (final log in logs) {
|
||||
final dy = sp.planEovY - log.eovY;
|
||||
final dx = sp.planEovX - log.eovX;
|
||||
if (math.sqrt(dy * dy + dx * dx) <= tolerance.value) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<List<VehiclePositionLog>> loadLogs() async {
|
||||
final projectId = ProjectService.to.activeProjectId;
|
||||
if (projectId == null) return [];
|
||||
return _db.listVehiclePositionLogs(projectId);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// Periodikus rögzítés
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
|
||||
void startLogging() {
|
||||
if (isLogging.value) return;
|
||||
isLogging.value = true;
|
||||
_logTick(); // azonnali első pont
|
||||
_logTimer = Timer.periodic(
|
||||
Duration(seconds: logIntervalSec.value), (_) => _logTick());
|
||||
}
|
||||
|
||||
void stopLogging() {
|
||||
isLogging.value = false;
|
||||
_logTimer?.cancel();
|
||||
_logTimer = null;
|
||||
}
|
||||
|
||||
Future<void> _logTick() async {
|
||||
if (!hasPosition.value) return;
|
||||
final projectId = ProjectService.to.activeProjectId;
|
||||
final vehicleId = VehicleIdentityService.to.selectedVehicle.value;
|
||||
if (projectId == null || vehicleId == null) return;
|
||||
|
||||
final gnss = GnssService.to;
|
||||
final entry = VehiclePositionLog(
|
||||
projectId: projectId,
|
||||
vehicleId: vehicleId,
|
||||
eovY: curEovY.value,
|
||||
eovX: curEovX.value,
|
||||
lat: gnss.latitude.value,
|
||||
lon: gnss.longitude.value,
|
||||
altitude: gnss.altitude.value,
|
||||
speedKmh: speedKmh.value,
|
||||
heading: travelHeading.value,
|
||||
fixQuality: gnss.gpsQuality.value,
|
||||
accuracy: gnss.horizontalAccuracy,
|
||||
deviceId: Get.isRegistered<DeviceIdentityService>() &&
|
||||
DeviceIdentityService.to.isReady
|
||||
? DeviceIdentityService.to.deviceId
|
||||
: null,
|
||||
appInstanceId: Get.isRegistered<DeviceIdentityService>() &&
|
||||
DeviceIdentityService.to.isReady
|
||||
? DeviceIdentityService.to.appInstanceId
|
||||
: null,
|
||||
);
|
||||
await _db.insertVehiclePositionLog(entry);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user