HarmonyOS 鸿蒙Next中Map Kit如何加载kmz格式图层

HarmonyOS 鸿蒙Next中Map Kit如何加载kmz格式图层 Map Kit地图服务当前不支持直接加载kmz格式图层,如何通过已有接口实现kmz格式的图层加载?

9 回复

【背景知识】

addImageOverlay:在地图上增加图覆盖物。

【解决方案】

方案逻辑:Map Kit地图服务当前不支持直接加载kmz格式图层,通过解压kmz格式文件,可以发现其内容为多张覆盖全球的图片和一个描述每张图片位置信息的kml文件。

本方案通过读取kml文件,获取所有文件的位置坐标信息,并通过addImageOverlay依次添加所有图片到对应的坐标位置,实现完整地球图层的添加。

1. 将kmz文件中的kml文件放在rawfile目录下,使用getRawFileContent读取文件内容,并使用三方库@ifbear/fast-xml-parser中的XMLParser将kml格式内容转换为json格式内容。

let context = this.getUIContext().getHostContext();
try {
  context?.resourceManager.getRawFileContent('doc.kml', (error, value) => {
    if (error != null) {
      console.info(`error is:${error}`);
    } else {
      let rawFile = value;
      let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
      let rawFileString = textDecoder.decodeToString(rawFile, { stream: false });
      console.info(`rawFileString is ${rawFileString}`);
      const parser = new XMLParser();
      this.xmlContent = parser.parse(rawFileString) as object;
      this.addImageOverlays();
    }
  });
} catch (error) {
  let code = (error as BusinessError).code;
  let message = (error as BusinessError).message;
  console.error(`callback getRawFileContent failed, error code: ${code}, message: ${message}.`);
}

2. 遍历第第1步获取的json描述文件内容,获取到每个图片的名称和坐标信息,依次配置ImageOverlayParams参数并通过addImageOverlay添加图片覆盖物。

async addImageOverlays() {
  // 不同的kml文件中,坐标信息在json中的位置可能不一样,根据实际的json结构进行调用
  for (let i = 0; i < (this.xmlContent as object)['kml']['Document']['GroundOverlay'].length; ++i) {
    let imageOverlayParams: mapCommon.ImageOverlayParams = {
      // 覆盖物范围
      bounds: {
        southwest: {
          latitude: (this.xmlContent as object)['kml']['Document']['GroundOverlay'][i]['LatLonBox']['south'] as number,
          longitude: (this.xmlContent as object)['kml']['Document']['GroundOverlay'][i]['LatLonBox']['west'] as number
        },
        northeast: {
          latitude: (this.xmlContent as object)['kml']['Document']['GroundOverlay'][i]['LatLonBox']['north'] as number,
          longitude: (this.xmlContent as object)['kml']['Document']['GroundOverlay'][i]['LatLonBox']['east'] as number
        }
      },
      // 覆盖物图片,图标需存放在resources/rawfile/files目录下
      image: (this.xmlContent as object)['kml']['Document']['GroundOverlay'][i]['Icon']['href'],
      transparency: 0.3,
      zIndex: 101,
      anchorU: 0.5,
      anchorV: 0.5,
      clickable: true,
      visible: true,
      bearing: 0
    };
    // 添加覆盖物
    try {
      let imageOverlay = await this.mapController?.addImageOverlay(imageOverlayParams);
      console.info(`Add imageOverlay success,imageOverlay content is ${imageOverlay}`);
    } catch (e) {
      console.error(`Failed to create the imageOverlay, code is:${e.code}, message is ${e.message}`);
    }
  }
}

实现效果:

cke_5282.png

完整代码:

import { util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { XMLParser } from "[@ifbear](/user/ifbear)/fast-xml-parser";
import { map, mapCommon, MapComponent } from '@kit.MapKit';
import { AsyncCallback } from '@kit.BasicServicesKit';

@Entry
@Component
struct AddXmzOverlay {
  @State xmlContent?: object = undefined;
  private mapOptions?: mapCommon.MapOptions;
  private mapController?: map.MapComponentController;
  private callback?: AsyncCallback<map.MapComponentController>;

  aboutToAppear(): void {
    this.mapOptions = {
      position: {
        target: {
          latitude: -51.4624451955,
          longitude: 171.62500335
        },
        zoom: 2
      }
    };

    this.callback = async (err, mapController) => {
      if (!err) {
        this.mapController = mapController;
      } else {
        console.error(`Failed to initialize the map, code is:${err.code}, message is ${err.message}`);
      }
    };
  }
  async addImageOverlays() {
    // 不同的kml文件中,坐标信息在json中的位置可能不一样,根据实际的json结构进行调用
    for (let i = 0; i < (this.xmlContent as object)['kml']['Document']['GroundOverlay'].length; ++i) {
      let imageOverlayParams: mapCommon.ImageOverlayParams = {
        // 覆盖物范围
        bounds: {
          southwest: {
            latitude: (this.xmlContent as object)['kml']['Document']['GroundOverlay'][i]['LatLonBox']['south'] as number,
            longitude: (this.xmlContent as object)['kml']['Document']['GroundOverlay'][i]['LatLonBox']['west'] as number
          },
          northeast: {
            latitude: (this.xmlContent as object)['kml']['Document']['GroundOverlay'][i]['LatLonBox']['north'] as number,
            longitude: (this.xmlContent as object)['kml']['Document']['GroundOverlay'][i]['LatLonBox']['east'] as number
          }
        },
        // 覆盖物图片,图标需存放在resources/rawfile/files目录下
        image: (this.xmlContent as object)['kml']['Document']['GroundOverlay'][i]['Icon']['href'],
        transparency: 0.3,
        zIndex: 101,
        anchorU: 0.5,
        anchorV: 0.5,
        clickable: true,
        visible: true,
        bearing: 0
      };
      // 添加覆盖物
      try {
        let imageOverlay = await this.mapController?.addImageOverlay(imageOverlayParams);
        console.info(`Add imageOverlay success,imageOverlay content is ${imageOverlay}`);
      } catch (e) {
        console.error(`Failed to create the imageOverlay, code is:${e.code}, message is ${e.message}`);
      }
    }
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      Column() {
        MapComponent({
          mapOptions: this.mapOptions,
          mapCallback: this.callback,
        })
          .width('100%')
          .height('100%');
      }.width('100%')

      // 加载图层
      Button('加载kmz图层')
        .onClick(() => {
          let context = this.getUIContext().getHostContext();
          try {
            context?.resourceManager.getRawFileContent('doc.kml', (error, value) => {
              if (error != null) {
                console.info(`error is:${error}`);
              } else {
                let rawFile = value;
                let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true });
                let rawFileString = textDecoder.decodeToString(rawFile, { stream: false });
                console.info(`rawFileString is ${rawFileString}`);
                const parser = new XMLParser();
                this.xmlContent = parser.parse(rawFileString) as object;
                this.addImageOverlays();
              }
            });
          } catch (error) {
            let code = (error as BusinessError).code;
            let message = (error as BusinessError).message;
            console.error(`callback getRawFileContent failed, error code: ${code}, message: ${message}.`);
          }
        })
        .margin(20)

    }.height('100%')
  }
}

更多关于HarmonyOS 鸿蒙Next中Map Kit如何加载kmz格式图层的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


https://developer.huawei.com/consumer/cn/doc/architecture-guides/convenient-life-v1_2-ts_137-0000002407690692

  • 覆盖物:覆盖物是一种位于底图和底图标注层之间的特殊Overlay,该图层不会遮挡地图标注信息。通过ImageOverlayParams类来设置,开发者可以通过ImageOverlayParams类设置一张图片,该图片可随地图的平移、缩放、旋转等操作做相应的变换。
  • ImageOverlayParams类中image参数只支持ResourceStr和image.PixelMap,无法直接添加网络图片url,可以把网络图片的url转换成PixelMap类型显示。

KMZ 本质上是压缩包,里面通常是 KML + 图片资源。Map Kit 不能直接加载 KMZ 时,可以在应用侧先解压:解析 KML 里的 GroundOverlay/LatLonBox 或 gx:LatLonQuad,拿到每张图片对应的经纬度范围,再用 Map Kit 的图片覆盖物能力逐张添加。

要注意三点:1)KMZ 里的图片路径是相对 KML 的,解压后要重建资源映射;2)覆盖全球的大图层可能切片很多,最好按当前视野懒加载/卸载;3)KML 支持的样式很多,不要一开始承诺完整兼容,先支持你们业务里用到的 GroundOverlay/Placemark 子集。

同问,

不清楚

能适配其他ios系统吗,

哈?适配IOS?,

HarmonyOS 鸿蒙Next中Map Kit不支持直接加载KMZ。需先将KMZ解压为KML(KMZ是ZIP压缩包),再用GeoJsonLayerKmlLayer加载KML文件。若需显示图片等附件,需自行解析并处理相对路径。

KMZ本质是ZIP压缩包,核心是KML文件。可自行解压解析后,利用MapKit的已有覆盖物接口绘制。

具体步骤:

  1. 解压KMZ:使用@ohos/zlib解压,读取内部doc.kml
  2. 解析KML:正则或XML解析(如@ohos/xml)提取Placemark中的坐标与几何类型。
  3. 转换与绘制:将KML坐标转为LatLng,根据几何类型调用对应接口:
    • PointmapController.addMarker()
    • LineStringmapController.addPolyline()
    • PolygonmapController.addPolygon()
    • 可读取Style设置颜色、线宽等。

示例(核心逻辑):

import { zlib } from '@ohos/zlib';

async function loadKMZ(kmzPath: string, mapCtrl: map.MapController) {
  // 解压
  const uncompressed = await zlib.unzipFile(kmzPath);
  const kml = uncompressed.get('doc.kml') as string;
  
  // 解析坐标(简写,实际需完整解析XML)
  const placemarks = parseKML(kml); // 返回[{type, coordinates, style}]
  
  for (const pm of placemarks) {
    const latsLngs = pm.coordinates.map(c => 
      new map.LatLng(c.lat, c.lng));
    
    switch (pm.type) {
      case 'Point':
        mapCtrl.addMarker(new map.Marker({ position: latsLngs[0] }));
        break;
      case 'LineString':
        mapCtrl.addPolyline(new map.Polyline({
          points: latsLngs,
          width: 4,
          color: pm.color || 0xFF0000FF
        }));
        break;
      case 'Polygon':
        mapCtrl.addPolygon(new map.Polygon({
          points: latsLngs,
          fillColor: pm.fillColor || 0x330000FF,
          strokeColor: pm.color || 0xFF0000FF
        }));
        break;
    }
  }
}

注意:KML中的<MultiGeometry>需递归处理,<gx:Track>等复杂结构可先忽略。坐标顺序为经度,纬度(可能含海拔),需按parseFloat处理。

回到顶部