From 9656bbd3320edce5b926ceed1a653492ffc44b7a Mon Sep 17 00:00:00 2001 From: "torok.istvan" Date: Sat, 20 Jun 2026 01:02:50 +0200 Subject: [PATCH] =?UTF-8?q?Terepbej=C3=A1r=C3=A1s:=20geometri=C3=A1k=20hos?= =?UTF-8?q?sz=C3=A1nak=20=C3=A9s=20ter=C3=BClet=C3=A9nek=20sz=C3=A1m=C3=AD?= =?UTF-8?q?t=C3=A1sa=20=C3=A9s=20jelz=C3=A9se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/geometry_measure.dart | 105 ++++++++++++++++++ lib/core/geometry_measure_formatter.dart | 17 +++ .../controllers/map_survey_controller.dart | 78 +++++++++++-- ..._edit_line_or_polygon_drawing_content.dart | 13 +++ .../map_edit_tools/map_edit_toolbar.dart | 4 +- .../map_edit_tools/save_sheet_actions.dart | 24 ++-- 6 files changed, 220 insertions(+), 21 deletions(-) create mode 100644 lib/core/geometry_measure.dart create mode 100644 lib/core/geometry_measure_formatter.dart diff --git a/lib/core/geometry_measure.dart b/lib/core/geometry_measure.dart new file mode 100644 index 0000000..2857d33 --- /dev/null +++ b/lib/core/geometry_measure.dart @@ -0,0 +1,105 @@ +import 'dart:math'; + +import 'package:latlong2/latlong.dart'; + +class GeometryMeasure { + static final Distance _distance = const Distance(); + + static double lineLengthMeters(List points) { + if (points.length < 2) return 0.0; + + double sum = 0.0; + + for (var i = 1; i < points.length; i++) { + sum += _distance.as( + LengthUnit.Meter, + points[i - 1], + points[i], + ); + } + + return sum; + } + + static double polygonAreaSquareMeters(List points) { + final polygon = _normalizedPolygon(points); + + if (polygon.length < 3) return 0.0; + + final origin = _centroid(polygon); + final projected = + polygon.map((p) => _projectToLocalMeters(p, origin)).toList(); + + double area = 0.0; + + for (var i = 0; i < projected.length; i++) { + final j = (i + 1) % projected.length; + + area += projected[i].x * projected[j].y - projected[j].x * projected[i].y; + } + + return area.abs() / 2.0; + } + + static List _normalizedPolygon(List source) { + final points = List.from(source); + + if (points.length < 2) return points; + + final first = points.first; + final last = points.last; + + final closeDistance = _distance.as( + LengthUnit.Meter, + first, + last, + ); + + if (closeDistance < 0.01) { + points.removeLast(); + } + + return points; + } + + static LatLng _centroid(List points) { + double lat = 0.0; + double lon = 0.0; + + for (final p in points) { + lat += p.latitude; + lon += p.longitude; + } + + return LatLng( + lat / points.length, + lon / points.length, + ); + } + + static _Point2D _projectToLocalMeters( + LatLng point, + LatLng origin, + ) { + const earthRadius = 6378137.0; + + final latRad = point.latitude * pi / 180.0; + final lonRad = point.longitude * pi / 180.0; + + final originLatRad = origin.latitude * pi / 180.0; + final originLonRad = origin.longitude * pi / 180.0; + + final x = earthRadius * (lonRad - originLonRad) * cos(originLatRad); + + final y = earthRadius * (latRad - originLatRad); + + return _Point2D(x, y); + } +} + +class _Point2D { + final double x; + final double y; + + const _Point2D(this.x, this.y); +} diff --git a/lib/core/geometry_measure_formatter.dart b/lib/core/geometry_measure_formatter.dart new file mode 100644 index 0000000..ae8c661 --- /dev/null +++ b/lib/core/geometry_measure_formatter.dart @@ -0,0 +1,17 @@ +class GeometryMeasureFormatter { + static String length(double meters) { + if (meters < 1000.0) { + return '${meters.toStringAsFixed(0)} m'; + } + + return '${(meters / 1000.0).toStringAsFixed(2)} km'; + } + + static String area(double squareMeters) { + if (squareMeters < 10000.0) { + return '${squareMeters.toStringAsFixed(0)} m²'; + } + + return '${(squareMeters / 10000.0).toStringAsFixed(2)} ha'; + } +} 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 29b0c03..25cc15f 100644 --- a/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart +++ b/lib/pages/map_survey/presentations/controllers/map_survey_controller.dart @@ -21,6 +21,8 @@ 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/controls/wgs84_coordinate_formatter.dart'; +import 'package:terepi_seged/core/geometry_measure.dart'; +import 'package:terepi_seged/core/geometry_measure_formatter.dart'; import 'package:terepi_seged/enums/map_edit_tool.dart'; import 'package:terepi_seged/enums/map_survey_mode.dart'; import 'package:terepi_seged/enums/note_type.dart'; @@ -194,6 +196,26 @@ class MapSurveyController extends GetxController { final selectedNoteItemType = NoteType.line.obs; int? _editingNoteItemId; bool get isGeometryEditing => _editingNoteItemId != null; + final draftLengthMeters = 0.0.obs; + final draftAreaSquareMeters = 0.0.obs; + + String get draftMeasureText { + switch (activeEditTool.value) { + case MapEditTool.line: + return GeometryMeasureFormatter.length( + draftLengthMeters.value, + ); + + case MapEditTool.polygon: + return GeometryMeasureFormatter.area( + draftAreaSquareMeters.value, + ); + + case MapEditTool.point: + case MapEditTool.none: + return ''; + } + } // NoteItem? get selectedPoint => // pointNotes.firstWhereOrNull((n) => n.id == selectedNoteItemId.value); @@ -233,6 +255,7 @@ class MapSurveyController extends GetxController { PolygonEditorController(mode: PolygonEditorMode.polygon); polygonEditorController.addListener(() { editorPointCount.value = polygonEditorController.points.length; + _updateDraftMeasurements(); }); await _setInitialPositionFromPhone(); @@ -1064,18 +1087,21 @@ class MapSurveyController extends GetxController { void startPointTool() { activeEditTool.value = MapEditTool.point; + activeEditLabel.value = ''; } void startLineTool() { polygonEditorController.clear(); polygonEditorController.setMode(PolygonEditorMode.line); activeEditTool.value = MapEditTool.line; + activeEditLabel.value = ''; } void startPolygonTool() { polygonEditorController.clear(); polygonEditorController.setMode(PolygonEditorMode.polygon); activeEditTool.value = MapEditTool.polygon; + activeEditLabel.value = ''; } void cancelEditing() { @@ -1084,14 +1110,8 @@ class MapSurveyController extends GetxController { _editingNoteItemId = null; selectedNoteItemId.value = null; editorPointCount.value = 0; - } - - void openFeatureList() { - // TODO: DraggableScrollableSheet / bottom sheet lista - } - - void openLayerPanel() { - // TODO: rétegek sheet + draftAreaSquareMeters.value = 0.0; + draftLengthMeters.value = 0.0; } void finishGeometry() { @@ -1252,6 +1272,8 @@ class MapSurveyController extends GetxController { await updateNoteItem(updated); _editingNoteItemId = null; activeEditTool.value = MapEditTool.none; + draftAreaSquareMeters.value = 0.0; + draftLengthMeters.value = 0.0; } Future deleteEditingItem() async { @@ -1262,6 +1284,8 @@ class MapSurveyController extends GetxController { _editingNoteItemId = null; selectedNoteItemId.value = null; activeEditTool.value = MapEditTool.none; + draftAreaSquareMeters.value = 0.0; + draftLengthMeters.value = 0.0; } // Future finishDraft() async { @@ -1518,6 +1542,8 @@ class MapSurveyController extends GetxController { polygonEditorController.clear(); activeEditTool.value = MapEditTool.none; editorPointCount.value = 0; + draftAreaSquareMeters.value = 0.0; + draftLengthMeters.value = 0.0; Get.snackbar( 'Geometria frissítve', @@ -1540,6 +1566,9 @@ class MapSurveyController extends GetxController { } polygonEditorController.clear(); activeEditTool.value = MapEditTool.none; + draftAreaSquareMeters.value = 0.0; + draftLengthMeters.value = 0.0; + activeEditLabel.value = ''; } if (polygonEditorController.mode == PolygonEditorMode.polygon) { @@ -1554,6 +1583,8 @@ class MapSurveyController extends GetxController { } polygonEditorController.clear(); activeEditTool.value = MapEditTool.none; + draftAreaSquareMeters.value = 0.0; + draftLengthMeters.value = 0.0; } } @@ -1561,4 +1592,35 @@ class MapSurveyController extends GetxController { if (polygonEditorController.mode == PolygonEditorMode.line) return 2; return 3; // polygon } + + void _updateDraftMeasurements() { + switch (activeEditTool.value) { + case MapEditTool.line: + final points = List.from( + polygonEditorController.points, + ); + + draftLengthMeters.value = GeometryMeasure.lineLengthMeters(points); + + draftAreaSquareMeters.value = 0.0; + break; + + case MapEditTool.polygon: + final points = List.from( + polygonEditorController.points, + ); + + draftLengthMeters.value = GeometryMeasure.lineLengthMeters(points); + + draftAreaSquareMeters.value = + GeometryMeasure.polygonAreaSquareMeters(points); + break; + + case MapEditTool.point: + case MapEditTool.none: + draftLengthMeters.value = 0.0; + draftAreaSquareMeters.value = 0.0; + break; + } + } } diff --git a/lib/widgets/map_edit_tools/map_edit_line_or_polygon_drawing_content.dart b/lib/widgets/map_edit_tools/map_edit_line_or_polygon_drawing_content.dart index 9345050..7c57fe3 100644 --- a/lib/widgets/map_edit_tools/map_edit_line_or_polygon_drawing_content.dart +++ b/lib/widgets/map_edit_tools/map_edit_line_or_polygon_drawing_content.dart @@ -20,6 +20,7 @@ class LineOrPolygonDrawingContent extends StatelessWidget { return Obx(() { final pointCount = controller.editorPointCount.value; final canFinish = controller.canFinishGeometry; + final measureText = controller.draftMeasureText; return Column( mainAxisSize: MainAxisSize.min, @@ -42,6 +43,18 @@ class LineOrPolygonDrawingContent extends StatelessWidget { ), ), ), + if (measureText.isNotEmpty) ...[ + const SizedBox( + width: 6, + ), + SmallStatusChip( + text: measureText, + color: colorScheme.primary, + ), + const SizedBox( + width: 6, + ), + ], SmallStatusChip( text: _requiredPointText(controller.activeEditTool.value), color: canFinish diff --git a/lib/widgets/map_edit_tools/map_edit_toolbar.dart b/lib/widgets/map_edit_tools/map_edit_toolbar.dart index 4b43071..5fa5ff4 100644 --- a/lib/widgets/map_edit_tools/map_edit_toolbar.dart +++ b/lib/widgets/map_edit_tools/map_edit_toolbar.dart @@ -70,13 +70,13 @@ class MapEditCompactToolbar extends StatelessWidget { icon: Icons.list_alt_outlined, label: 'Lista', selected: false, - onTap: controller.openFeatureList, + onTap: () {}, ), ToolbarAction( icon: Icons.layers_outlined, label: 'Rétegek', selected: false, - onTap: controller.openLayerPanel, + onTap: () {}, ), ], ), diff --git a/lib/widgets/map_edit_tools/save_sheet_actions.dart b/lib/widgets/map_edit_tools/save_sheet_actions.dart index 1a290d5..259165c 100644 --- a/lib/widgets/map_edit_tools/save_sheet_actions.dart +++ b/lib/widgets/map_edit_tools/save_sheet_actions.dart @@ -18,17 +18,19 @@ class SaveSheetActions extends StatelessWidget { Row(children: [ Expanded( child: OutlinedButton.icon( - style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10))), - icon: Icon( - isEditing ? Icons.close : Icons.arrow_back, - size: 18, - ), - label: Text(isEditing ? 'Mégse' : 'Vissza'), - onPressed: () => Get.back(), - ), + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10))), + icon: Icon( + isEditing ? Icons.close : Icons.arrow_back, + size: 18, + ), + label: Text(isEditing ? 'Mégse' : 'Vissza'), + onPressed: () { + Get.back(); + ctrl.cancelEditing(); + }), ), const SizedBox(width: 12), Expanded(