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 TrackSyncMode { localOnly, online }
enum TrackSyncStatus { pending, synced, error }
// Track
class Track {
@ -68,41 +71,50 @@ class Track {
// Statisztikák ezeket a DB is tárolja a gyors listázáshoz
final double distanceMeters;
final int pointCount;
final TrackSyncMode syncMode;
final TrackSyncStatus syncStatus;
final String? supabaseId;
const Track({
this.id,
this.projectId,
required this.name,
required this.startTime,
this.endTime,
this.status = TrackStatus.recording,
this.source = 'Telefon GPS',
this.distanceMeters = 0,
this.pointCount = 0,
});
const Track(
{this.id,
this.projectId,
required this.name,
required this.startTime,
this.endTime,
this.status = TrackStatus.recording,
this.source = 'Telefon GPS',
this.distanceMeters = 0,
this.pointCount = 0,
this.syncMode = TrackSyncMode.online,
this.syncStatus = TrackSyncStatus.pending,
this.supabaseId});
Track copyWith({
int? id,
int? projectId,
String? name,
DateTime? startTime,
DateTime? endTime,
TrackStatus? status,
String? source,
double? distanceMeters,
int? pointCount,
}) =>
Track copyWith(
{int? id,
int? projectId,
String? name,
DateTime? startTime,
DateTime? endTime,
TrackStatus? status,
String? source,
double? distanceMeters,
int? pointCount,
TrackSyncMode? syncMode,
TrackSyncStatus? syncStatus,
String? supabaseId}) =>
Track(
id: id ?? this.id,
projectId: projectId ?? this.projectId,
name: name ?? this.name,
startTime: startTime ?? this.startTime,
endTime: endTime ?? this.endTime,
status: status ?? this.status,
source: source ?? this.source,
distanceMeters: distanceMeters ?? this.distanceMeters,
pointCount: pointCount ?? this.pointCount,
);
id: id ?? this.id,
projectId: projectId ?? this.projectId,
name: name ?? this.name,
startTime: startTime ?? this.startTime,
endTime: endTime ?? this.endTime,
status: status ?? this.status,
source: source ?? this.source,
distanceMeters: distanceMeters ?? this.distanceMeters,
pointCount: pointCount ?? this.pointCount,
syncMode: syncMode ?? this.syncMode,
syncStatus: syncStatus ?? this.syncStatus,
supabaseId: supabaseId ?? this.supabaseId);
/// Formázott időtartam (óó:pp:mm)
String get durationFormatted {
@ -132,24 +144,34 @@ class Track {
'source': source,
'distance_m': distanceMeters,
'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(
id: m['id'] as int?,
projectId: m['project_id'] as int?,
name: m['name'] as String,
startTime: DateTime.parse(m['start_time'] as String),
endTime: m['end_time'] != null
? DateTime.parse(m['end_time'] as String)
: null,
status: TrackStatus.values.firstWhere(
(s) => s.name == (m['status'] as String?),
orElse: () => TrackStatus.finished,
),
source: m['source'] as String? ?? 'Telefon GPS',
distanceMeters: (m['distance_m'] as num?)?.toDouble() ?? 0,
pointCount: m['point_count'] as int? ?? 0,
);
id: m['id'] as int?,
projectId: m['project_id'] as int?,
name: m['name'] as String,
startTime: DateTime.parse(m['start_time'] as String),
endTime: m['end_time'] != null
? DateTime.parse(m['end_time'] as String)
: null,
status: TrackStatus.values.firstWhere(
(s) => s.name == (m['status'] as String?),
orElse: () => TrackStatus.finished,
),
source: m['source'] as String? ?? 'Telefon GPS',
distanceMeters: (m['distance_m'] as num?)?.toDouble() ?? 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

View File

@ -5,6 +5,7 @@ import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:latlong2/latlong.dart';
import 'package:share_plus/share_plus.dart';
import 'package:terepi_seged/services/project_service.dart';
import '../../../../services/location_source.dart';
import '../../../../services/phone_gps_source.dart';
@ -116,19 +117,23 @@ class TrackingController extends GetxController {
// Publikus API
/// 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;
_source = source ?? PhoneGpsSource(intervalMs: 1000, distanceFilter: 2.0);
// Track létrehozása az adatbázisban
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(
name: name,
startTime: now,
source: _source!.displayName,
));
name: trackName,
startTime: now,
source: _source!.displayName,
projectId: ProjectService.to.activeProjectId,
syncMode: syncMode));
currentTrack.value = await _db.getTrack(trackId);
// Állapot reset
@ -141,7 +146,7 @@ class TrackingController extends GetxController {
// Foreground service indítása (háttér-működéshez)
await FlutterForegroundTask.startService(
notificationTitle: 'Track rögzítése',
notificationText: name,
notificationText: trackName,
callback: startTrackingCallback,
);

View File

@ -87,7 +87,10 @@ class AppDatabase {
status TEXT NOT NULL DEFAULT 'recording',
source TEXT NOT NULL DEFAULT 'Telefon GPS',
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)');