Kitűzés: kényelmi funkciók, pontok listája, export
This commit is contained in:
@@ -3,10 +3,13 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:terepi_seged/controls/geoid_grid.dart';
|
||||
import 'package:terepi_seged/services/app_database.dart';
|
||||
import 'package:terepi_seged/services/coord_converter_service.dart';
|
||||
import 'package:terepi_seged/services/gnss/gnss_service.dart';
|
||||
import 'package:terepi_seged/services/project_service.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import '../../models/stakeout_point.dart';
|
||||
|
||||
@@ -49,11 +52,13 @@ class StakeoutService extends GetxService {
|
||||
if (active.value == value) return;
|
||||
active.value = value;
|
||||
if (value) {
|
||||
WakelockPlus.enable();
|
||||
await load();
|
||||
_applyPhase();
|
||||
_scheduleHaptic();
|
||||
} else {
|
||||
_hapticTimer?.cancel();
|
||||
WakelockPlus.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +79,18 @@ class StakeoutService extends GetxService {
|
||||
|
||||
final hapticsEnabled = true.obs;
|
||||
|
||||
final soundEnabled = true.obs;
|
||||
|
||||
/// Átlagolás tároláskor
|
||||
final averagingSeconds = 0.obs;
|
||||
final isAveraging = false.obs;
|
||||
final averagingElapsed = 0.obs;
|
||||
|
||||
/// Max. megengedett crossline eltolás (m; 0=nincs korlát)
|
||||
final maxOffset = 0.0.obs;
|
||||
|
||||
late GeoidGrid geoidGrid;
|
||||
|
||||
// ── Aktuális pozíció (EOV) és navigációs értékek ────────────────
|
||||
final hasPosition = false.obs;
|
||||
final curEovY = 0.0.obs;
|
||||
@@ -96,8 +113,11 @@ class StakeoutService extends GetxService {
|
||||
bool _toleranceAnnounced = false;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
await _loadSettings();
|
||||
geoidGrid = await GeoidGrid.load('assets/Grids/geoid_eht2014.gtx');
|
||||
|
||||
if (Get.isRegistered<GnssService>()) {
|
||||
ever(GnssService.to.lastGgaLine, (_) => _onPosition());
|
||||
}
|
||||
@@ -109,6 +129,24 @@ class StakeoutService extends GetxService {
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
Future<void> _loadSettings() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
toleranceXY.value = prefs.getDouble('stakeout_tolerance') ?? 0.03;
|
||||
averagingSeconds.value = prefs.getInt('stakeout_avg_seconds') ?? 0;
|
||||
hapticsEnabled.value = prefs.getBool('stakeout_haptics') ?? true;
|
||||
soundEnabled.value = prefs.getBool('stakeout_sound') ?? true;
|
||||
maxOffset.value = prefs.getDouble('stakeout_max_offset') ?? 0;
|
||||
}
|
||||
|
||||
Future<void> saveSettings() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setDouble('stakeout_tolerance', toleranceXY.value);
|
||||
await prefs.setInt('stakeout_avg_seconds', averagingSeconds.value);
|
||||
await prefs.setBool('stakeout_haptics', hapticsEnabled.value);
|
||||
await prefs.setBool('stakeout_sound', soundEnabled.value);
|
||||
await prefs.setDouble('stakeout_max_offset', maxOffset.value);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// Betöltés / cél-kezelés
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
@@ -227,12 +265,18 @@ class StakeoutService extends GetxService {
|
||||
!Get.isRegistered<CoordConverterService>()) {
|
||||
return;
|
||||
}
|
||||
final lat = gnss.latitude.value;
|
||||
final lon = gnss.longitude.value;
|
||||
final alt = gnss.altitude.value;
|
||||
final sep = gnss.geoidSeparation.value;
|
||||
|
||||
final p = CoordConverterService.to
|
||||
.wgsToEovPoint(gnss.longitude.value, gnss.latitude.value);
|
||||
final p = CoordConverterService.to.wgsToEovPoint(lon, lat);
|
||||
curEovY.value = p.x;
|
||||
curEovX.value = p.y;
|
||||
curAlt.value = gnss.altitude.value;
|
||||
|
||||
curAlt.value = geoidGrid.toEovHeight(lat, lon, alt, sep)!;
|
||||
|
||||
//curAlt.value = gnss.altitude.value;
|
||||
hasPosition.value = true;
|
||||
|
||||
// Haladási irány: legalább 0,5 m elmozdulásból (állva zajos lenne).
|
||||
@@ -287,7 +331,7 @@ class StakeoutService extends GetxService {
|
||||
if (withinTolerance.value && !wasWithin && !_toleranceAnnounced) {
|
||||
_toleranceAnnounced = true;
|
||||
if (hapticsEnabled.value) HapticFeedback.heavyImpact();
|
||||
SystemSound.play(SystemSoundType.alert);
|
||||
if (soundEnabled.value) SystemSound.play(SystemSoundType.alert);
|
||||
} else if (!withinTolerance.value) {
|
||||
_toleranceAnnounced = false;
|
||||
}
|
||||
@@ -330,6 +374,7 @@ class StakeoutService extends GetxService {
|
||||
} else {
|
||||
HapticFeedback.lightImpact();
|
||||
}
|
||||
if (soundEnabled.value) SystemSound.play(SystemSoundType.click);
|
||||
_scheduleHaptic();
|
||||
});
|
||||
}
|
||||
@@ -380,15 +425,29 @@ class StakeoutService extends GetxService {
|
||||
// Műveletek (adatbázis: AppDatabase)
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Tárolás a jelenlegi mért pozícióval. A rekordba az eltérés mindig
|
||||
/// vonal-relatívan (inline/crossline) kerül; vonal híján É/K bontásban.
|
||||
/// Tárolás a jelenlegi (vagy átlagolt) mért pozícióval. Ha az
|
||||
/// [averagingSeconds] > 0, a megadott ideig gyűjti és átlagolja a
|
||||
/// pozíciókat — csökkenti a pillanatnyi zaj hatását. A rekordba az
|
||||
/// eltérés mindig vonal-relatívan (inline/crossline) kerül; vonal
|
||||
/// híján É/K bontásban.
|
||||
Future<StakeoutPoint?> storeCurrent() async {
|
||||
final t = target.value;
|
||||
if (t == null || !hasPosition.value) return null;
|
||||
if (t == null || !hasPosition.value || isAveraging.value) return null;
|
||||
final gnss = GnssService.to;
|
||||
|
||||
final dy = t.planEovY - curEovY.value;
|
||||
final dx = t.planEovX - curEovX.value;
|
||||
double useY = curEovY.value;
|
||||
double useX = curEovX.value;
|
||||
double useAlt = curAlt.value;
|
||||
|
||||
if (averagingSeconds.value > 0) {
|
||||
final avg = await _collectAverage(averagingSeconds.value);
|
||||
useY = avg.y;
|
||||
useX = avg.x;
|
||||
useAlt = avg.alt;
|
||||
}
|
||||
|
||||
final dy = t.planEovY - useY;
|
||||
final dx = t.planEovX - useX;
|
||||
final lineBearing = lineBearingAt(t);
|
||||
final rad = (lineBearing ?? 0) * math.pi / 180;
|
||||
final inline =
|
||||
@@ -396,16 +455,18 @@ class StakeoutService extends GetxService {
|
||||
final crossline =
|
||||
lineBearing != null ? dy * math.cos(rad) - dx * math.sin(rad) : dy;
|
||||
|
||||
final w = CoordConverterService.to.eovToWgsPoint(useY, useX);
|
||||
|
||||
final updated = t.copyWith(
|
||||
status: StakeoutStatus.staked,
|
||||
measuredEovY: curEovY.value,
|
||||
measuredEovX: curEovX.value,
|
||||
measuredEovZ: curAlt.value,
|
||||
measuredLat: gnss.latitude.value,
|
||||
measuredLon: gnss.longitude.value,
|
||||
measuredEovY: useY,
|
||||
measuredEovX: useX,
|
||||
measuredEovZ: useAlt,
|
||||
measuredLat: w.y,
|
||||
measuredLon: w.x,
|
||||
devInline: inline,
|
||||
devCrossline: crossline,
|
||||
devDz: t.planEovZ != null ? t.planEovZ! - curAlt.value : null,
|
||||
devDz: t.planEovZ != null ? t.planEovZ! - useAlt : null,
|
||||
fixQuality: gnss.gpsQuality.value,
|
||||
accuracy: gnss.horizontalAccuracy,
|
||||
stakedAt: DateTime.now(),
|
||||
@@ -417,6 +478,36 @@ class StakeoutService extends GetxService {
|
||||
return updated;
|
||||
}
|
||||
|
||||
/// Pozíció-átlagolás: [seconds] másodpercig minden GGA-pozíciót
|
||||
/// gyűjt, majd átlagol. Ha nem jött minta, a pillanatnyi értéket adja.
|
||||
Future<({double y, double x, double alt})> _collectAverage(
|
||||
int seconds) async {
|
||||
isAveraging.value = true;
|
||||
averagingElapsed.value = 0;
|
||||
final ys = <double>[], xs = <double>[], alts = <double>[];
|
||||
|
||||
final worker = ever(curEovY, (_) {
|
||||
ys.add(curEovY.value);
|
||||
xs.add(curEovX.value);
|
||||
alts.add(curAlt.value);
|
||||
});
|
||||
final ticker = Timer.periodic(
|
||||
const Duration(seconds: 1), (_) => averagingElapsed.value++);
|
||||
try {
|
||||
await Future.delayed(Duration(seconds: seconds));
|
||||
} finally {
|
||||
worker.dispose();
|
||||
ticker.cancel();
|
||||
isAveraging.value = false;
|
||||
}
|
||||
|
||||
if (ys.isEmpty) {
|
||||
return (y: curEovY.value, x: curEovX.value, alt: curAlt.value);
|
||||
}
|
||||
double mean(List<double> l) => l.reduce((a, b) => a + b) / l.length;
|
||||
return (y: mean(ys), x: mean(xs), alt: mean(alts));
|
||||
}
|
||||
|
||||
Future<void> skipCurrent() async {
|
||||
final t = target.value;
|
||||
if (t == null) return;
|
||||
@@ -428,6 +519,28 @@ class StakeoutService extends GetxService {
|
||||
advance();
|
||||
}
|
||||
|
||||
/// Státusz-állítás a listából (kihagyás / visszaállítás).
|
||||
Future<void> setPointStatus(StakeoutPoint p, StakeoutStatus status) async {
|
||||
final updated = p.copyWith(status: status);
|
||||
await _db.updateStakeoutPoint(updated);
|
||||
final i = points.indexWhere((e) => e.uuid == p.uuid);
|
||||
if (i >= 0) points[i] = updated;
|
||||
points.refresh();
|
||||
if (target.value?.uuid == p.uuid && status != StakeoutStatus.pending) {
|
||||
advance();
|
||||
}
|
||||
}
|
||||
|
||||
/// Pont törlése (soft delete — a szinkron viszi a többi eszközre is).
|
||||
Future<void> deletePoint(StakeoutPoint p) async {
|
||||
await _db.softDeleteStakeoutPoint(p.id!);
|
||||
points.removeWhere((e) => e.uuid == p.uuid);
|
||||
if (target.value?.uuid == p.uuid) {
|
||||
target.value = _firstPending();
|
||||
_recompute();
|
||||
}
|
||||
}
|
||||
|
||||
/// Transzverzális eltolt pont: a vonalra merőlegesen [dist] méterre
|
||||
/// ([toRight] = jobbra a station-növekedés irányából nézve). Az új
|
||||
/// pont lesz a cél; az eltolás-vektor a rekordba kerül.
|
||||
|
||||
Reference in New Issue
Block a user