diff --git a/lib/controls/geoid_grid.dart b/lib/controls/geoid_grid.dart new file mode 100644 index 0000000..6317fb3 --- /dev/null +++ b/lib/controls/geoid_grid.dart @@ -0,0 +1,67 @@ +import 'dart:typed_data'; +import 'package:flutter/services.dart'; + +class GeoidGrid { + final double southLat, westLon, latStep, lonStep; + final int nRows, nCols; + final Float32List grid; // big-endian, south-to-north + + GeoidGrid._(this.southLat, this.westLon, this.latStep, this.lonStep, + this.nRows, this.nCols, this.grid); + + static Future load(String assetPath) async { + final bytes = (await rootBundle.load(assetPath)).buffer.asByteData(); + final southLat = bytes.getFloat64(0, Endian.big); + final westLon = bytes.getFloat64(8, Endian.big); + final latStep = bytes.getFloat64(16, Endian.big); + final lonStep = bytes.getFloat64(24, Endian.big); + final nRows = bytes.getInt32(32, Endian.big); + final nCols = bytes.getInt32(36, Endian.big); + + final grid = Float32List(nRows * nCols); + for (int i = 0; i < grid.length; i++) { + grid[i] = bytes.getFloat32(40 + i * 4, Endian.big); + } + return GeoidGrid._(southLat, westLon, latStep, lonStep, nRows, nCols, grid); + } + + /// Returns EHT2014 geoid undulation [m] at (lat, lon) via bilinear interpolation. + /// Returns null if outside grid or in no-data area. + double? getUndulation(double lat, double lon) { + final rowF = (lat - southLat) / latStep; + final colF = (lon - westLon) / lonStep; + final r0 = rowF.floor(); + final c0 = colF.floor(); + if (r0 < 0 || r0 >= nRows - 1 || c0 < 0 || c0 >= nCols - 1) return null; + + final dr = rowF - r0; + final dc = colF - c0; + + double v(int r, int c) => grid[r * nCols + c].toDouble(); + final noData = -80.0; + final v00 = v(r0, c0); + if (v00 < noData) return null; + final v10 = v(r0 + 1, c0); + if (v10 < noData) return null; + final v01 = v(r0, c0 + 1); + if (v01 < noData) return null; + final v11 = v(r0 + 1, c0 + 1); + if (v11 < noData) return null; + + return v00 * (1 - dr) * (1 - dc) + + v10 * dr * (1 - dc) + + v01 * (1 - dr) * dc + + v11 * dr * dc; + } + + /// Orthometric height in the Hungarian EHT2014 system. + /// [nmea_ortho] = GGA field 9 (altitudeAboveMeanSeaLevel) + /// [nmea_sep] = GGA field 11 (geoidSeparation from receiver) + double? toEovHeight( + double lat, double lon, double nmeaOrtho, double nmeaSep) { + final nEht2014 = getUndulation(lat, lon); + if (nEht2014 == null) return null; + final ellipsoidal = nmeaOrtho + nmeaSep; // h = H + N_receiver + return ellipsoidal - nEht2014; // H_EOV = h - N_EHT2014 + } +} diff --git a/lib/pages/map/presentation/controllers/map_controller.dart b/lib/pages/map/presentation/controllers/map_controller.dart index acfdd60..5932306 100644 --- a/lib/pages/map/presentation/controllers/map_controller.dart +++ b/lib/pages/map/presentation/controllers/map_controller.dart @@ -21,6 +21,7 @@ import 'package:permission_handler/permission_handler.dart' as permission_handler; import 'package:share_plus/share_plus.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; +import 'package:terepi_seged/controls/geoid_grid.dart'; import 'package:terepi_seged/eov/convert_coordinate.dart'; import 'package:terepi_seged/eov/eov.dart'; import 'package:terepi_seged/gnss_sentences/gngga.dart'; @@ -78,6 +79,8 @@ class MapViewController extends GetxController { RxDouble gpsAltitudeError = 0.0.obs; Rx gpsDateTime = DateTime(2000).obs; Rx eov = Eov(0, 0).obs; + Rx eovHeight = 0.0.obs; + RxInt latDegree = 0.obs; RxInt latMin = 0.obs; RxDouble latSec = 0.0.obs; @@ -99,6 +102,8 @@ class MapViewController extends GetxController { RxInt pointsToMeasureSelectedValue = (-1).obs; RxDouble distance = 0.0.obs; + late GeoidGrid geoidGrid; + TextEditingController pointIdController = TextEditingController(); TextEditingController pointDescriptionController = TextEditingController(); TextEditingController gpsHeightController = TextEditingController(); @@ -180,6 +185,8 @@ class MapViewController extends GetxController { }) .subscribe(); + geoidGrid = await GeoidGrid.load('assets/Grids/geoid_eht2014.gtx'); + mapController = MapController(); // riveGpsIconController = RiveUtils.getRiveController(Artboard(), @@ -482,6 +489,11 @@ class MapViewController extends GetxController { gpsQuality.value = sentence.gpsQualityIndicator; eov.value = ConvertCoordinate.ConvertWgsToEov( gpsLatitude.value, gpsLongitude.value); + eovHeight.value = geoidGrid?.toEovHeight( + gpsLatitude.value, + gpsLongitude.value, + gpsAltitude.value, + gpsGeoidSeparation.value); if (pointsToMeasureSelectedValue.value >= 0) { double coordX = diff --git a/lib/pages/map/presentation/views/map_view.dart b/lib/pages/map/presentation/views/map_view.dart index 33d1fd5..295b8b9 100644 --- a/lib/pages/map/presentation/views/map_view.dart +++ b/lib/pages/map/presentation/views/map_view.dart @@ -154,7 +154,7 @@ class MapView extends GetView { "EovX: ${controller.formatEov.format(controller.eov.value.Y)}"), Text( "EovY: ${controller.formatEov.format(controller.eov.value.X)}"), - Text("Alt: ${controller.gpsAltitude} (m)"), + Text("Alt: ${controller.eovHeight.value} (m)"), const Divider( height: 5.0, color: Colors.black,