Flutter调用MapQuest Directions API绘制路线显示异常问题
路线渲染异常核心原因
- API请求参数经纬度错配:构造MapQuest请求URL时,两个途经点的经纬度交叉写反,第一个点传入的是「起点纬度+终点经度」,第二个点传入的是「终点纬度+起点经度」,请求返回的路线坐标本身就和实际起终点位置完全不匹配。
- 路线坐标提取逻辑错误:MapQuest Directions接口返回的
maneuvers数组仅存储转向节点坐标,两个转向节点之间的完整道路形状坐标存储在route.shapePoints字段下,仅提取转向点连线只会得到跨建筑、跨道路的生硬折线,无法贴合实际道路走向。 - 地图视角逻辑存在缺陷:自定义MapWidget每次执行build时,都会强制把相机动画拉回当前定位点、固定18倍缩放,用户既无法自由拖动查看完整路线,组件也不会自动适配路线范围调整视野。
- URL构造方式不规范:手动拼接JSON字符串到请求参数中,未做URL编码,特殊场景下会出现请求解析失败的问题。
可行修复方案
1. 修复MapQuest API请求层逻辑
修正经纬度传参错误,改用标准Uri构造方法避免编码问题,同时提取完整道路形状点而非仅转向点:
import 'dart:math'; class MapQuestDataProvider { Future<MapQuestDirection> getDirections( LatLng source, LatLng destination) async { try { MapQuestDirection result = MapQuestDirection(locations: []); const apiKey = "[API_KEY]"; // 标准Uri构造,自动处理参数编码 final uri = Uri.https( "www.mapquestapi.com", "/directions/v2/optimizedroute", { "key": apiKey, "shapeFormat": "raw", // 返回未压缩的原始坐标点,无需额外解压 "json": jsonEncode({ "locations": [ "${source.latitude},${source.longitude}", // 修正:起点完整经纬度 "${destination.latitude},${destination.longitude}" // 修正:终点完整经纬度 ] }) } ); final response = await http.get(uri); if (response.statusCode == 200) { final data = jsonDecode(response.body); final route = data["route"]; final shapePoints = route["shapePoints"] as List; // shapePoints按[纬度, 经度, 纬度, 经度]顺序排列,两两解析为坐标 for (int i = 0; i < shapePoints.length; i += 2) { final lat = shapePoints[i] as double; final lng = shapePoints[i+1] as double; result.locations.add(LatLng(lat, lng)); } return result; } else { throw Exception('Failed to load route'); } } catch (e) { print("Request exception: $e"); throw e; } } }
2. 修复MapWidget相机控制逻辑
移除每次build强制重置视角的逻辑,新增路线自动适配视野能力:
class _MapWidgetState extends State<MapWidget> { GoogleMapController? mapController; final defaultMapZooming = 18.0; bool _initialLocationSetted = false; // 自动计算路线边界,调整相机视野覆盖全部路径点 void _fitRouteBounds(List<LatLng> points) { if (mapController == null || points.isEmpty) return; double minLat = points.first.latitude; double maxLat = points.first.latitude; double minLng = points.first.longitude; double maxLng = points.first.longitude; for (final point in points) { minLat = min(minLat, point.latitude); maxLat = max(maxLat, point.latitude); minLng = min(minLng, point.longitude); maxLng = max(maxLng, point.longitude); } final bounds = LatLngBounds( southwest: LatLng(minLat, minLng), northeast: LatLng(maxLat, maxLng) ); mapController!.animateCamera(CameraUpdate.newLatLngBounds(bounds, 80)); } @override void didUpdateWidget(covariant MapWidget oldWidget) { super.didUpdateWidget(oldWidget); // 折线更新时自动适配路线视野 if (widget.polylines.isNotEmpty) { final allPoints = widget.polylines.values.expand((poly) => poly.points).toList(); _fitRouteBounds(allPoints); } } @override Widget build(BuildContext context) { return Container(child: _mapView(context)); } Widget _mapView(BuildContext context) { // 仅首次拿到定位时初始化视角,避免每次build重置用户操作 if (mapController != null && widget.currentLocation != null && !_initialLocationSetted) { _initialLocationSetted = true; mapController!.animateCamera(CameraUpdate.newLatLngZoom( LatLng(widget.currentLocation!.latitude, widget.currentLocation!.longitude), defaultMapZooming)); } return GoogleMap( onMapCreated: (controller) => mapController = controller, zoomControlsEnabled: false, myLocationEnabled: false, mapType: MapType.normal, onLongPress: widget.onTab, markers: widget.markers, polylines: widget.polylines.values.toSet(), minMaxZoomPreference: const MinMaxZoomPreference(12, 25), initialCameraPosition: CameraPosition( target: _getLocationOrDefault(), zoom: defaultMapZooming, ), ); } LatLng _getLocationOrDefault() { return widget.currentLocation ?? const LatLng(21.215415017175165, 72.8879595194489); } }
3. 优化折线构造逻辑
移除手动重复插入起终点的逻辑,直接使用接口返回的完整路径点绘制,可额外添加圆角样式优化显示效果:
void setPathFromMapQuestData(List<LatLng> locations) { final polylineCoordinates = List<LatLng>.from(locations); const polylineId = PolylineId("poly"); final poly = Polyline( polylineId: polylineId, color: Colors.deepPurpleAccent, points: polylineCoordinates, width: 8, startCap: Cap.roundCap, endCap: Cap.roundCap, jointType: JointType.round ); polylines[polylineId] = poly; setState(() { this.polylines = Map.from(polylines); }); }
- 调试阶段可先打印接口返回的坐标列表,确认首尾点和实际起终点位置匹配后再渲染,快速验证参数是否正确。
- 正式环境如果需要减少接口传输体积,可将
shapeFormat改为cmp6,引入对应解压工具处理压缩坐标即可。
内容的提问来源于stack exchange,提问作者moaad cole
相关产品推荐
相关产品推荐

