import 'dart:async'; import 'dart:convert'; import 'dart:math'; import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:http/http.dart' as http; class ChatNavigationCard extends StatefulWidget { final String content; final Map? data; final bool isDarkMode; const ChatNavigationCard(this.data, this.content, this.isDarkMode, {super.key}); @override State createState() => _ChatNavigationCardState(); } class _ChatNavigationCardState extends State { final String apiKey = '8bde023b7e5e00f321ea17929f10fbae'; LatLng startPoint = LatLng(116.397428, 39.90923); // 起点坐标 LatLng endPoint = LatLng(116.397026, 39.903303); // 终点坐标 late Future _mapUrl; @override void initState() { super.initState(); _getLocation(); } void _getLocation() { List startPointList = widget.data?['location'].split(','); List endPointList = widget.data?['destination'].split(','); startPoint = LatLng( double.parse(startPointList[0]), double.parse(startPointList[1]), ); endPoint = LatLng( double.parse(endPointList[0]), double.parse(endPointList[1]), ); _mapUrl = _getMapUrl(); } Future _getMapUrl() async { // 1. 获取路径数据 final route = await _fetchRouteData(); // 2. 解析并简化路径 List simplified = _simplifyPath(route, epsilon: 0.0001); // 3. 计算地图参数 final zoom = _calculateAutoZoom(simplified); // 4. 生成静态地图URL return _buildStaticMapUrl(simplified, zoom); } Future> _fetchRouteData() async { final url = 'https://restapi.amap.com/v3/direction/driving?' 'origin=${startPoint.lng},${startPoint.lat}&' 'destination=${endPoint.lng},${endPoint.lat}&' 'key=$apiKey'; final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { final data = json.decode(response.body); return _parsePolyline(data['route']['paths'][0]['steps']); } throw Exception('Failed to load route'); } List _parsePolyline(List steps) { final List path = []; for (final step in steps) { final points = (step['polyline'] as String).split(';'); for (final point in points) { final coords = point.split(',').map(double.parse).toList(); path.add(LatLng(coords[0], coords[1])); } } return path; } List _simplifyPath(List points, {double epsilon = 0.0001}) { return douglasPeucker(points, epsilon); } int _calculateAutoZoom(List points, {int mapWidth = 1024, int mapHeight = 512}) { if (points.isEmpty) return 15; // 默认缩放级别 // 1. 计算坐标范围 double minLng = double.infinity; double maxLng = -double.infinity; double minLat = double.infinity; double maxLat = -double.infinity; for (final p in points) { final lng = p.lng; final lat = p.lat; minLng = math.min(minLng, lng); maxLng = math.max(maxLng, lng); minLat = math.min(minLat, lat); maxLat = math.max(maxLat, lat); } // 3. 计算缩放级别(基于Leaflet算法改进) const worldWidth = 256; // 高德瓦片尺寸 const worldHeight = 256; const zoomMax = 18; // 高德最大缩放级别 double latRad(double lat) { final sin = math.sin(lat * math.pi / 180); return math.log((1 + sin) / (1 - sin)) / 2; } final latFraction = (latRad(maxLat) - latRad(minLat)) / math.pi; final lngDiff = maxLng - minLng; final lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360; final latZoom = math.log((mapHeight / worldHeight) / latFraction) / math.ln2; final lngZoom = math.log((mapWidth / worldWidth) / lngFraction) / math.ln2; return math.min(math.min(latZoom, lngZoom).floor(), zoomMax); } String _buildStaticMapUrl(List path, int zoom) { final pathParam = path.map((p) => '${p.lng},${p.lat}').join(';'); return 'https://restapi.amap.com/v3/staticmap?' 'zoom=$zoom&' 'size=1024*512&' 'paths=10,0x0000ff,1,,:$pathParam&' 'markers=mid,,A:${startPoint.lng},${startPoint.lat}|' 'mid,,B:${endPoint.lng},${endPoint.lat}&' 'key=$apiKey'; } List douglasPeucker(List points, double epsilon) { if (points.length < 3) return points; double maxDistance = 0; int index = 0; for (int i = 1; i < points.length - 1; i++) { double d = _perpendicularDistance(points[i], points.first, points.last); if (d > maxDistance) { index = i; maxDistance = d; } } if (maxDistance > epsilon) { return [ ...douglasPeucker(points.sublist(0, index + 1), epsilon), ...douglasPeucker(points.sublist(index), epsilon).skip(1) ]; } return [points.first, points.last]; } double _perpendicularDistance(LatLng point, LatLng start, LatLng end) { final area = (end.lng - start.lng) * (point.lat - start.lat) - (end.lat - start.lat) * (point.lng - start.lng); final length = sqrt(pow(end.lng - start.lng, 2) + pow(end.lat - start.lat, 2)); return area.abs() / length; } @override Widget build(BuildContext context) { return Column( children: [ FutureBuilder( future: _mapUrl, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { return Image.network(snapshot.data); } return const CircularProgressIndicator(); }, ), 10.verticalSpace, Text( widget.content, style: TextStyle( color: widget.isDarkMode ? Colors.white : Colors.black, fontSize: 16.sp, height: 1.5, ), ), ], ); } } class LatLng { final double lat; final double lng; LatLng(this.lng, this.lat); // 高德使用经度在前纬度在后的格式 }