Távolság vagy terület mérése a térképen terepbejárás nézetben

This commit is contained in:
2026-07-05 01:41:15 +02:00
parent 2dc7768f5f
commit 80f7c8d571
4 changed files with 369 additions and 0 deletions
@@ -0,0 +1,245 @@
// 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 = <Marker>[];
final polylines = <Polyline>[];
// ── 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<LatLng> 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<LatLng> 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<LatLng> 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<LatLng> 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)}';
if (m2 < 1000000) return '${(m2 / 10000).toStringAsFixed(2)} ha';
return '${(m2 / 1000000).toStringAsFixed(3)} km²';
}
String _totalDistLabel(List<LatLng> pts) => '${_fmtDist(_totalDist(pts))}';
String _areaLabel(List<LatLng> 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,
),
);
}
}