200 lines
7.0 KiB
Dart
200 lines
7.0 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:latlong2/latlong.dart';
|
||
import 'package:terepi_seged/services/coord_converter_service.dart';
|
||
import 'package:get/get.dart';
|
||
|
||
/// Egy navigációs útvonal (kerülő, megközelítés…) a
|
||
/// `terepi_seged_vibro_nav_routes` táblából.
|
||
///
|
||
/// A `geometry` az EREDETI GeoJSON (wgs84 vagy eov), a `lines` pedig a
|
||
/// BETÖLTÉSKOR egyszer, WGS84-re átváltott, kirajzolásra kész vonalak —
|
||
/// a térkép így nem számol újra GNSS-frissítéskor.
|
||
class VibroNavRoute {
|
||
final String id;
|
||
final String projectId;
|
||
final String name;
|
||
final Map<String, dynamic> geometry;
|
||
final String crs; // 'auto' | 'wgs84' | 'eov'
|
||
final DateTime? validDate;
|
||
final String? vehicle;
|
||
final String routeKind; // 'detour' | 'access' | 'restricted'
|
||
final String? color;
|
||
final bool isActive;
|
||
final List<List<LatLng>> lines;
|
||
|
||
const VibroNavRoute({
|
||
required this.id,
|
||
required this.projectId,
|
||
required this.name,
|
||
required this.geometry,
|
||
required this.crs,
|
||
required this.validDate,
|
||
required this.vehicle,
|
||
required this.routeKind,
|
||
required this.color,
|
||
required this.isActive,
|
||
required this.lines,
|
||
});
|
||
|
||
factory VibroNavRoute.fromMap(Map<String, dynamic> m) {
|
||
final geometry = Map<String, dynamic>.from(m['geometry'] as Map);
|
||
final crs = (m['crs'] as String?) ?? 'auto';
|
||
final dateStr = m['valid_date'] as String?;
|
||
return VibroNavRoute(
|
||
id: m['id'] as String,
|
||
projectId: m['project_id'] as String,
|
||
name: (m['name'] as String?) ?? '',
|
||
geometry: geometry,
|
||
crs: crs,
|
||
validDate: dateStr == null ? null : DateTime.parse(dateStr),
|
||
vehicle: (m['vehicle'] as String?)?.trim().isEmpty ?? true
|
||
? null
|
||
: (m['vehicle'] as String).trim(),
|
||
routeKind: (m['route_kind'] as String?) ?? 'detour',
|
||
color: m['color'] as String?,
|
||
isActive: (m['is_active'] as bool?) ?? true,
|
||
lines: toWgsLines(geometry, crs),
|
||
);
|
||
}
|
||
|
||
int get pointCount => lines.fold(0, (s, l) => s + l.length);
|
||
|
||
String get dateLabel => validDate == null
|
||
? 'minden nap'
|
||
: '${validDate!.year}-${validDate!.month.toString().padLeft(2, '0')}-'
|
||
'${validDate!.day.toString().padLeft(2, '0')}';
|
||
|
||
String get vehicleLabel => vehicle ?? 'minden jármű';
|
||
|
||
// ═════════════════════════════════════════════════════════════════
|
||
// Fajták / színek
|
||
// ═════════════════════════════════════════════════════════════════
|
||
|
||
static const kinds = <String, String>{
|
||
'detour': 'Kerülő',
|
||
'access': 'Megközelítés',
|
||
'restricted': 'Tiltott / veszélyes',
|
||
};
|
||
|
||
static Color colorFor(String kind, String? hex) {
|
||
final custom = _parseHex(hex);
|
||
if (custom != null) return custom;
|
||
switch (kind) {
|
||
case 'access':
|
||
return const Color(0xFF2979FF);
|
||
case 'restricted':
|
||
return const Color(0xFFD50000);
|
||
case 'detour':
|
||
default:
|
||
return const Color(0xFFFFAB00);
|
||
}
|
||
}
|
||
|
||
static Color? _parseHex(String? hex) {
|
||
if (hex == null) return null;
|
||
var h = hex.trim().replaceFirst('#', '');
|
||
if (h.length == 6) h = 'FF$h';
|
||
if (h.length != 8) return null;
|
||
final v = int.tryParse(h, radix: 16);
|
||
return v == null ? null : Color(v);
|
||
}
|
||
|
||
// ═════════════════════════════════════════════════════════════════
|
||
// Geometria → WGS84 vonalak
|
||
// ═════════════════════════════════════════════════════════════════
|
||
|
||
/// Megállapítja a koordinátarendszert az ELSŐ koordináta alapján
|
||
/// (egy geometrián belül egységes vetületet feltételezünk).
|
||
/// WGS84: |x| ≤ 180 és |y| ≤ 90. EOV: több tízezres–százezres értékek.
|
||
static String detectCrs(Map<String, dynamic> geometry) {
|
||
final first = _firstCoordinate(geometry);
|
||
if (first == null) return 'wgs84';
|
||
return (first[0].abs() <= 180 && first[1].abs() <= 90) ? 'wgs84' : 'eov';
|
||
}
|
||
|
||
static List<double>? _firstCoordinate(Map<String, dynamic> geometry) {
|
||
final lines = _rawLines(geometry);
|
||
for (final l in lines) {
|
||
if (l.isNotEmpty) return l.first;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// A GeoJSON geometria nyers vonalai ([x, y] párok listájának listája).
|
||
static List<List<List<double>>> _rawLines(Map<String, dynamic> geometry) {
|
||
final type = geometry['type'];
|
||
final coords = geometry['coordinates'];
|
||
if (coords is! List) return const [];
|
||
|
||
List<List<double>> parseLine(dynamic raw) {
|
||
final out = <List<double>>[];
|
||
if (raw is! List) return out;
|
||
for (final c in raw) {
|
||
if (c is List && c.length >= 2 && c[0] is num && c[1] is num) {
|
||
out.add([(c[0] as num).toDouble(), (c[1] as num).toDouble()]);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
if (type == 'LineString') return [parseLine(coords)];
|
||
if (type == 'MultiLineString') return coords.map(parseLine).toList();
|
||
return const [];
|
||
}
|
||
|
||
/// WGS84 [LatLng] vonalak. A nem véges vagy érvénytelen pontokat
|
||
/// eldobja (a LatLng NaN összeomlasztja a térképet), a 2 pontnál
|
||
/// rövidebb vonalakat kihagyja.
|
||
static List<List<LatLng>> toWgsLines(
|
||
Map<String, dynamic> geometry, String crs) {
|
||
final raw = _rawLines(geometry);
|
||
if (raw.isEmpty) return const [];
|
||
|
||
final effective = crs == 'auto' ? detectCrs(geometry) : crs;
|
||
final out = <List<LatLng>>[];
|
||
|
||
for (final line in raw) {
|
||
final pts = <LatLng>[];
|
||
for (final c in line) {
|
||
final ll = effective == 'eov' ? _eovToLatLng(c[0], c[1]) : _wgs(c);
|
||
if (ll != null) pts.add(ll);
|
||
}
|
||
if (pts.length >= 2) out.add(pts);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
static LatLng? _wgs(List<double> c) {
|
||
var lon = c[0];
|
||
var lat = c[1];
|
||
// Gyakori hiba: felcserélt sorrend [szélesség, hosszúság]. Magyarország
|
||
// területén a szélesség 45–49, a hosszúság 16–23, ezért egyértelmű.
|
||
if (lon > 44 && lon < 50 && lat > 15 && lat < 24) {
|
||
final t = lon;
|
||
lon = lat;
|
||
lat = t;
|
||
}
|
||
if (!lon.isFinite || !lat.isFinite) return null;
|
||
if (lon.abs() > 180 || lat.abs() > 90) return null;
|
||
return LatLng(lat, lon);
|
||
}
|
||
|
||
static LatLng? _eovToLatLng(double a, double b) {
|
||
if (!Get.isRegistered<CoordConverterService>()) return null;
|
||
// Szabvány: [EOV Y (kelet ~400k–950k), EOV X (észak ~45k–350k)].
|
||
// Ha felcserélve érkezett, javítjuk.
|
||
var y = a;
|
||
var x = b;
|
||
if (y < 400000 && x > 400000) {
|
||
final t = y;
|
||
y = x;
|
||
x = t;
|
||
}
|
||
try {
|
||
final w = CoordConverterService.to.eovToWgsPoint(y, x);
|
||
if (w.x.isFinite && w.y.isFinite) return LatLng(w.y, w.x);
|
||
} catch (_) {}
|
||
return null;
|
||
}
|
||
}
|