Flutter地图集成(高德与百度)

3,225 阅读2分钟

本文只考虑ios集成,安卓暂时没踩坑

1.Flutter集成高德地图SDK

特别提醒:如果你想要做导航功能,目前高德的flutter sdk是不支持的。可以转用百度 sdk,不要浪费时间去研究,详见下方文档。

文档地址:lbs.amap.com/api/flutter…

依赖列表如下:

 # 高德地图

amap_flutter_map: ^3.0.0

# 高德定位

amap_flutter_location: ^3.0.0

permission_handler: ^10.2.0

ios权限配置

打开info -> Source Code

在source code中添加以下权限:

<key>NSLocationWhenInUseUsageDescription</key>
<string>使用APP时开启定位服务</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>使用APP时一直使用定位服务</string>

根据permission_handler的文档,我们还需要在Podfile中开启以下权限:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    target.build_configurations.each do |config|
      config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
        '$(inherited)',
      ## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
      'PERMISSION_LOCATION=1',
    ]
    end
  end
end

代码示例

import 'dart:async';
import 'package:amap_flutter_map/amap_flutter_map.dart';
import 'package:amap_flutter_base/amap_flutter_base.dart';
import 'package:amap_flutter_location/amap_flutter_location.dart';
import 'package:amap_flutter_location/amap_location_option.dart';

import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';

class HomeSelectShop extends StatefulWidget {
  const HomeSelectShop({super.key});

  @override
  State<HomeSelectShop> createState() => _HomeSelectShopState();
}

class _HomeSelectShopState extends State<HomeSelectShop> {
  Map<String, Object>? _locationResult;

  late StreamSubscription<Map<String, Object>> _locationListener;

  final AMapFlutterLocation _locationPlugin = AMapFlutterLocation();

  AMapApiKey amapApiKeys =
      const AMapApiKey(iosKey: '3b5f7ecb3a149ce8a5bf4d2b82d55944');

  //需要先设置一个空的map赋值给AMapWidget的markers,否则后续无法添加marker
  final Map<String, Marker> _markers = <String, Marker>{};

  final Map<String, Polyline> _polylines = <String, Polyline>{};

  @override
  void initState() {
    super.initState();
    AMapFlutterLocation.setApiKey("", "3b5f7ecb3a149ce8a5bf4d2b82d55944");    AMapFlutterLocation.updatePrivacyShow(true, true);
    AMapFlutterLocation.updatePrivacyAgree(true);
    requestPermission();

    ///注册定位结果监听
    _locationListener = _locationPlugin
        .onLocationChanged()
        .listen((Map<String, Object> result) {
      setState(() {
        _locationResult = result;
      });
    });
  }

  @override
  void dispose() {
    super.dispose();

    ///移除定位监听
    _locationListener.cancel();

    ///销毁定位
    _locationPlugin.destroy();
  }

  /// 动态申请定位权限
  void requestPermission() async {
    // 申请权限
    bool hasLocationPermission = await requestLocationPermission();
    if (hasLocationPermission) {
      // print("定位权限申请通过");
      _startLocation();
    } else {
      // print("定位权限申请不通过");
    }
  }

  ///开始定位
  void _startLocation() {
    //将定位参数设置给定位插件
    _locationPlugin.setLocationOption(AMapLocationOption());
    _locationPlugin.startLocation();
  }

  /// 申请定位权限
  /// 授予定位权限返回true, 否则返回false
  Future<bool> requestLocationPermission() async {
    //获取当前的权限
    var status = await Permission.location.status;
    if (status == PermissionStatus.granted) {
      //已经授权
      return true;
    } else {
      //未授权则发起一次申请
      status = await Permission.location.request();
      if (status == PermissionStatus.granted) {
        return true;
      } else {
        return false;
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_locationResult == null ||
        _locationResult?['latitude'] == null ||
        _locationResult?['longitude'] == null ||
        _locationResult?['errorCode'] == 0) {
      return const Text('Sorry');
    }
    var latitude = double.parse(_locationResult!['latitude'] as String);
    var longitude = double.parse(_locationResult!['longitude'] as String);

    for (int i = 0; i < 1; i++) {
      LatLng position = LatLng(latitude, longitude - 0.0052);
      Marker marker = Marker(
          position: position,
          icon: BitmapDescriptor.defaultMarkerWithHue(
              BitmapDescriptor.hueOrange));
      _markers[marker.id] = marker;

      final Polyline polyline = Polyline(
          width: 20,
          customTexture:
              BitmapDescriptor.fromIconPath('assets/images/texture_green.png'),
          joinType: JoinType.round,
          points: [
            LatLng(latitude, longitude),
            LatLng(latitude, longitude - 0.0052),
          ]);
      _polylines[polyline.id] = polyline;
    }

    return Stack(
      children: [
        AMapWidget(
          privacyStatement: const AMapPrivacyStatement(
              hasAgree: true, hasContains: true, hasShow: true),
          initialCameraPosition: CameraPosition(
            target: LatLng(latitude, longitude),
            zoom: 15,
          ),
          trafficEnabled: true,
          myLocationStyleOptions: MyLocationStyleOptions(true),
          markers: Set<Marker>.of(_markers.values),
          polylines: Set<Polyline>.of(_polylines.values),
        ),
      ],
    );
  }
}

结果预览

注意事项

需要特别注意的是,你在代码中获取的定位是不准的,毕竟xcode有内置的location定位参数,你可以通过修改对应的参数来获取你想要的定位,如图:

2.Flutter集成百度地图SDK

文档地址:lbsyun.baidu.com/index.php?t…

同上,需要先配置 Source Code + Podfile。

如遇 ios-授权失败:230 的提示,需要检查App的Bundle Identifier是否跟你的百度安全码是一致的,如下图所示:

依赖列表如下:

permission_handler: ^10.2.0

flutter_bmflocation: ^3.3.0

flutter_baidu_mapapi_map: ^3.3.0

flutter_baidu_mapapi_search: ^3.3.0

案例构建

需求:我想根据当前的坐标,形成到某个点的步行推荐路线,代码如下:

import 'dart:io';
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bmflocation/flutter_bmflocation.dart';
import 'package:flutter_mall/model/walk_route_model.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
import 'package:flutter_baidu_mapapi_search/flutter_baidu_mapapi_search.dart';

class HomeSelectShop extends StatefulWidget {
  const HomeSelectShop({super.key});

  @override
  State<HomeSelectShop> createState() => _HomeSelectShopState();
}

class _HomeSelectShopState extends State<HomeSelectShop> {
  LocationFlutterPlugin myLocPlugin = LocationFlutterPlugin();
  BaiduLocation _loationResult = BaiduLocation();
  bool _suc = false;
  late BMFMapController _myMapController;

  BMFPolyline? _polyline;
  WalkRouteModel? _routeModel;
  @override
  void initState() {
    super.initState();

    /// 动态申请定位权限
    requestPermission();
    // 设置是否隐私政策
    myLocPlugin.setAgreePrivacy(true);
    BMFMapSDK.setAgreePrivacy(true);

    if (Platform.isIOS) {
      /// 设置ios端ak, android端ak可以直接在清单文件中配置
      myLocPlugin.authAK('6csB1DFVMOsfwagN0VymekhC5MVGcdwO');
      BMFMapSDK.setApiKeyAndCoordType(
          '6csB1DFVMOsfwagN0VymekhC5MVGcdwO', BMF_COORD_TYPE.BD09LL);

      ///接受定位回调
      myLocPlugin.singleLocationCallback(callback: (BaiduLocation result) {
        setState(() {
          _loationResult = result;
          routeSearch();
          // locationFinish();
        });
      });
    }

    /// iOS端鉴权结果
    myLocPlugin.getApiKeyCallback(callback: (String result) {
      String str = result;
      print('鉴权结果:' + str);
    });
  }

  void requestPermission() async {
    // 申请权限
    bool hasLocationPermission = await requestLocationPermission();
    if (hasLocationPermission) {
      // 权限申请通过
      _locationAction();
      _startLocation();
    } else {}
  }

  /// 申请定位权限
  /// 授予定位权限返回true, 否则返回false
  Future<bool> requestLocationPermission() async {
    //获取当前的权限
    var status = await Permission.location.status;
    if (status == PermissionStatus.granted) {
      //已经授权
      return true;
    } else {
      //未授权则发起一次申请
      status = await Permission.location.request();
      if (status == PermissionStatus.granted) {
        return true;
      } else {
        return false;
      }
    }
  }

  void _locationAction() async {
    /// ios 端设置定位参数
    Map iosMap = initIOSOptions().getMap();

    _suc = await myLocPlugin.prepareLoc({}, iosMap);
    print('设置定位参数:$iosMap');
  }

  BaiduLocationIOSOption initIOSOptions() {
    BaiduLocationIOSOption options = BaiduLocationIOSOption(
        coordType: BMFLocationCoordType.bd09ll,
        BMKLocationCoordinateType: 'BMKLocationCoordinateTypeBMK09LL',
        desiredAccuracy: BMFDesiredAccuracy.best);
    return options;
  }

  /// 启动定位
  Future<void> _startLocation() async {
    if (Platform.isIOS) {
      _suc = await myLocPlugin
          .singleLocation({'isReGeocode': true, 'isNetworkState': true});
    }
  }

  ///定位完成添加mark
  void locationFinish() {
    /// 创建BMFMarker
    BMFMarker marker = BMFMarker.icon(
        position: BMFCoordinate(
            _loationResult.latitude ?? 0.0, _loationResult.longitude ?? 0.0),
        title: 'flutterMaker',
        identifier: 'flutter_marker',
        icon: 'assets/images/icon_mark.png');
    print(_loationResult.latitude.toString() +
        _loationResult.longitude.toString());

    /// 添加Marker
    _myMapController.addMarker(marker);

    ///设置中心点
    _myMapController.setCenterCoordinate(
        BMFCoordinate(
            _loationResult.latitude ?? 0.0, _loationResult.longitude ?? 0.0),
        false);
  }

  // 定位完成形成规定路线
  void routeSearch() async {
    /// 起点
    BMFPlanNode startNode = BMFPlanNode(
        name: '测试',
        cityName: '深圳市',
        pt: BMFCoordinate(
            _loationResult.latitude ?? 0.0, _loationResult.longitude ?? 0.0));

    /// 终点
    BMFPlanNode endNode = BMFPlanNode(
      name: '嘉宾公园',
      cityName: '深圳市',
    );
    BMFWalkingRoutePlanOption option = BMFWalkingRoutePlanOption(
      from: startNode,
      to: endNode,
    );

    /// 检索对象
    BMFWalkingRouteSearch routeSearch = BMFWalkingRouteSearch();

    /// 检索结果回调
    routeSearch.onGetWalkingRouteSearchResult(
        callback: _onGetWalkingRouteSearchResult);

    /// 发起检索
    bool result = await routeSearch.walkingRouteSearch(option);

    if (result) {
      print("发起检索成功");
    } else {
      print("发起检索失败");
    }
  }

  /// 检索结果回调
  void _onGetWalkingRouteSearchResult(
      BMFWalkingRouteResult result, BMFSearchErrorCode errorCode) {
    if (errorCode != BMFSearchErrorCode.NO_ERROR) {
      var error = "检索失败" + "errorCode:${errorCode.toString()}";
      print(error);
      return;
    }

    /// 所有步行路线中第一条路线
    BMFWalkingRouteLine firstLine = result.routes![0];
    _routeModel = WalkRouteModel.withModel(firstLine);

    /// 移除marker
    _myMapController.cleanAllMarkers();

    /// 起点marker
    BMFMarker startMarker = BMFMarker.icon(
      position: _routeModel!.startNode!.location!,
      title: _routeModel?.startNode?.title,
      icon: "assets/images/icon_start.png",
    );

    _myMapController.addMarker(startMarker);

    /// 终点marker
    BMFMarker endMarker = BMFMarker.icon(
      position: _routeModel!.endNode!.location!,
      title: _routeModel?.endNode?.title,
      icon: "assets/images/icon_end.png",
    );
    _myMapController.addMarker(endMarker);

    List<BMFCoordinate> coordinates = [];
    for (BMFWalkingStep? step in firstLine.steps!) {
      if (null == step) {
        continue;
      }

      /// 路线经过的路段坐标点
      if (null != step.points) {
        coordinates.addAll(step.points!);
      }
    }

    if (_polyline != null) {
      _myMapController.removeOverlay(_polyline!.id);
    }

    /// 添加路线polyline
    _polyline = BMFPolyline(
      coordinates: coordinates,
      indexs: [0],
      textures: ["assets/images/traffic_texture_smooth.png"],
      dottedLine: false,
    );
    _myMapController.addPolyline(_polyline!);

    /// 根据polyline设置地图显示范围
    BMFCoordinateBounds coordinateBounds = getVisibleMapRect(coordinates);
    _myMapController.setVisibleMapRectWithPadding(
      visibleMapBounds: coordinateBounds,
      insets: EdgeInsets.only(top: 65.0, bottom: 70, left: 10, right: 10),
      animated: true,
    );
  }

  /// 设置地图参数
  BMFMapOptions initMapOptions() {
    BMFMapOptions mapOptions = BMFMapOptions(
        center: BMFCoordinate(39.917215, 116.380341),
        zoomLevel: 18,
        mapPadding: BMFEdgeInsets(top: 0, left: 0, right: 0, bottom: 0));
    return mapOptions;
  }

  /// 创建完成回调
  void onBMFMapCreated(BMFMapController controller) {
    _myMapController = controller;

    /// 地图加载回调
    _myMapController.setMapDidLoadCallback(callback: () {
      print('mapDidLoad-地图加载完成');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        width: double.infinity,
        height: double.infinity,
        child: BMFMapWidget(
          onBMFMapCreated: (controller) {
            onBMFMapCreated(controller);
          },
          mapOptions: initMapOptions(),
        ));
  }
}

/// 获取地图显示区域
BMFCoordinateBounds getVisibleMapRect(List<BMFCoordinate> coordinates) {
  BMFCoordinate fisrt = coordinates[0];
  double leftBottomX = fisrt.latitude;
  double leftBottomY = fisrt.longitude;
  double rightTopX = fisrt.latitude;
  double rightTopY = fisrt.longitude;

  for (BMFCoordinate coordinate in coordinates) {
    if (coordinate.latitude < leftBottomX) {
      leftBottomX = coordinate.latitude;
    }

    if (coordinate.longitude < leftBottomY) {
      leftBottomY = coordinate.longitude;
    }

    if (coordinate.latitude > rightTopX) {
      rightTopX = coordinate.latitude;
    }

    if (coordinate.longitude > rightTopY) {
      rightTopY = coordinate.longitude;
    }
  }

  BMFCoordinate leftBottom = BMFCoordinate(leftBottomX, leftBottomY);
  BMFCoordinate rightTop = BMFCoordinate(rightTopX, rightTopY);

  BMFCoordinateBounds coordinateBounds =
      BMFCoordinateBounds(northeast: rightTop, southwest: leftBottom);

  return coordinateBounds;
}

在lib/model下,新建三个文件

这些文件和图片资源,都可以在百度地图的Demo中找到,点击此进行跳转

最后的效果图如下:

注意事项:

1.加完对应的资源后要重新启动下项目,不然解析不出对应的资源

2.无论是高德还是百度,都要先搞定权限+模拟器的定位

3.两者的API大同小异,我们可以先获取定位,再进行相应的路线规划。