Terepbejárás: geometriák hosszának és területének számítása és jelzése
This commit is contained in:
parent
a9316b9b8d
commit
9656bbd332
105
lib/core/geometry_measure.dart
Normal file
105
lib/core/geometry_measure.dart
Normal 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);
|
||||
}
|
||||
17
lib/core/geometry_measure_formatter.dart
Normal file
17
lib/core/geometry_measure_formatter.dart
Normal 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)} m²';
|
||||
}
|
||||
|
||||
return '${(squareMeters / 10000.0).toStringAsFixed(2)} ha';
|
||||
}
|
||||
}
|
||||
@ -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<void> 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<void> 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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -27,8 +27,10 @@ class SaveSheetActions extends StatelessWidget {
|
||||
size: 18,
|
||||
),
|
||||
label: Text(isEditing ? 'Mégse' : 'Vissza'),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
ctrl.cancelEditing();
|
||||
}),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user