HarmonyOS Map Kit 开发全攻略:地图集成、标记控制与定位融合

91 阅读5分钟

在 HarmonyOS 应用开发中,地图功能是本地生活、出行导航、位置服务类 App 的核心模块。华为 Map Kit 提供了丰富的地图能力,包括地图渲染、标记点管理、相机控制、定位融合等功能。本文将从开发准备(签名配置) 到核心功能实现(地图显示、标记控制、定位融合) ,一步步拆解 Map Kit 的集成流程,附带完整可复用代码,助力开发者快速落地地图相关功能。

签名

签名的主要原因是为了确保应用的安全性和合法性,HarmonyOS应用/元服务通过数字证书(.cer文件)和Profile文件(.p7b文件)来保证应用/元服务的完整性。

编辑

  • 准备:创建项目、创建应用
- 菜单【证书、APP ID和Profile】点击APP ID 然后点击新增

```
应用名称: app名字  例如我写  锋密手持弹幕  或者 测试1
应用包名   com.xxx.xxx     com.sljz.tet1      一般都是网址倒过来写  如果元服务就不用写了
```
  • 1.生成秘钥和证书请求文件:Build构建 => generate key and csr 生成私钥和证书请求文件
genderate key       fmcrm.p12				秘钥密码123456.z
generator csr        fmcrm.csr 
  • 2.申请发布证书
https://developer.huawei.com/consumer/cn/   点击右侧管理中心  =》 点击【appgallery connect】 =》点击【证书、APP ID和Profile 】 => 点击左侧菜单【证书】=》点击新增证书

证书名称: fmcrm_release_cert
证书类型: 发布证书
证书请求文件:fmcrm.csr
  • 3.申请发布Profile:点击【证书、APP ID和Profile】新增【Profile】名字【fmcrm_profile_】下载后就是fmcrm_profile_Release.p7b文件
名称: fmscdm_profile_   后期下载就是 fmscdm_profile_Release.p7b
类型:发布
证书  2操作的
权限:不用
  • 4.配置签名信息 File -> Project Structure... => signing config
default  : bundle name 默认的  然后去掉 auto 勾选

填写 ok就行

注:如果修改的不是default 则要继续修改`根目录build-profile.json5文件`   =》 `products` => `"signingConfig": "app1自己写的名字",`

显示地图

developer.huawei.com/consumer/cn…

import { MapComponent, mapCommon, map } from '@kit.MapKit';
import { AsyncCallback } from '@kit.BasicServicesKit';

@Entry
@Component
struct HuaweiMapDemo {
  private TAG = "HuaweiMapDemo";
  private mapOptions?: mapCommon.MapOptions;
  private callback?: AsyncCallback<map.MapComponentController>;
  private mapController?: map.MapComponentController;
  private mapEventManager?: map.MapEventManager;

  aboutToAppear(): void {
    // 地图初始化参数,设置地图中心点坐标及层级
    this.mapOptions = {
      position: {
        target: {
          latitude: 39.9,
          longitude: 116.4
        },
        zoom: 10
      }
    };

    // 地图初始化的回调
    this.callback = async (err, mapController) => {
      if (!err) {
        // 获取地图的控制器类,用来操作地图
        this.mapController = mapController;
        this.mapEventManager = this.mapController.getEventManager();
        let callback = () => {
          console.info(this.TAG, `on-mapLoad`);
        }
        this.mapEventManager.on("mapLoad", callback);
      }
    };
  }

  // 页面每次显示时触发一次,包括路由过程、应用进入前台等场景,仅@Entry装饰的自定义组件生效
  onPageShow(): void {
    // 将地图切换到前台
    if (this.mapController) {
      this.mapController.show();
    }
  }

  // 页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景,仅@Entry装饰的自定义组件生效
  onPageHide(): void {
    // 将地图切换到后台
    if (this.mapController) {
      this.mapController.hide();
    }
  }

  build() {
    Stack() {
      // 调用MapComponent组件初始化地图
      MapComponent({ mapOptions: this.mapOptions, mapCallback: this.callback }).width('100%').height('100%');
    }.height('100%')
  }
}

控制地图标记

编辑

import { MapComponent, mapCommon, map } from '@kit.MapKit';
import { AsyncCallback } from '@kit.BasicServicesKit';


@Entry
@Component
struct HuaweiMapDemo {
  private TAG = "HuaweiMapDemo";
  private mapOptions?: mapCommon.MapOptions;
  private callback?: AsyncCallback<map.MapComponentController>;
  private mapController?: map.MapComponentController;
  private mapEventManager?: map.MapEventManager;
  private marker?: map.Marker;

  aboutToAppear(): void {
    // 地图初始化参数,设置地图中心点坐标及层级
    this.mapOptions = {
      position: {
        target: {
          latitude: 39.9,
          longitude: 116.4
        },
        zoom: 10
      }
    };

    // 地图初始化的回调
    this.callback = async (err, mapController) => {
      if (!err) {
        // 获取地图的控制器类,用来操作地图
        this.mapController = mapController;
        this.mapEventManager = this.mapController.getEventManager();
        let callback = async () => {
          console.info(this.TAG, `on-mapLoad`);
          let markerOptions: mapCommon.MarkerOptions = {
            position: {
              latitude: 39.9,
              longitude: 116.4
            },
            icon: $r('app.media.point'),  // ✅ update1
            clickable: true,
            // 设置信息窗标题
            title: "自定义信息窗", // ✅ update1
          };
          this.marker = await this.mapController?.addMarker(markerOptions);
          this.marker?.setInfoWindowVisible(true);
          this.marker?.setSnippet('这是地址');

        }
        this.mapEventManager.on("mapLoad", callback);
      }
    };
  }

  // 页面每次显示时触发一次,包括路由过程、应用进入前台等场景,仅@Entry装饰的自定义组件生效
  onPageShow(): void {
    // 将地图切换到前台
    if (this.mapController) {
      this.mapController.show();
    }
  }

  // 页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景,仅@Entry装饰的自定义组件生效
  onPageHide(): void {
    // 将地图切换到后台
    if (this.mapController) {
      this.mapController.hide();
    }
  }

  build() {
    Stack() {
      // 调用MapComponent组件初始化地图
      MapComponent({ mapOptions: this.mapOptions, mapCallback: this.callback,
        // 自定义信息窗   // ✅ update2
        customInfoWindow: this.customInfoWindow }).width('100%').height('100%');
    }.height('100%')
  }



  // ✅ update2
  // ✅ update2
  @BuilderParam customInfoWindow: (params: map.MarkerDelegate) => void = this.customInfoWindowBuilder;

  @Builder
  customInfoWindowBuilder(params: map.MarkerDelegate) {
    if (params.marker) {
      Column() {
        Text(params.marker.getTitle())
          .width(180)
          .height(24)
          .backgroundColor(Color.Green)
          .borderRadius({
            topLeft: 12,
            topRight: 12
          })
          .padding({
            top: 4,
            bottom: 4,
            left: 12
          })
          .fontSize(12)
          .fontColor('#E6FFFFFF')
          .lineHeight(16)
          .textAlign(TextAlign.Start)
        Text(params.marker.getSnippet())
          .width(180)
          .backgroundColor('#FFFFFF')
          .borderRadius({
            bottomLeft: 12,
            bottomRight: 12
          })
          .padding({
            top: 4,
            bottom: 6,
            left: 12,
            right: 8
          })
          .fontSize(14)
          .fontColor('#E6000000')
          .lineHeight(19)
          .textAlign(TextAlign.Start)
      }
    }
  }
}

更改地图位置

developer.huawei.com/consumer/cn…

Button('测试').onClick(() => {
  console.log('hello')

  this.marker?.setPosition({
    latitude: 29.9,
    longitude: 116.4
  });
  this.marker?.setInfoWindowVisible(true);
  this.marker?.setSnippet('这是地址2');

  let mapLocation: mapCommon.LatLng = {
    latitude: 29.9,
    longitude: 116.4
  }
  // await map.convertCoordinate(mapCommon.CoordinateType.WGS84, mapCommon.CoordinateType.GCJ02, {
  //   latitude: location.latitude,
  //   longitude: location.longitude
  // });
  let cameraUpdate = map.newCameraPosition( {  // 创建CameraUpdate对象
    target: mapLocation,
    zoom: 15,
    tilt: 0,
    bearing: 0
  });
  this.mapController?.animateCamera(cameraUpdate, 100); // // 以动画方式移动地图相机
  // ....
})

定位

封装Map组件

import { MapComponent, mapCommon, map } from '@kit.MapKit';
import { AsyncCallback } from '@kit.BasicServicesKit';


@Component
export struct Map2 {

  @Consume latitude:number
  @Consume longitude:number
  @Consume  @Watch('onUploadLocation')  content:string

  async onUploadLocation() {

    console.log('slj Map2 onUploadLocation ', this.latitude)
    console.log('slj Map2 onUploadLocation ', this.longitude)
    console.log('slj Map2 onUploadLocation ', this.content)

    // FIXBUG: 定位获取的经度纬度 直接给华为地图不够精确  需要通过转换下
    let mapLocation: mapCommon.LatLng =  await map.convertCoordinate(mapCommon.CoordinateType.WGS84, mapCommon.CoordinateType.GCJ02, {
      latitude: this.latitude,
      longitude: this.longitude
    });


    this.marker?.setPosition(mapLocation)
    this.marker?.setSnippet(this.content);
    this.marker?.setInfoWindowVisible(true);


    let cameraUpdate = map.newCameraPosition( {  // 创建CameraUpdate对象
      target: mapLocation,
      zoom: 15,
      tilt: 0,
      bearing: 0
    });
    this.mapController?.animateCamera(cameraUpdate, 1000);
  }



  private TAG = "HuaweiMapDemo";
  private mapOptions?: mapCommon.MapOptions;
  private callback?: AsyncCallback<map.MapComponentController>;
  private mapController?: map.MapComponentController;
  private mapEventManager?: map.MapEventManager;
  private marker?: map.Marker;

  aboutToAppear(): void {
    // 地图初始化参数,设置地图中心点坐标及层级
    this.mapOptions = {
      position: {
        target: {
          latitude: 39.9,
          longitude: 116.4
        },
        zoom: 10
      }
    };

    // 地图初始化的回调
    this.callback = async (err, mapController) => {
      if (!err) {
        // 获取地图的控制器类,用来操作地图
        this.mapController = mapController;
        this.mapEventManager = this.mapController.getEventManager();
        let callback = async () => {
          console.info(this.TAG, `on-mapLoad`);

          // ========
          // Marker初始化参数
          let markerOptions: mapCommon.MarkerOptions = {
            position: {
              latitude: 39.9,
              longitude: 116.4
            },
            rotation: 0,
            visible: true,
            zIndex: 0,
            alpha: 1,
            anchorU: 0.5,
            anchorV: 1,
            clickable: true,
            draggable: true,
            flat: false,

            icon: $r('app.media.point'),  // ✅ update1
            // clickable: true,
            // 设置信息窗标题
            title: "您当前的位置信息如下:", // ✅ update1
          };
          // 创建Marker
          this.marker = await this.mapController?.addMarker(markerOptions);


          this.marker?.setSnippet('这是地址');
          this.marker?.setInfoWindowVisible(true);
          // ========
        }
        this.mapEventManager.on("mapLoad", callback);
      }
    };
  }

  // 页面每次显示时触发一次,包括路由过程、应用进入前台等场景,仅@Entry装饰的自定义组件生效
  onPageShow(): void {
    // 将地图切换到前台
    if (this.mapController) {
      this.mapController.show();
    }
  }

  // 页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景,仅@Entry装饰的自定义组件生效
  onPageHide(): void {
    // 将地图切换到后台
    if (this.mapController) {
      this.mapController.hide();
    }
  }

  build() {
    Stack() {
      // 调用MapComponent组件初始化地图
      MapComponent({ mapOptions: this.mapOptions, mapCallback: this.callback,
        // 自定义信息窗   // ✅ update2
        customInfoWindow: this.customInfoWindow }).width('100%').height('100%');


      // Button('更改位置').onClick(() => {
      //
      //   this.marker?.setPosition({
      //     latitude: 29.9,
      //     longitude: 116.4
      //   })
      //   this.marker?.setSnippet('这是地址2');
      //   this.marker?.setInfoWindowVisible(true);
      //
      //
      //
      //   let cameraUpdate = map.newCameraPosition( {  // 创建CameraUpdate对象
      //     target: {
      //       latitude: 29.9,
      //       longitude: 116.4
      //     },
      //     zoom: 15,
      //     tilt: 0,
      //     bearing: 0
      //   });
      //   this.mapController?.animateCamera(cameraUpdate, 1000);
      // })
    }.height('100%')
  }



  // ✅ update2
  // ✅ update2
  @BuilderParam customInfoWindow: (params: map.MarkerDelegate) => void = this.customInfoWindowBuilder;

  @Builder
  customInfoWindowBuilder(params: map.MarkerDelegate) {
    if (params.marker) {
      Column() {
        Text(params.marker.getTitle())
          .width(180)
          .height(24)
          .backgroundColor(Color.Green)
          .borderRadius({
            topLeft: 12,
            topRight: 12
          })
          .padding({
            top: 4,
            bottom: 4,
            left: 12
          })
          .fontSize(12)
          .fontColor('#E6FFFFFF')
          .lineHeight(16)
          .textAlign(TextAlign.Start)
        Text(params.marker.getSnippet())
          .width(180)
          .backgroundColor('#FFFFFF')
          .borderRadius({
            bottomLeft: 12,
            bottomRight: 12
          })
          .padding({
            top: 4,
            bottom: 6,
            left: 12,
            right: 8
          })
          .fontSize(14)
          .fontColor('#E6000000')
          .lineHeight(19)
          .textAlign(TextAlign.Start)
      }
    }
  }
}

然后使用

import {Map2} from '../components/Map2'
import AccessControl from '../utils/AccessControl'
import { geoLocationManager } from '@kit.LocationKit'
import { abilityAccessCtrl } from '@kit.AbilityKit'
import { BusinessError } from '@kit.BasicServicesKit'

@Entry
@Component
struct Test {


  @Provide latitude:number  = 39.9
  @Provide longitude:number  = 116.4
  @Provide content:string  = '暂时未发获取位置信息'

  aboutToAppear(): void {
    this.getLocation()
  }


  async getLocation() {
    // 定位模拟器不支持 必须真机
    // 1 申请位置权限
    const state = await AccessControl.checkPermissions(['ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION'])
    console.log('slj 1 申请位置权限结果:', state)
    if (state) {
      // 2 检查全局位置开关状态(再设置)  这玩意关了所以app都无法使用  弹框让他去开启
      try {
        let locationEnabled = geoLocationManager.isLocationEnabled();  // 如果是false则拉起系统弹框
        console.log('slj 2 检查全局开关状态:', locationEnabled)
        if (!locationEnabled) {
          let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
          const state2 = await atManager.requestGlobalSwitch(getContext(this), abilityAccessCtrl.SwitchType.LOCATION).then((data: Boolean) => {
            console.info('slj 2衍伸弹窗 success/cancel:' + JSON.stringify(data));
            return data
          }).catch((err: BusinessError) => {
            console.error('slj 2衍伸弹窗:' + JSON.stringify(err));
            return false
          });
          if (!state2) return
        }

        // 3 单次获取当前设备位置 geoLocationManager.getCurrentLocation(request)
        try {
          geoLocationManager.getCurrentLocation({
            'locatingPriority': geoLocationManager.LocatingPriority.PRIORITY_LOCATING_SPEED,
            'locatingTimeoutMs': 10000
          }).then((result) => { // 调用getCurrentLocation获取当前设备位置,通过promise接收上报的位置
            console.log('slj 3 单次获取当前设备位置 current location: ' + JSON.stringify(result));
            // : {"latitude":40.11527531549031,"longitude":116.24535796600712,"altitude":0,"accuracy":11.084914,"speed":0,"timeStamp":1735980695625,"direction":0,"timeSinceBoot":99791209379781,"additionSize":1,"additions":["requestId:d2fc6c59-d95b-4f3f-a2d0-c1c4c7a28e8f"],"additionsMap":{},"altitudeAccuracy":0,"speedAccuracy":0,"directionAccuracy":0,"uncertaintyOfTimeSinceBoot":0,"sourceType":2}
            // 转化为地理位置信息
            let reverseGeocodeRequest:geoLocationManager.ReverseGeoCodeRequest = {"latitude": result.latitude, "longitude": result.longitude, "maxItems": 1, locale: 'zh'};
            try {
              geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest, (err, data) => {
                if (err) {
                  console.log('slj 3延伸 getAddressesFromLocation err: ' + JSON.stringify(err));
                } else {
                  console.log('slj 3延伸 getAddressesFromLocation data: ' + JSON.stringify(data));
                  console.log('slj ', data[0].latitude?.toString())
                  console.log('slj ', data[0].longitude?.toString())
                  console.log('slj ', data[0].placeName)
                  this.latitude = data[0].latitude as number
                  this.longitude = data[0].longitude as number
                  this.content = data[0].placeName as string
                }
              });
            } catch (err) {
              console.error("errCode:" + JSON.stringify(err));
            }
            // ==============
          })
            .catch((error:BusinessError) => { // 接收上报的错误码
              console.error('promise, getCurrentLocation: error=' + JSON.stringify(error));
            });
        } catch (err) {
          console.error("errCode:" + JSON.stringify(err));
        }
        // end
      } catch (err) {
        console.error("errCode:" + err.code + ", message:"  + err.message);
      }
    }

    // 细节1 持续获取 (后台运行就会自动停止  得配置长时任务)
    // 细节2 把经度纬度转换为具体明细地址 中文
  }

  build() {
    Column() {
      Map2()
    }
  }
}

鸿蒙开发者班级