import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:em2rp/models/route_result_model.dart'; /// Affiche 1 ou 2 itinéraires sur une carte OpenStreetMap. /// Route TOLL = bleu, Route TOLL_FREE = vert. class RouteMapWidget extends StatelessWidget { final List routes; final RouteResult? selectedRoute; const RouteMapWidget({ super.key, required this.routes, this.selectedRoute, }); /// Décode une polyline Google encodée en liste de LatLng. List _decode(String encoded) { if (encoded.isEmpty) return []; try { final result = []; int index = 0, lat = 0, lng = 0; final len = encoded.length; while (index < len) { int shift = 0, result0 = 0; int b; do { b = encoded.codeUnitAt(index++) - 63; result0 |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); final dlat = (result0 & 1) != 0 ? ~(result0 >> 1) : (result0 >> 1); lat += dlat; shift = 0; result0 = 0; do { b = encoded.codeUnitAt(index++) - 63; result0 |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); final dlng = (result0 & 1) != 0 ? ~(result0 >> 1) : (result0 >> 1); lng += dlng; result.add(LatLng(lat / 1e5, lng / 1e5)); } return result; } catch (e) { return []; } } LatLngBounds? _computeBounds(List> allPoints) { double? minLat, maxLat, minLng, maxLng; for (final pts in allPoints) { for (final p in pts) { minLat = minLat == null ? p.latitude : p.latitude < minLat ? p.latitude : minLat; maxLat = maxLat == null ? p.latitude : p.latitude > maxLat ? p.latitude : maxLat; minLng = minLng == null ? p.longitude : p.longitude < minLng ? p.longitude : minLng; maxLng = maxLng == null ? p.longitude : p.longitude > maxLng ? p.longitude : maxLng; } } if (minLat == null) return null; return LatLngBounds( LatLng(minLat - 0.02, minLng! - 0.02), LatLng(maxLat! + 0.02, maxLng! + 0.02), ); } @override Widget build(BuildContext context) { final allPolylines = []; final allPointGroups = >[]; for (final route in routes) { final pts = _decode(route.encodedPolyline); if (pts.isEmpty) continue; allPointGroups.add(pts); final isSelected = selectedRoute == null || selectedRoute!.routeType == route.routeType; final isToll = route.routeType == 'TOLL'; allPolylines.add(Polyline( points: pts, strokeWidth: isSelected ? 5.0 : 3.0, color: isToll ? (isSelected ? const Color(0xFF1565C0) : const Color(0xFF1565C0).withValues(alpha: 0.4)) : (isSelected ? const Color(0xFF2E7D32) : const Color(0xFF2E7D32).withValues(alpha: 0.4)), )); } final bounds = _computeBounds(allPointGroups); final mapController = MapController(); // Marqueurs de départ / arrivée final markers = []; for (final group in allPointGroups) { if (group.isEmpty) continue; // Départ markers.add(Marker( point: group.first, width: 32, height: 32, child: const Icon(Icons.circle, color: Colors.green, size: 20), )); // Arrivée markers.add(Marker( point: group.last, width: 32, height: 32, child: const Icon(Icons.location_pin, color: Colors.red, size: 32), )); } return FlutterMap( mapController: mapController, options: MapOptions( initialCameraFit: bounds != null ? CameraFit.bounds( bounds: bounds, padding: const EdgeInsets.all(32), ) : const CameraFit.coordinates( coordinates: [LatLng(46.2276, 2.2137)], padding: EdgeInsets.all(32)), interactionOptions: const InteractionOptions( flags: InteractiveFlag.all, ), ), children: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.em2events.em2rp', ), PolylineLayer(polylines: allPolylines), MarkerLayer(markers: markers), ], ); } }