Terepbejárás geometriák szerkesztése: kontrollpontok és tulajdonságok.

This commit is contained in:
2026-06-19 12:53:50 +02:00
parent 1276ac0610
commit 0257beec38
8 changed files with 921 additions and 108 deletions
@@ -23,8 +23,10 @@ import 'package:terepi_seged/controls/geoid_grid.dart';
import 'package:terepi_seged/controls/wgs84_coordinate_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';
import 'package:terepi_seged/eov/convert_coordinate.dart';
import 'package:terepi_seged/eov/eov.dart';
import 'package:terepi_seged/models/note_item.dart';
import 'package:terepi_seged/models/point_to_measure.dart';
import 'package:terepi_seged/models/point_with_description_model.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -32,11 +34,13 @@ import 'package:terepi_seged/pages/map_survey/presentations/views/measured_point
import 'package:terepi_seged/pages/ntrip_settings/presentation/controllers/ntrip_settings_controller.dart';
import 'package:terepi_seged/pages/ntrip_settings/presentation/views/ntrip_settings_sheet.dart';
import 'package:terepi_seged/pages/tracking/presentation/controllers/tracking_controller.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_connection.dart';
import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
import 'package:terepi_seged/services/gnss/gnss_service.dart';
import 'package:terepi_seged/services/ntrip_service.dart';
import 'package:terepi_seged/services/project_service.dart';
import 'package:terepi_seged/widgets/map_edit_tools/map_feature_save_sheet.dart';
class MapSurveyController extends GetxController {
@@ -120,6 +124,9 @@ class MapSurveyController extends GetxController {
final MapController mapController = MapController();
final polylineHitNotifier = ValueNotifier<LayerHitResult<int>?>(null);
final polygonHitNotifier = ValueNotifier<LayerHitResult<int>?>(null);
final currentLocationMarker = <Marker>[].obs;
final pointNotesMarker = <Marker>[].obs;
final pointsToMeasureMarker = <Marker>[].obs;
@@ -166,8 +173,8 @@ class MapSurveyController extends GetxController {
final activeEditTool = MapEditTool.none.obs;
final editorPointCount = 0.obs;
final pointNotes = <Marker>[].obs;
final polylineNotes = <Polyline<Object>>[].obs;
final polygonNotes = <Polygon<Object>>[].obs;
final polylineNotes = <Polyline<int>>[].obs;
final polygonNotes = <Polygon<int>>[].obs;
late final PolygonEditorController polygonEditorController;
@@ -181,6 +188,13 @@ class MapSurveyController extends GetxController {
const PolygonLabelPlacementCalculator.centroid();
bool get isMapEditing => activeEditTool.value != MapEditTool.none;
final selectedNoteItemId = Rx<int?>(null);
final selectedNoteItemType = NoteType.line.obs;
int? _editingNoteItemId;
bool get isGeometryEditing => _editingNoteItemId != null;
// NoteItem? get selectedPoint =>
// pointNotes.firstWhereOrNull((n) => n.id == selectedNoteItemId.value);
// ─────────────────────────────────────────────────────────────────
// Lifecycle
@@ -229,6 +243,10 @@ class MapSurveyController extends GetxController {
await _initStorage();
gpsHeightController.text = '1.8';
ever(ProjectService.to.activeProject, (_) => _loadNoteItems());
await _loadNoteItems();
}
@override
@@ -962,6 +980,9 @@ class MapSurveyController extends GetxController {
void cancelEditing() {
polygonEditorController.clear();
activeEditTool.value = MapEditTool.none;
_editingNoteItemId = null;
selectedNoteItemId.value = null;
editorPointCount.value = 0;
}
void openFeatureList() {
@@ -1005,73 +1026,437 @@ class MapSurveyController extends GetxController {
//draftPoints.clear();
}
Future<void> finishDraft() async {
if (polygonEditorController.mode == PolygonEditorMode.line) {
print("Points number in line: ${polygonEditorController.points.length}");
print(
"1. point coords: ${polygonEditorController.points[0].latitude} - ${polygonEditorController.points[0].longitude}");
if (polygonEditorController.points.length < 2) return;
Marker _markerFromNoteItem(NoteItem item) {
return Marker(
key: ValueKey('note_point_${item.id}'),
point: item.points.first,
width: 32.0,
height: 32.0,
child: GestureDetector(
onTap: () => selectedNoteItem(item.id!),
child: Obx(() {
final isSelected = selectedNoteItemId.value == item.id;
return Center(
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: isSelected ? 26.0 : 20.0,
height: isSelected ? 26.0 : 20.0,
decoration: BoxDecoration(
color: item.color,
shape: BoxShape.circle,
border: Border.all(
width: isSelected ? 3.0 : 1.5,
color: isSelected ? Colors.white : item.strokeColor),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.3),
blurRadius: isSelected ? 8 : 3)
]),
));
}),
),
);
}
// ── SQLite mentés ─────────────────────────────────────────────────
Polyline polyline = Polyline(
points: List.from(polygonEditorController.points),
color: activeEditColor.value,
strokeWidth: activeEditStrokeWidth.value,
// hitValue: (
// title: 'Purple Line',
// subtitle: 'Nothing really special here...',
// ),
Future<NoteItem?> _saveItem({
required NoteType type,
required List<LatLng> points,
}) async {
final projectId = ProjectService.to.activeProject.value?.id;
final item = NoteItem(
projectId: projectId, // projekt nélkül is mentődik
type: type,
points: points,
color: activeEditColor.value,
opacity: activeEditOpacity.value,
strokeWidth: activeEditStrokeWidth.value,
strokeColor: activeEditStrokeColor.value,
label: activeEditLabel.value,
createdAt: DateTime.now(),
);
try {
final id = await AppDatabase.instance.insertNoteItem(item);
// id-vel visszaadott NoteItem — a hitValue ehhez az id-hez kötődik
return NoteItem(
id: id,
projectId: item.projectId,
type: item.type,
points: item.points,
color: item.color,
opacity: item.opacity,
strokeWidth: item.strokeWidth,
strokeColor: item.strokeColor,
label: item.label,
createdAt: item.createdAt,
);
polylineNotes.add(polyline);
// polylineNotes.refresh();
print("Points number in polylineNotes: ${polylineNotes.length}");
print(
"1. point coords of polyline: ${polyline.points[0].latitude} - ${polyline.points[0].longitude}");
polygonEditorController.clear();
activeEditTool.value = MapEditTool.none;
}
if (polygonEditorController.mode == PolygonEditorMode.polygon) {
print(
"Points number in polygon: ${polygonEditorController.points.length}");
Polygon polygon = Polygon(
points: List.from(polygonEditorController.points),
color: activeEditColor.value.withValues(alpha: activeEditOpacity.value),
borderColor: activeEditStrokeColor.value,
borderStrokeWidth: activeEditStrokeWidth.value,
label: activeEditLabel.value,
labelPlacementCalculator: _labelPlacementCalculator,
// hitValue: (
// title: 'Basic Filled Polygon',
// subtitle: 'Nothing really special here...',
);
polygonNotes.add(polygon);
//polygonNotes.refresh();
//update();
print("Points number in polygonNotes: ${polygonNotes.length}");
polygonEditorController.clear();
activeEditTool.value = MapEditTool.none;
} catch (e) {
print('_saveItem hiba: $e');
return null;
}
}
void saveEditedPoint({required LatLng point}) {
Marker marker = Marker(
point: point,
width: 15.0,
height: 15.0,
child: Container(
width: 15.0,
height: 15.0,
decoration: BoxDecoration(
color: Colors.amber[700],
shape: BoxShape.circle,
border: Border.all(width: 1.0, color: Colors.black)),
));
pointNotes.add(marker);
// ── SQLite betöltés (onReady-ben hívandó) ─────────────────────────
Future<void> _loadNoteItems() async {
final projectId = ProjectService.to.activeProject.value?.id;
final items = await AppDatabase.instance.listNoteItems(projectId);
// Listák resetelése
pointNotes.clear();
polylineNotes.clear();
polygonNotes.clear();
for (final item in items) {
switch (item.type) {
case NoteType.point:
pointNotes.add(_markerFromNoteItem(item));
case NoteType.line:
polylineNotes.add(item.toPolyline());
case NoteType.polygon:
polygonNotes.add(item.toPolygon());
}
}
}
Future<void> finishDraft() async {
if (_editingNoteItemId != null) {
if (polygonEditorController.points.isEmpty) {
await _finishStyleUpdate();
} else {
// ── FRISSÍTÉSI MÓD ──────────────────────────────────────────
await _finishGeometryUpdate();
}
} else {
// ── LÉTREHOZÁSI MÓD (eredeti logika) ────────────────────────
await _finishCreate();
}
}
Future<void> _finishStyleUpdate() async {
final id = _editingNoteItemId!;
final existing = await AppDatabase.instance.getNoteItem(id);
if (existing == null) return;
final updated = existing.copyWith(
color: activeEditColor.value,
opacity: activeEditOpacity.value,
strokeWidth: activeEditStrokeWidth.value,
strokeColor: activeEditStrokeColor.value,
label: activeEditLabel.value,
);
await updateNoteItem(updated);
_editingNoteItemId = null;
}
Future<void> deleteEditingItem() async {
final id = _editingNoteItemId;
if (id == null) return;
final item = await AppDatabase.instance.getNoteItem(id);
if (item != null) await deleteNoteItem(item);
_editingNoteItemId = null;
selectedNoteItemId.value = null;
activeEditTool.value = MapEditTool.none;
}
// Future<void> finishDraft() async {
// if (polygonEditorController.mode == PolygonEditorMode.line) {
// print("Points number in line: ${polygonEditorController.points.length}");
// print(
// "1. point coords: ${polygonEditorController.points[0].latitude} - ${polygonEditorController.points[0].longitude}");
// if (polygonEditorController.points.length < 2) return;
// final saved = await _saveItem(
// type: NoteType.line,
// points: List.from(polygonEditorController.points));
// if (saved != null) {
// polylineNotes.add(saved.toPolyline());
// }
// polygonEditorController.clear();
// activeEditTool.value = MapEditTool.none;
// }
// if (polygonEditorController.mode == PolygonEditorMode.polygon) {
// if (polygonEditorController.points.length < 3) return;
// final saved = await _saveItem(
// type: NoteType.polygon,
// points: List.from(polygonEditorController.points));
// if (saved != null) {
// polygonNotes.add(saved.toPolygon());
// }
// polygonEditorController.clear();
// activeEditTool.value = MapEditTool.none;
// }
// }
void saveEditedPoint({required LatLng point}) {
if (_editingNoteItemId != null) {
// ── PONT FRISSÍTÉSI MÓD ──────────────────────────────────────
final id = _editingNoteItemId!;
_editingNoteItemId = null;
activeEditTool.value = MapEditTool.none;
AppDatabase.instance.getNoteItem(id).then((existing) async {
if (existing == null) return;
final updated = existing.copyWith(points: [point]);
await updateNoteItem(updated);
});
} else {
// ── ÚJ PONT LÉTREHOZÁS (eredeti logika) ─────────────────────
_saveItem(
type: NoteType.point,
points: [point],
).then((saved) {
if (saved == null) return;
pointNotes.add(_markerFromNoteItem(saved));
});
activeEditTool.value = MapEditTool.none;
}
}
// void saveEditedPoint({required LatLng point}) {
// _saveItem(type: NoteType.point, points: [point]).then((saved) {
// if (saved == null) return;
// pointNotes.add(_markerFromNoteItem(saved));
// });
// // Marker marker = Marker(
// // point: point,
// // width: 15.0,
// // height: 15.0,
// // child: Container(
// // width: 15.0,
// // height: 15.0,
// // decoration: BoxDecoration(
// // color: Colors.amber[700],
// // shape: BoxShape.circle,
// // border: Border.all(width: 1.0, color: Colors.black)),
// // ));
// //pointNotes.add(marker);
// activeEditTool.value = MapEditTool.none;
// }
Future<void> selectedNoteItem(int id) async {
selectedNoteItemId.value = id;
final item = await AppDatabase.instance.getNoteItem(id);
if (item == null) return;
_editingNoteItemId = item.id;
activeEditColor.value = item.color;
activeEditOpacity.value = item.opacity;
activeEditStrokeWidth.value = item.strokeWidth;
activeEditStrokeColor.value = item.strokeColor;
activeEditLabel.value = item.label;
activeEditTool.value = switch (item.type) {
NoteType.point => MapEditTool.point,
NoteType.line => MapEditTool.line,
NoteType.polygon => MapEditTool.polygon
};
Get.bottomSheet(
DraggableScrollableSheet(
initialChildSize: 0.52,
minChildSize: 0.35,
maxChildSize: 0.85,
snap: true,
snapSizes: const [0.35, 0.52, 0.85],
expand: false,
builder: (_, scrollCtrl) =>
MapFeatureSaveSheet(ctrl: this, scrollCtrl: scrollCtrl)),
isScrollControlled: true,
backgroundColor: Colors.transparent,
ignoreSafeArea: false)
.whenComplete(() {
selectedNoteItemId.value = null;
//_editingNoteItemId = null;
if (!isGeometryEditing) {
activeEditTool.value = MapEditTool.none;
}
});
}
void clearNoteItemSelection() {
selectedNoteItemId.value = null;
}
Future<void> updateNoteItem(NoteItem updated) async {
await AppDatabase.instance.updateNoteItem(updated);
switch (updated.type) {
case NoteType.line:
final idx = polylineNotes.indexWhere((p) => p.hitValue == updated.id);
if (idx >= 0) {
polylineNotes[idx] = updated.toPolyline();
polylineNotes.refresh();
}
case NoteType.polygon:
final idx = polygonNotes.indexWhere((p) => p.hitValue == updated.id);
if (idx >= 0) {
polygonNotes[idx] = updated.toPolygon();
polygonNotes.refresh();
}
case NoteType.point:
final idx = _findPointMarkerIndex(updated.id!);
if (idx >= 0) {
pointNotes[idx] = _markerFromNoteItem(updated);
pointNotes.refresh();
}
}
}
Future<void> deleteNoteItem(NoteItem item) async {
await AppDatabase.instance.deleteNoteItem(item.id!);
switch (item.type) {
case NoteType.line:
polylineNotes.removeWhere((p) => p.hitValue == item.id);
case NoteType.polygon:
polygonNotes.removeWhere((p) => p.hitValue == item.id);
case NoteType.point:
// Pontot tag-elt Key alapján keresünk
final idx = _findPointMarkerIndex(item.id!);
if (idx >= 0) pointNotes.removeAt(idx);
}
selectedNoteItemId.value = null;
}
/// Pont marker indexének megkeresése.
/// A marker Key-je hordozza a NoteItem id-t.
int _findPointMarkerIndex(int noteId) {
return pointNotes.indexWhere(
(m) => m.key == ValueKey('note_point_$noteId'),
);
}
// ── Geometria szerkesztés indítása ────────────────────────────────────
Future<void> startGeometryEdit() async {
final id = _editingNoteItemId;
if (id == null) return;
final item = await AppDatabase.instance.getNoteItem(id);
if (item == null) return;
if (item.type == NoteType.point) {
// Pontnál: régi törlése, új elhelyezés vár
activeEditTool.value = MapEditTool.point;
return;
}
// Editor mód beállítása a típus szerint
final editorMode = item.type == NoteType.line
? PolygonEditorMode.line
: PolygonEditorMode.polygon;
// PolygonEditorController újraindítása a meglévő pontokkal
// Dispose → újra létrehozás szükséges ha a controller már él
polygonEditorController.clear();
polygonEditorController.setMode(editorMode);
// Meglévő pontok betöltése az editorba
for (final point in item.points) {
polygonEditorController.addPoint(point);
}
editorPointCount.value = item.points.length;
// Szerkesztési mód aktiválása
activeEditTool.value =
item.type == NoteType.line ? MapEditTool.line : MapEditTool.polygon;
}
// ── Geometria szerkesztés megszakítása ────────────────────────────────
void cancelGeometryEdit() {
_editingNoteItemId = null;
polygonEditorController.clear();
activeEditTool.value = MapEditTool.none;
editorPointCount.value = 0;
}
Future<void> _finishGeometryUpdate() async {
final id = _editingNoteItemId!;
if (polygonEditorController.points.length < _minPoints) {
Get.snackbar(
'Figyelem',
'Nincs elég pont a geometriához.',
snackPosition: SnackPosition.BOTTOM,
);
return;
}
// Meglévő elem lekérése az adatbázisból
final existing = await AppDatabase.instance.getNoteItem(id);
if (existing == null) return;
// Frissítés: csak a pontok változnak, a stílus marad
final updated = existing.copyWith(
points: List<LatLng>.from(polygonEditorController.points),
// Ha a stílus is változott a szerkesztés közben:
color: activeEditColor.value,
opacity: activeEditOpacity.value,
strokeWidth: activeEditStrokeWidth.value,
strokeColor: activeEditStrokeColor.value,
label: activeEditLabel.value,
);
// SQLite + display lista frissítése
await updateNoteItem(updated);
// Reset
_editingNoteItemId = null;
polygonEditorController.clear();
activeEditTool.value = MapEditTool.none;
editorPointCount.value = 0;
Get.snackbar(
'Geometria frissítve',
updated.label.isNotEmpty ? updated.label : '',
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 2),
);
}
Future<void> _finishCreate() async {
if (polygonEditorController.mode == PolygonEditorMode.line) {
if (polygonEditorController.points.length < 2) return;
final saved = await _saveItem(
type: NoteType.line,
points: List.from(polygonEditorController.points),
);
if (saved != null) {
polylineNotes.add(saved.toPolyline());
}
polygonEditorController.clear();
activeEditTool.value = MapEditTool.none;
}
if (polygonEditorController.mode == PolygonEditorMode.polygon) {
if (polygonEditorController.points.length < 3) return;
final saved = await _saveItem(
type: NoteType.polygon,
points: List.from(polygonEditorController.points),
);
if (saved != null) {
polygonNotes.add(saved.toPolygon());
}
polygonEditorController.clear();
activeEditTool.value = MapEditTool.none;
}
}
int get _minPoints {
if (polygonEditorController.mode == PolygonEditorMode.line) return 2;
return 3; // polygon
}
}