// lib/widgets/map/measure_layer.dart // // Távolság és terület mérés — rögzítés nélkül, csak memóriában // // - Pontok: koppintásra kerülnek a térképre // - Vonalak: azonnal megjelennek a szakaszok // - Hossz: minden szakasz felezőpontján buborékban // - Terület: centroidon m² / ha (+ kerület zárójelben) import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:get/get.dart'; import 'package:latlong2/latlong.dart'; import 'package:terepi_seged/enums/map_measure_type.dart'; import '../../pages/map_survey/presentations/controllers/map_survey_controller.dart'; class DistanceOrAreaMeasureLayer extends StatelessWidget { final MapSurveyController controller; const DistanceOrAreaMeasureLayer({super.key, required this.controller}); @override Widget build(BuildContext context) { return Obx(() { final type = controller.mapMeasureType.value; final points = controller.distanceOrAreaMeasurePoints.toList(); if (type == MapMeasureType.none || points.isEmpty) { return const SizedBox.shrink(); } final markers = []; final polylines = []; // ── Vonalak ─────────────────────────────────────────────────── if (points.length >= 2) { // Fővonal polylines.add(Polyline( points: points, color: Colors.deepOrange, strokeWidth: 2.5, borderColor: Colors.white.withOpacity(0.6), borderStrokeWidth: 1.0, )); // Szakaszhosszak a felezőpontokon for (int i = 0; i < points.length - 1; i++) { final mid = _midpoint(points[i], points[i + 1]); final dist = _haversine(points[i], points[i + 1]); markers.add(Marker( point: mid, width: 80, height: 22, alignment: Alignment.center, child: _LabelBubble( text: _fmtDist(dist), color: Colors.deepOrange, ), )); } } // ── Záró szár (terület módban: vissza az első ponthoz) ──────── if (type == MapMeasureType.area && points.length >= 3) { polylines.add(Polyline( points: [points.last, points.first], color: Colors.deepOrange.withOpacity(0.5), strokeWidth: 2.0, //isDotted: true, )); } // ── Összesítő buborék ───────────────────────────────────────── if (points.length >= 2) { final anchor = type == MapMeasureType.area && points.length >= 3 ? _centroid(points) : points.last; final label = type == MapMeasureType.distance ? _totalDistLabel(points) : _areaLabel(points); markers.add(Marker( point: anchor, width: 140, height: 36, alignment: type == MapMeasureType.area ? Alignment.center : Alignment.topCenter, child: _LabelBubble( text: label, color: Colors.indigo, large: true, ), )); } // ── Mérési pontok ───────────────────────────────────────────── for (int i = 0; i < points.length; i++) { final isLast = i == points.length - 1; final isFirst = i == 0; markers.add(Marker( point: points[i], width: 20, height: 20, alignment: Alignment.center, child: Container( decoration: BoxDecoration( color: isFirst ? Colors.green : isLast ? Colors.deepOrange : Colors.white, shape: BoxShape.circle, border: Border.all(color: Colors.deepOrange, width: 2), boxShadow: const [ BoxShadow(color: Colors.black26, blurRadius: 3), ], ), ), )); } return Stack(children: [ if (polylines.isNotEmpty) PolylineLayer(polylines: polylines), if (markers.isNotEmpty) MarkerLayer(markers: markers), ]); }); } // ── Számítások ──────────────────────────────────────────────────── static const _distCalc = Distance(); double _haversine(LatLng a, LatLng b) => _distCalc.as(LengthUnit.Meter, a, b); LatLng _midpoint(LatLng a, LatLng b) => LatLng( (a.latitude + b.latitude) / 2, (a.longitude + b.longitude) / 2, ); LatLng _centroid(List pts) => LatLng( pts.map((p) => p.latitude).reduce((a, b) => a + b) / pts.length, pts.map((p) => p.longitude).reduce((a, b) => a + b) / pts.length, ); double _totalDist(List pts) { double total = 0; for (int i = 0; i < pts.length - 1; i++) { total += _haversine(pts[i], pts[i + 1]); } return total; } /// Gömbháromszög módszer — pontos terület m²-ben double _area(List pts) { if (pts.length < 3) return 0; const r = 6371000.0; double area = 0; final n = pts.length; for (int i = 0; i < n; i++) { final j = (i + 1) % n; final xi = pts[i].longitude * pi / 180; final xj = pts[j].longitude * pi / 180; final yi = pts[i].latitude * pi / 180; final yj = pts[j].latitude * pi / 180; area += (xj - xi) * (2 + sin(yi) + sin(yj)); } return (area.abs() * r * r / 2); } double _perimeter(List pts) { double p = _totalDist(pts); if (pts.length >= 3) p += _haversine(pts.last, pts.first); return p; } // ── Formázás ────────────────────────────────────────────────────── String _fmtDist(double m) { if (m < 1000) return '${m.toStringAsFixed(0)} m'; return '${(m / 1000).toStringAsFixed(2)} km'; } String _fmtArea(double m2) { if (m2 < 10000) return '${m2.toStringAsFixed(0)} m²'; if (m2 < 1000000) return '${(m2 / 10000).toStringAsFixed(2)} ha'; return '${(m2 / 1000000).toStringAsFixed(3)} km²'; } String _totalDistLabel(List pts) => '⟷ ${_fmtDist(_totalDist(pts))}'; String _areaLabel(List pts) { final a = _area(pts); final p = _perimeter(pts); return '${_fmtArea(a)}\n(${_fmtDist(p)})'; } } // ─── Felirat buborék ────────────────────────────────────────────────────────── class _LabelBubble extends StatelessWidget { final String text; final Color color; final bool large; const _LabelBubble({ required this.text, required this.color, this.large = false, }); @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.symmetric( horizontal: large ? 8 : 5, vertical: large ? 4 : 2), decoration: BoxDecoration( color: Colors.white.withOpacity(0.93), borderRadius: BorderRadius.circular(6), border: Border(left: BorderSide(color: color, width: 3)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.15), blurRadius: 4, offset: const Offset(0, 1), ), ], ), child: Text( text, style: TextStyle( fontSize: large ? 12 : 10, fontWeight: FontWeight.w700, color: color, height: 1.2, fontFeatures: const [FontFeature.tabularFigures()], ), textAlign: TextAlign.center, maxLines: 2, ), ); } }