Térképi rétegkezelő lérehozása, track online mentésének előkészítése

This commit is contained in:
torok.istvan 2026-06-12 13:41:36 +02:00
parent 644201dc8a
commit beb07fe007
10 changed files with 169 additions and 55 deletions

View File

@ -0,0 +1 @@
enum LayerType { geojson, shapefile, dxf, csv, postgis, wms, track, gnss }

View File

@ -0,0 +1 @@
enum LayerVisibility { visible, hidden }

View File

@ -0,0 +1,11 @@
// import 'package:flutter/material.dart';
// import 'package:terepi_seged/map_layers/map_layer.dart';
// import 'map_geojson_layer.dart';
// class DxfLayer extends MapLayer {
// // DXF parsing nincs Flutter csomag, szerver oldali konverzió kell
// // Supabase Edge Function: DXF GeoJSON (ogr2ogr alapú)
// @override
// Widget buildFlutterMapLayer() => GeoJsonLayer.fromFeatures(_features);
// }

View File

@ -0,0 +1,17 @@
// import 'package:flutter/material.dart';
// import 'package:flutter_map/flutter_map.dart';
// import 'map_layer.dart';
// class GeoJsonLayer extends MapLayer {
// final String sourceUrl; // Supabase Storage URL
// final String? localPath; // offline cache
// FeatureCollection? _data;
// @override
// Widget buildFlutterMapLayer() {
// if (_data == null) return const SizedBox.shrink();
// return PolygonLayer(polygons: _buildPolygons());
// // vagy PolylineLayer / MarkerLayer a geometry type-tól függően
// }
// }

View File

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:terepi_seged/enums/map_layer_type.dart';
import 'package:terepi_seged/enums/map_layer_visibility.dart';
abstract class MapLayer {
late final String id;
late final String name;
late final LayerType type;
late LayerVisibility visibility;
late double opacity;
late int zIndex;
//LayerStyle style;
// Minden réteg tudja magát flutter_map widget- alakítani
Widget buildFlutterMapLayer();
// Offline cache-elhetőség
Future<void> prefetch();
bool get isCached;
}

View File

@ -0,0 +1,23 @@
// import 'package:flutter/material.dart';
// import 'package:flutter_map/flutter_map.dart';
// import 'package:supabase_flutter/supabase_flutter.dart';
// import 'map_layer.dart';
// class PostGisLayer extends MapLayer {
// final String table; // Supabase tábla neve
// final String geomColumn; // geometry oszlop
// final String? filter; // pl. "project_id=eq.123"
// final BoundingBox? bbox; // csak látható terület lekérése
// @override
// Widget buildFlutterMapLayer() {
// // flutter_map_tile_caching + PostGIS tile endpoint
// return TileLayer(urlTemplate: _buildMvtUrl());
// }
// // MapBox Vector Tiles (MVT) Supabase Edge Function-ből
// String _buildMvtUrl() =>
// '${Supabase.instance.client.supabaseUrl}/functions/v1/tiles'
// '/$table/{z}/{x}/{y}?geom=$geomColumn${filter != null ? "&filter=$filter" : ""}';
// }

View File

@ -0,0 +1,11 @@
// import 'package:flutter/material.dart';
// import 'map_geojson_layer.dart';
// import 'map_layer.dart';
// class ShapefileLayer extends MapLayer {
// // SHP GeoJSON konverzió device-on
// // shapefile_dart csomag
// @override
// Widget buildFlutterMapLayer() => GeoJsonLayer.fromFeatures(_features);
// }

View File

@ -54,6 +54,9 @@ class TrackPoint {
enum TrackStatus { recording, paused, finished } enum TrackStatus { recording, paused, finished }
enum TrackSyncMode { localOnly, online }
enum TrackSyncStatus { pending, synced, error }
// Track // Track
class Track { class Track {
@ -68,9 +71,12 @@ class Track {
// Statisztikák ezeket a DB is tárolja a gyors listázáshoz // Statisztikák ezeket a DB is tárolja a gyors listázáshoz
final double distanceMeters; final double distanceMeters;
final int pointCount; final int pointCount;
final TrackSyncMode syncMode;
final TrackSyncStatus syncStatus;
final String? supabaseId;
const Track({ const Track(
this.id, {this.id,
this.projectId, this.projectId,
required this.name, required this.name,
required this.startTime, required this.startTime,
@ -79,10 +85,12 @@ class Track {
this.source = 'Telefon GPS', this.source = 'Telefon GPS',
this.distanceMeters = 0, this.distanceMeters = 0,
this.pointCount = 0, this.pointCount = 0,
}); this.syncMode = TrackSyncMode.online,
this.syncStatus = TrackSyncStatus.pending,
this.supabaseId});
Track copyWith({ Track copyWith(
int? id, {int? id,
int? projectId, int? projectId,
String? name, String? name,
DateTime? startTime, DateTime? startTime,
@ -91,7 +99,9 @@ class Track {
String? source, String? source,
double? distanceMeters, double? distanceMeters,
int? pointCount, int? pointCount,
}) => TrackSyncMode? syncMode,
TrackSyncStatus? syncStatus,
String? supabaseId}) =>
Track( Track(
id: id ?? this.id, id: id ?? this.id,
projectId: projectId ?? this.projectId, projectId: projectId ?? this.projectId,
@ -102,7 +112,9 @@ class Track {
source: source ?? this.source, source: source ?? this.source,
distanceMeters: distanceMeters ?? this.distanceMeters, distanceMeters: distanceMeters ?? this.distanceMeters,
pointCount: pointCount ?? this.pointCount, pointCount: pointCount ?? this.pointCount,
); syncMode: syncMode ?? this.syncMode,
syncStatus: syncStatus ?? this.syncStatus,
supabaseId: supabaseId ?? this.supabaseId);
/// Formázott időtartam (óó:pp:mm) /// Formázott időtartam (óó:pp:mm)
String get durationFormatted { String get durationFormatted {
@ -132,6 +144,9 @@ class Track {
'source': source, 'source': source,
'distance_m': distanceMeters, 'distance_m': distanceMeters,
'point_count': pointCount, 'point_count': pointCount,
'is_local_only': syncMode == TrackSyncMode.localOnly ? 1 : 0,
'sync_status': syncStatus.name,
'supabase_id': supabaseId,
}; };
factory Track.fromMap(Map<String, dynamic> m) => Track( factory Track.fromMap(Map<String, dynamic> m) => Track(
@ -149,7 +164,14 @@ class Track {
source: m['source'] as String? ?? 'Telefon GPS', source: m['source'] as String? ?? 'Telefon GPS',
distanceMeters: (m['distance_m'] as num?)?.toDouble() ?? 0, distanceMeters: (m['distance_m'] as num?)?.toDouble() ?? 0,
pointCount: m['point_count'] as int? ?? 0, pointCount: m['point_count'] as int? ?? 0,
); syncMode: (m['is_local_only'] as int?) == 1
? TrackSyncMode.localOnly
: TrackSyncMode.online,
syncStatus: TrackSyncStatus.values
.byName(m['sync_status'] as String? ?? 'pending'),
supabaseId: m['supabase_id'] as String?);
bool get isLocalOnly => syncMode == TrackSyncMode.localOnly;
} }
// Segédszámítás: Haversine távolság // Segédszámítás: Haversine távolság

View File

@ -5,6 +5,7 @@ import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:terepi_seged/services/project_service.dart';
import '../../../../services/location_source.dart'; import '../../../../services/location_source.dart';
import '../../../../services/phone_gps_source.dart'; import '../../../../services/phone_gps_source.dart';
@ -116,19 +117,23 @@ class TrackingController extends GetxController {
// Publikus API // Publikus API
/// Elindítja a rögzítést a megadott forrással (alapértelmezett: telefon GPS). /// Elindítja a rögzítést a megadott forrással (alapértelmezett: telefon GPS).
Future<void> startRecording({LocationSource? source}) async { Future<void> startRecording(
{LocationSource? source,
String? name,
TrackSyncMode syncMode = TrackSyncMode.online}) async {
if (isRecording.value) return; if (isRecording.value) return;
_source = source ?? PhoneGpsSource(intervalMs: 1000, distanceFilter: 2.0); _source = source ?? PhoneGpsSource(intervalMs: 1000, distanceFilter: 2.0);
// Track létrehozása az adatbázisban // Track létrehozása az adatbázisban
final now = DateTime.now(); final now = DateTime.now();
final name = DateFormat('yyyy-MM-dd HH:mm').format(now); final trackName = name ?? DateFormat('yyyy-MM-dd HH:mm').format(now);
final trackId = await _db.insertTrack(Track( final trackId = await _db.insertTrack(Track(
name: name, name: trackName,
startTime: now, startTime: now,
source: _source!.displayName, source: _source!.displayName,
)); projectId: ProjectService.to.activeProjectId,
syncMode: syncMode));
currentTrack.value = await _db.getTrack(trackId); currentTrack.value = await _db.getTrack(trackId);
// Állapot reset // Állapot reset
@ -141,7 +146,7 @@ class TrackingController extends GetxController {
// Foreground service indítása (háttér-működéshez) // Foreground service indítása (háttér-működéshez)
await FlutterForegroundTask.startService( await FlutterForegroundTask.startService(
notificationTitle: 'Track rögzítése', notificationTitle: 'Track rögzítése',
notificationText: name, notificationText: trackName,
callback: startTrackingCallback, callback: startTrackingCallback,
); );

View File

@ -87,7 +87,10 @@ class AppDatabase {
status TEXT NOT NULL DEFAULT 'recording', status TEXT NOT NULL DEFAULT 'recording',
source TEXT NOT NULL DEFAULT 'Telefon GPS', source TEXT NOT NULL DEFAULT 'Telefon GPS',
distance_m REAL NOT NULL DEFAULT 0, distance_m REAL NOT NULL DEFAULT 0,
point_count INTEGER NOT NULL DEFAULT 0 point_count INTEGER NOT NULL DEFAULT 0,
is_local_only INTEGER NOT NULL DEFAULT 0,
sync_status TEXT NOT NULL DEFAULT 'pending',
supabase_id TEXT
) )
'''); ''');
await db.execute('CREATE INDEX idx_tracks_project ON tracks(project_id)'); await db.execute('CREATE INDEX idx_tracks_project ON tracks(project_id)');