Terepbejárás: geometriák hosszának és területének számítása és jelzése

This commit is contained in:
torok.istvan 2026-06-20 01:02:50 +02:00
parent a9316b9b8d
commit 9656bbd332
6 changed files with 220 additions and 21 deletions

View File

@ -0,0 +1,105 @@
import 'dart:math';
import 'package:latlong2/latlong.dart';
class GeometryMeasure {
static final Distance _distance = const Distance();
static double lineLengthMeters(List<LatLng> 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<LatLng> 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<LatLng> _normalizedPolygon(List<LatLng> source) {
final points = List<LatLng>.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<LatLng> 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);
}

View File

@ -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)}';
}
return '${(squareMeters / 10000.0).toStringAsFixed(2)} ha';
}
}

View File

@ -21,6 +21,8 @@ import 'package:share_plus/share_plus.dart';
import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:terepi_seged/controls/geoid_grid.dart'; import 'package:terepi_seged/controls/geoid_grid.dart';
import 'package:terepi_seged/controls/wgs84_coordinate_formatter.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_edit_tool.dart';
import 'package:terepi_seged/enums/map_survey_mode.dart'; import 'package:terepi_seged/enums/map_survey_mode.dart';
import 'package:terepi_seged/enums/note_type.dart'; import 'package:terepi_seged/enums/note_type.dart';
@ -194,6 +196,26 @@ class MapSurveyController extends GetxController {
final selectedNoteItemType = NoteType.line.obs; final selectedNoteItemType = NoteType.line.obs;
int? _editingNoteItemId; int? _editingNoteItemId;
bool get isGeometryEditing => _editingNoteItemId != null; 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 => // NoteItem? get selectedPoint =>
// pointNotes.firstWhereOrNull((n) => n.id == selectedNoteItemId.value); // pointNotes.firstWhereOrNull((n) => n.id == selectedNoteItemId.value);
@ -233,6 +255,7 @@ class MapSurveyController extends GetxController {
PolygonEditorController(mode: PolygonEditorMode.polygon); PolygonEditorController(mode: PolygonEditorMode.polygon);
polygonEditorController.addListener(() { polygonEditorController.addListener(() {
editorPointCount.value = polygonEditorController.points.length; editorPointCount.value = polygonEditorController.points.length;
_updateDraftMeasurements();
}); });
await _setInitialPositionFromPhone(); await _setInitialPositionFromPhone();
@ -1064,18 +1087,21 @@ class MapSurveyController extends GetxController {
void startPointTool() { void startPointTool() {
activeEditTool.value = MapEditTool.point; activeEditTool.value = MapEditTool.point;
activeEditLabel.value = '';
} }
void startLineTool() { void startLineTool() {
polygonEditorController.clear(); polygonEditorController.clear();
polygonEditorController.setMode(PolygonEditorMode.line); polygonEditorController.setMode(PolygonEditorMode.line);
activeEditTool.value = MapEditTool.line; activeEditTool.value = MapEditTool.line;
activeEditLabel.value = '';
} }
void startPolygonTool() { void startPolygonTool() {
polygonEditorController.clear(); polygonEditorController.clear();
polygonEditorController.setMode(PolygonEditorMode.polygon); polygonEditorController.setMode(PolygonEditorMode.polygon);
activeEditTool.value = MapEditTool.polygon; activeEditTool.value = MapEditTool.polygon;
activeEditLabel.value = '';
} }
void cancelEditing() { void cancelEditing() {
@ -1084,14 +1110,8 @@ class MapSurveyController extends GetxController {
_editingNoteItemId = null; _editingNoteItemId = null;
selectedNoteItemId.value = null; selectedNoteItemId.value = null;
editorPointCount.value = 0; editorPointCount.value = 0;
} draftAreaSquareMeters.value = 0.0;
draftLengthMeters.value = 0.0;
void openFeatureList() {
// TODO: DraggableScrollableSheet / bottom sheet lista
}
void openLayerPanel() {
// TODO: rétegek sheet
} }
void finishGeometry() { void finishGeometry() {
@ -1252,6 +1272,8 @@ class MapSurveyController extends GetxController {
await updateNoteItem(updated); await updateNoteItem(updated);
_editingNoteItemId = null; _editingNoteItemId = null;
activeEditTool.value = MapEditTool.none; activeEditTool.value = MapEditTool.none;
draftAreaSquareMeters.value = 0.0;
draftLengthMeters.value = 0.0;
} }
Future<void> deleteEditingItem() async { Future<void> deleteEditingItem() async {
@ -1262,6 +1284,8 @@ class MapSurveyController extends GetxController {
_editingNoteItemId = null; _editingNoteItemId = null;
selectedNoteItemId.value = null; selectedNoteItemId.value = null;
activeEditTool.value = MapEditTool.none; activeEditTool.value = MapEditTool.none;
draftAreaSquareMeters.value = 0.0;
draftLengthMeters.value = 0.0;
} }
// Future<void> finishDraft() async { // Future<void> finishDraft() async {
@ -1518,6 +1542,8 @@ class MapSurveyController extends GetxController {
polygonEditorController.clear(); polygonEditorController.clear();
activeEditTool.value = MapEditTool.none; activeEditTool.value = MapEditTool.none;
editorPointCount.value = 0; editorPointCount.value = 0;
draftAreaSquareMeters.value = 0.0;
draftLengthMeters.value = 0.0;
Get.snackbar( Get.snackbar(
'Geometria frissítve', 'Geometria frissítve',
@ -1540,6 +1566,9 @@ class MapSurveyController extends GetxController {
} }
polygonEditorController.clear(); polygonEditorController.clear();
activeEditTool.value = MapEditTool.none; activeEditTool.value = MapEditTool.none;
draftAreaSquareMeters.value = 0.0;
draftLengthMeters.value = 0.0;
activeEditLabel.value = '';
} }
if (polygonEditorController.mode == PolygonEditorMode.polygon) { if (polygonEditorController.mode == PolygonEditorMode.polygon) {
@ -1554,6 +1583,8 @@ class MapSurveyController extends GetxController {
} }
polygonEditorController.clear(); polygonEditorController.clear();
activeEditTool.value = MapEditTool.none; 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; if (polygonEditorController.mode == PolygonEditorMode.line) return 2;
return 3; // polygon return 3; // polygon
} }
void _updateDraftMeasurements() {
switch (activeEditTool.value) {
case MapEditTool.line:
final points = List<LatLng>.from(
polygonEditorController.points,
);
draftLengthMeters.value = GeometryMeasure.lineLengthMeters(points);
draftAreaSquareMeters.value = 0.0;
break;
case MapEditTool.polygon:
final points = List<LatLng>.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;
}
}
} }

View File

@ -20,6 +20,7 @@ class LineOrPolygonDrawingContent extends StatelessWidget {
return Obx(() { return Obx(() {
final pointCount = controller.editorPointCount.value; final pointCount = controller.editorPointCount.value;
final canFinish = controller.canFinishGeometry; final canFinish = controller.canFinishGeometry;
final measureText = controller.draftMeasureText;
return Column( return Column(
mainAxisSize: MainAxisSize.min, 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( SmallStatusChip(
text: _requiredPointText(controller.activeEditTool.value), text: _requiredPointText(controller.activeEditTool.value),
color: canFinish color: canFinish

View File

@ -70,13 +70,13 @@ class MapEditCompactToolbar extends StatelessWidget {
icon: Icons.list_alt_outlined, icon: Icons.list_alt_outlined,
label: 'Lista', label: 'Lista',
selected: false, selected: false,
onTap: controller.openFeatureList, onTap: () {},
), ),
ToolbarAction( ToolbarAction(
icon: Icons.layers_outlined, icon: Icons.layers_outlined,
label: 'Rétegek', label: 'Rétegek',
selected: false, selected: false,
onTap: controller.openLayerPanel, onTap: () {},
), ),
], ],
), ),

View File

@ -27,8 +27,10 @@ class SaveSheetActions extends StatelessWidget {
size: 18, size: 18,
), ),
label: Text(isEditing ? 'Mégse' : 'Vissza'), label: Text(isEditing ? 'Mégse' : 'Vissza'),
onPressed: () => Get.back(), onPressed: () {
), Get.back();
ctrl.cancelEditing();
}),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(