2026-07-07 02:48:37 +02:00
|
|
|
import 'dart:math' as math;
|
|
|
|
|
|
2026-07-06 11:47:41 +02:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:flutter_map/flutter_map.dart';
|
|
|
|
|
import 'package:get/get.dart';
|
|
|
|
|
import 'package:latlong2/latlong.dart';
|
2026-07-07 02:48:37 +02:00
|
|
|
import 'package:terepi_seged/services/coord_converter_service.dart';
|
2026-07-06 11:47:41 +02:00
|
|
|
import 'package:terepi_seged/services/gnss/gnss_service.dart';
|
|
|
|
|
import 'package:terepi_seged/services/stakeout_service.dart';
|
|
|
|
|
|
|
|
|
|
import '../../../../models/stakeout_point.dart';
|
|
|
|
|
|
|
|
|
|
/// Kitűzési térképréteg — a SharedMapWidget children listájába illesztendő.
|
|
|
|
|
///
|
|
|
|
|
/// Csak akkor rajzol, ha a StakeoutService aktív (mode == stakeout), így
|
|
|
|
|
/// feltétel nélkül bent maradhat a rétegek között. Tartalma: vonalanként
|
|
|
|
|
/// polyline (station-sorrendben), státusz-színezett markerek (koppintás =
|
|
|
|
|
/// célváltás), vezetővonal a pozíciótól a célig.
|
|
|
|
|
class StakeoutMapLayers extends StatelessWidget {
|
|
|
|
|
const StakeoutMapLayers({super.key});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
if (!Get.isRegistered<StakeoutService>()) return const SizedBox.shrink();
|
|
|
|
|
|
|
|
|
|
return Obx(() {
|
|
|
|
|
final svc = StakeoutService.to;
|
|
|
|
|
if (!svc.active.value || svc.points.isEmpty) {
|
|
|
|
|
return const SizedBox.shrink();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final points = svc.points;
|
|
|
|
|
final target = svc.target.value;
|
|
|
|
|
final g = GnssService.to;
|
|
|
|
|
final hasPos = svc.hasPosition.value && g.latitude.value != 0;
|
|
|
|
|
|
|
|
|
|
// Vonalanként polyline (station-sorrendben, eltolt pontok nélkül).
|
|
|
|
|
final linePolys = <Polyline>[];
|
|
|
|
|
for (final line in svc.lines) {
|
|
|
|
|
final lp = points.where((p) => p.lineId == line && !p.isOffset).toList()
|
|
|
|
|
..sort((a, b) => a.station.compareTo(b.station));
|
|
|
|
|
if (lp.length < 2) continue;
|
|
|
|
|
linePolys.add(Polyline(
|
|
|
|
|
points: [for (final p in lp) LatLng(p.planLat, p.planLon)],
|
|
|
|
|
color: Colors.blueGrey.withOpacity(0.5),
|
|
|
|
|
strokeWidth: 2,
|
|
|
|
|
));
|
|
|
|
|
}
|
2026-07-07 02:48:37 +02:00
|
|
|
// Transzverzális az aktuális célnál: a vonalra merőleges segédvonal
|
|
|
|
|
// ±30 m hosszan, 10 m-enkénti méter-címkékkel — terepi akadálynál
|
|
|
|
|
// e mentén látszik, hova tolható el a pont.
|
|
|
|
|
final transversal = <LatLng>[];
|
|
|
|
|
final transversalTicks = <Marker>[];
|
|
|
|
|
if (target != null && Get.isRegistered<CoordConverterService>()) {
|
|
|
|
|
final lb = svc.lineBearingAt(target);
|
|
|
|
|
if (lb != null) {
|
|
|
|
|
final perpRad = (lb + 90) * math.pi / 180;
|
|
|
|
|
LatLng at(double d) {
|
|
|
|
|
final y = target.planEovY + d * math.sin(perpRad);
|
|
|
|
|
final x = target.planEovX + d * math.cos(perpRad);
|
|
|
|
|
final w = CoordConverterService.to.eovToWgsPoint(y, x);
|
|
|
|
|
return LatLng(w.y, w.x);
|
|
|
|
|
}
|
2026-07-06 11:47:41 +02:00
|
|
|
|
2026-07-07 02:48:37 +02:00
|
|
|
transversal
|
|
|
|
|
..add(at(-30))
|
|
|
|
|
..add(at(30));
|
|
|
|
|
|
|
|
|
|
for (final d in const [-30.0, -20.0, -10.0, 10.0, 20.0, 30.0]) {
|
|
|
|
|
transversalTicks.add(Marker(
|
|
|
|
|
point: at(d),
|
|
|
|
|
width: 24,
|
|
|
|
|
height: 16,
|
|
|
|
|
child: Container(
|
|
|
|
|
alignment: Alignment.center,
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: Colors.orange.withOpacity(0.85),
|
|
|
|
|
borderRadius: BorderRadius.circular(4),
|
|
|
|
|
),
|
|
|
|
|
child: Text(
|
|
|
|
|
d.abs().toStringAsFixed(0),
|
|
|
|
|
style: const TextStyle(
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
color: Colors.white,
|
|
|
|
|
fontWeight: FontWeight.w700),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-06 11:47:41 +02:00
|
|
|
// 300 pont fölött a címkék csak a célon (teljesítmény).
|
|
|
|
|
final showLabels = points.length <= 300;
|
|
|
|
|
|
|
|
|
|
return Stack(children: [
|
|
|
|
|
PolylineLayer(polylines: [
|
2026-07-07 02:48:37 +02:00
|
|
|
if (transversal.isNotEmpty)
|
|
|
|
|
Polyline(
|
|
|
|
|
points: transversal,
|
|
|
|
|
color: Colors.orange.withValues(alpha: 0.8),
|
|
|
|
|
strokeWidth: 2),
|
2026-07-06 11:47:41 +02:00
|
|
|
...linePolys,
|
|
|
|
|
if (hasPos && target != null)
|
|
|
|
|
Polyline(
|
|
|
|
|
points: [
|
|
|
|
|
LatLng(g.latitude.value, g.longitude.value),
|
|
|
|
|
LatLng(target.planLat, target.planLon),
|
|
|
|
|
],
|
|
|
|
|
color: Colors.blue,
|
|
|
|
|
strokeWidth: 3,
|
|
|
|
|
),
|
|
|
|
|
]),
|
|
|
|
|
MarkerLayer(markers: [
|
2026-07-07 02:48:37 +02:00
|
|
|
...transversalTicks,
|
2026-07-06 11:47:41 +02:00
|
|
|
for (final p in points)
|
|
|
|
|
Marker(
|
|
|
|
|
point: LatLng(p.planLat, p.planLon),
|
|
|
|
|
width: 56,
|
|
|
|
|
height: 40,
|
|
|
|
|
child: GestureDetector(
|
|
|
|
|
onTap: () => svc.setTarget(p),
|
|
|
|
|
child: _StakeoutMarker(
|
|
|
|
|
point: p,
|
|
|
|
|
isTarget: p.uuid == target?.uuid,
|
|
|
|
|
showLabel: showLabels || p.uuid == target?.uuid,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
]),
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class _StakeoutMarker extends StatelessWidget {
|
|
|
|
|
final StakeoutPoint point;
|
|
|
|
|
final bool isTarget;
|
|
|
|
|
final bool showLabel;
|
|
|
|
|
const _StakeoutMarker(
|
|
|
|
|
{required this.point, required this.isTarget, required this.showLabel});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
final (color, icon) = switch (point.status) {
|
|
|
|
|
StakeoutStatus.staked => (Colors.green, Icons.check_circle),
|
|
|
|
|
StakeoutStatus.skipped => (Colors.grey, Icons.block),
|
|
|
|
|
StakeoutStatus.pending => point.isOffset
|
|
|
|
|
? (Colors.purple, Icons.change_history)
|
|
|
|
|
: (Colors.deepOrange, Icons.change_history),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return Column(
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
children: [
|
|
|
|
|
Icon(icon,
|
|
|
|
|
size: isTarget ? 26 : 16, color: isTarget ? Colors.red : color),
|
|
|
|
|
if (showLabel)
|
|
|
|
|
Container(
|
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 3),
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: Colors.white.withOpacity(0.85),
|
|
|
|
|
borderRadius: BorderRadius.circular(4),
|
|
|
|
|
),
|
|
|
|
|
child: Text(
|
|
|
|
|
point.name,
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
fontWeight: isTarget ? FontWeight.w700 : FontWeight.w400,
|
|
|
|
|
color: isTarget ? Colors.red : Colors.black87,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|