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 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> 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 m) { final geometry = Map.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 = { '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 geometry) { final first = _firstCoordinate(geometry); if (first == null) return 'wgs84'; return (first[0].abs() <= 180 && first[1].abs() <= 90) ? 'wgs84' : 'eov'; } static List? _firstCoordinate(Map 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>> _rawLines(Map geometry) { final type = geometry['type']; final coords = geometry['coordinates']; if (coords is! List) return const []; List> parseLine(dynamic raw) { final out = >[]; 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> toWgsLines( Map geometry, String crs) { final raw = _rawLines(geometry); if (raw.isEmpty) return const []; final effective = crs == 'auto' ? detectCrs(geometry) : crs; final out = >[]; for (final line in raw) { final pts = []; 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 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()) 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; } }