Java生成与解析大疆无人机KMZ航线文件
目录
- 一、概述
- 1.template.kml 说明
- 2.waylines.wpml 说明
- 二、实现
- 1.实现思路
- 2.具体实现
- 2.1 引入 maven 依赖
- 2.2 使用 XStream 注解的 Java 类
- 2.3 生成 KMZ 文件
- 2.4 解析 KMZ 文件
- 2.5 测试
- 2.5.1 生成
- 2.5.2 解析
- 3.完整代码
一、概述
KMZ航线文件本质上是一个ZIP格式的压缩文件,一个标准的KMZ文件结构如下:
—— wpmz
|—— res
|—— template.kml
|—— waylines.wpml
- res:资源文件夹,用来存储航线所需的辅助资源文件(如图片、数据等)。
- template.kml:模板文件,定义了航线的业务属性,便于用户快速编辑和调整。
- waylines.wpml:执行文件,包含了无人机执行航线任务时的具体执行细节。
template.kml、waylines.wpml
和 res 资源文件夹都是航线文件格式标准的一部分,我们可以根据使用场景灵活调整文件结构。
如果需要将 KMZ 航线文件导入到 Pilot2 中使用,可以只生成 template.kml 文件,Pilot2 可以根据 template 文件生成 waylines 文件。
除特殊使用场景外(精准复拍前,准备参考目标照片等),可以不生成 res 文件夹。
1.template.kml 说明
template.kml是模板文件
,可以被 DJI Pilot 2、DJI Flighthub 2 或者其它软件解析,生成最终提供给无人机执行的路径和动作,即waylines.wpml文件。
template.kml文件由三部分组成:
- 创建信息:主要包含航线文件本身的信息,例如
文件的创建、更新时间
等。 - 任务信息:主要包含
wpml:missionConfig元素,定义航线任务的全局参数等
。 - 模板信息:主要包含
Folder元素,定义航线的模板信息
(如航点飞行、建图航拍、倾斜摄影、航带飞行等)。不同航线模板类型包含的元素不同。
示例文件
template.kml示例文件如下(以航点飞行模板为例):
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/1.0.6"><Document><wpml:author>Cleaner</wpml:author><wpml:createTime>1756429425069</wpml:createTime><wpml:updateTime>1756429425069</wpml:updateTime><wpml:missionConfig><wpml:finishAction>goHome</wpml:finishAction><wpml:exitOnRCLost>goContinue</wpml:exitOnRCLost><wpml:executeRCLostAction>goBack</wpml:executeRCLostAction><wpml:takeOffSecurityHeight>20</wpml:takeOffSecurityHeight><wpml:globalTransitionalSpeed>15</wpml:globalTransitionalSpeed><wpml:globalRTHHeight>100</wpml:globalRTHHeight><wpml:takeOffRefPoint>22.579831,113.937935,32.536774</wpml:takeOffRefPoint><wpml:takeOffRefPointAGLHeight>0</wpml:takeOffRefPointAGLHeight></wpml:missionConfig><Folder><wpml:templateType>waypoint</wpml:templateType><wpml:templateId>0</wpml:templateId><wpml:autoFlightSpeed>15</wpml:autoFlightSpeed><wpml:waylineCoordinateSysParam><wpml:coordinateMode>WGS84</wpml:coordinateMode><wpml:heightMode>EGM96</wpml:heightMode></wpml:waylineCoordinateSysParam><wpml:globalWaypointTurnMode>toPointAndStopWithDiscontinuityCurvature</wpml:globalWaypointTurnMode><wpml:globalUseStraightLine>1</wpml:globalUseStraightLine><wpml:gimbalPitchMode>manual</wpml:gimbalPitchMode><wpml:globalHeight>120</wpml:globalHeight><Placemark><wpml:isRisky>0</wpml:isRisky><Point><coordinates>113.935612998,22.583152266</coordinates></Point><wpml:index>0</wpml:index><wpml:useGlobalHeight>1</wpml:useGlobalHeight><wpml:ellipsoidHeight>123.495649717576</wpml:ellipsoidHeight><wpml:height>127</wpml:height><wpml:useGlobalSpeed>1</wpml:useGlobalSpeed><wpml:waypointSpeed>10</wpml:waypointSpeed><wpml:useGlobalTurnParam>1</wpml:useGlobalTurnParam><wpml:useStraightLine>1</wpml:useStraightLine><wpml:useGlobalHeadingParam>1</wpml:useGlobalHeadingParam></Placemark><Placemark><wpml:isRisky>0</wpml:isRisky><Point><coordinates>113.935821421,22.582421727</coordinates></Point><wpml:index>1</wpml:index><wpml:useGlobalHeight>1</wpml:useGlobalHeight><wpml:ellipsoidHeight>123.497734208512</wpml:ellipsoidHeight><wpml:height>127</wpml:height><wpml:useGlobalSpeed>1</wpml:useGlobalSpeed><wpml:waypointSpeed>10</wpml:waypointSpeed><wpml:useGlobalTurnParam>1</wpml:useGlobalTurnParam><wpml:useStraightLine>1</wpml:useStraightLine><wpml:useGlobalHeadingParam>1</wpml:useGlobalHeadingParam></Placemark><Placemark><wpml:isRisky>0</wpml:isRisky><Point><coordinates>113.936045587,22.581940624</coordinates></Point><wpml:index>2</wpml:index><wpml:useGlobalHeight>1</wpml:useGlobalHeight><wpml:ellipsoidHeight>123.499437353844</wpml:ellipsoidHeight><wpml:height>127</wpml:height><wpml:useGlobalSpeed>1</wpml:useGlobalSpeed><wpml:waypointSpeed>10</wpml:waypointSpeed><wpml:useGlobalTurnParam>1</wpml:useGlobalTurnParam><wpml:useStraightLine>1</wpml:useStraightLine><wpml:useGlobalHeadingParam>1</wpml:useGlobalHeadingParam></Placemark><Placemark><wpml:isRisky>0</wpml:isRisky><Point><coordinates>113.936284025,22.581568969</coordinates></Point><wpml:index>3</wpml:index><wpml:useGlobalHeight>1</wpml:useGlobalHeight><wpml:ellipsoidHeight>123.501001190067</wpml:ellipsoidHeight><wpml:height>127</wpml:height><wpml:useGlobalSpeed>1</wpml:useGlobalSpeed><wpml:waypointSpeed>10</wpml:waypointSpeed><wpml:useGlobalTurnParam>1</wpml:useGlobalTurnParam><wpml:useStraightLine>1</wpml:useStraightLine><wpml:useGlobalHeadingParam>1</wpml:useGlobalHeadingParam></Placemark><Placemark><wpml:isRisky>0</wpml:isRisky><Point><coordinates>113.93668438,22.581484696</coordinates></Point><wpml:index>4</wpml:index><wpml:useGlobalHeight>1</wpml:useGlobalHeight><wpml:ellipsoidHeight>123.502672008343</wpml:ellipsoidHeight><wpml:height>127</wpml:height><wpml:useGlobalSpeed>1</wpml:useGlobalSpeed><wpml:waypointSpeed>10</wpml:waypointSpeed><wpml:useGlobalTurnParam>1</wpml:useGlobalTurnParam><wpml:useStraightLine>1</wpml:useStraightLine><wpml:useGlobalHeadingParam>1</wpml:useGlobalHeadingParam></Placemark><Placemark><wpml:isRisky>0</wpml:isRisky><Point><coordinates>113.937234332,22.581549609</coordinates></Point><wpml:index>5</wpml:index><wpml:useGlobalHeight>1</wpml:useGlobalHeight><wpml:ellipsoidHeight>123.504647426208</wpml:ellipsoidHeight><wpml:height>127</wpml:height><wpml:useGlobalSpeed>1</wpml:useGlobalSpeed><wpml:waypointSpeed>10</wpml:waypointSpeed><wpml:useGlobalTurnParam>1</wpml:useGlobalTurnParam><wpml:useStraightLine>1</wpml:useStraightLine><wpml:useGlobalHeadingParam>1</wpml:useGlobalHeadingParam></Placemark></Folder></Document>
</kml>
2.waylines.wpml 说明
waylines.wpml
是飞机直接执行的文件,它定义了明确的无人机飞行和负载动作指令,这些指令由 DJI Pilot 2、DJI Flighthub 2 或者其它软件生成,也可被开发者直接编辑开发。
waylines.wpml文件由两部分组成:
- 任务信息:主要包含
wpml:missionConfig元素,定义航线任务的全局参数等
。 - 航线信息:主要包含
Folder元素,定义详细的航线信息(路径定义、动作定义等)
。每个Folder代表一条可执行的航线。特别的,当使用“倾斜摄影”模板时,将生成5条可执行航线,对应waylines.wpml内的5个Folder元素。
示例文件
waylines.wpml示例文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:wpml="http://www.dji.com/wpmz/1.0.6"><Document><wpml:missionConfig><wpml:finishAction>goHome</wpml:finishAction><wpml:exitOnRCLost>goContinue</wpml:exitOnRCLost><wpml:executeRCLostAction>goBack</wpml:executeRCLostAction><wpml:takeOffSecurityHeight>20</wpml:takeOffSecurityHeight><wpml:globalTransitionalSpeed>15</wpml:globalTransitionalSpeed><wpml:globalRTHHeight>100</wpml:globalRTHHeight></wpml:missionConfig><Folder><wpml:templateId>0</wpml:templateId><wpml:autoFlightSpeed>15</wpml:autoFlightSpeed><Placemark><wpml:isRisky>0</wpml:isRisky><Point><coordinates>113.935612998,22.583152266</coordinates></Point><wpml:index>0</wpml:index><wpml:waypointSpeed>10</wpml:waypointSpeed><wpml:useStraightLine>1</wpml:useStraightLine><wpml:executeHeight>123.495649717576</wpml:executeHeight></Placemark><Placemark><wpml:isRisky>0</wpml:isRisky><Point><coordinates>113.935821421,22.582421727</coordinates></Point><wpml:index>1</wpml:index><wpml:waypointSpeed>10</wpml:waypointSpeed><wpml:useStraightLine>1</wpml:useStraightLine><wpml:executeHeight>123.497734208512</wpml:executeHeight></Placemark><Placemark><wpml:isRisky>0</wpml:isRisky><Point><coordinates>113.936045587,22.581940624</coordinates></Point><wpml:index>2</wpml:index><wpml:waypointSpeed>10</wpml:waypointSpeed><wpml:useStraightLine>1</wpml:useStraightLine><wpml:executeHeight>123.499437353844</wpml:executeHeight></Placemark><Placemark><wpml:isRisky>0</wpml:isRisky><Point><coordinates>113.936284025,22.581568969</coordinates></Point><wpml:index>3</wpml:index><wpml:waypointSpeed>10</wpml:waypointSpeed><wpml:useStraightLine>1</wpml:useStraightLine><wpml:executeHeight>123.501001190067</wpml:executeHeight></Placemark><Placemark><wpml:isRisky>0</wpml:isRisky><Point><coordinates>113.93668438,22.581484696</coordinates></Point><wpml:index>4</wpml:index><wpml:waypointSpeed>10</wpml:waypointSpeed><wpml:useStraightLine>1</wpml:useStraightLine><wpml:executeHeight>123.502672008343</wpml:executeHeight></Placemark><Placemark><wpml:isRisky>0</wpml:isRisky><Point><coordinates>113.937234332,22.581549609</coordinates></Point><wpml:index>5</wpml:index><wpml:waypointSpeed>10</wpml:waypointSpeed><wpml:useStraightLine>1</wpml:useStraightLine><wpml:executeHeight>123.504647426208</wpml:executeHeight></Placemark><wpml:executeHeightMode>WGS84</wpml:executeHeightMode><wpml:waylineId>0</wpml:waylineId></Folder></Document>
</kml>
二、实现
1.实现思路
大疆开发者平台上云 API 文档中详细介绍了航线文件格式标准以及文件中具体的元素、名称以及取值和释义。
本文主要介绍如何生成和解析航点飞行模板类型的 KMZ 航线文件,具体的实现思路如下:
-
KMZ 文件生成
:首先生成一个 wpmz 文件夹,然后在 wpmz 文件夹下,生成 template.kml 文件和 waylines.wpml 文件,然后将 wpmz 文件夹压缩成 .zip 格式,最后修改 .zip 文件后缀为 .kmz。 -
KMZ 文件解析
:上传 KMZ 文件,对 KMZ 文件解压缩,获取到 template.kml 和 waylines.wpml 文件,然后分别解析 template.kml 文件和 waylines.wpml 文件。
template.kml 和 waylines.wpml 文件都是 XML 格式,而且文件中包含多个元素标签,每个元素标签又有不同的属性,航线文件内容比较灵活,因此可以使用 Java 类库 XStream 来方便的将 Java 对象和 XML 文档相互转换,航线文件中每个元素标签可以对应一个 Java Bean,通过操作这些 Java Bean 来生成和解析 KMZ 航线文件。
2.具体实现
由于代码量较大,这里主要贴一下关键代码
2.1 引入 maven 依赖
<!--航线kmz文件生成与解析-->
<dependency><groupId>com.thoughtworks.xstream</groupId><artifactId>xstream</artifactId><version>1.4.20</version>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-compress</artifactId><version>1.26.0</version>
</dependency>
2.2 使用 XStream 注解的 Java 类
package com.example.djpointdemo.dao.kml;import com.fasterxml.jackson.annotation.JsonInclude;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import lombok.Data;/*** @author qzz* @date 2025/8/12*/
@Data
@XStreamAlias("kml")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class KmlInfo {@XStreamAsAttribute@XStreamAlias("xmlns")private String xmlns = "http://www.opengis.net/kml/2.2";@XStreamAsAttribute@XStreamAlias("xmlns:wpml")private String wpml = "http://www.dji.com/wpmz/1.0.6";@XStreamAlias("Document")private KmlDocument document;
}
2.3 生成 KMZ 文件
/*** 生成航线 KMZ 文件** @param fileName 文件名* @param kmlParams 参数对象* @return 本地文件路径*/public static String buildKmz(String fileName, KmlParams kmlParams) {//深度拷贝文件KmlParams templateParams = getTemplateParams(kmlParams);KmlInfo kmlInfo = buildKml(templateParams);KmlInfo wpmlInfo = buildWpml(getWayLinesParams(kmlParams));return buildKmz(fileName, kmlInfo, wpmlInfo);}
/*** 生成航线 KMZ 文件** @param fileName 文件名* @param kmlInfo kml 文件信息* @param wpmlInfo wpml 文件信息* @return 本地文件路径*/public static String buildKmz(String fileName, KmlInfo kmlInfo, KmlInfo wpmlInfo) {XStream xStream = new XStream(new DomDriver());xStream.processAnnotations(KmlInfo.class);
// xStream.addImplicitCollection(KmlActionGroup.class, "action");String kml = XML_HEADER + xStream.toXML(kmlInfo);String wpml = XML_HEADER + xStream.toXML(wpmlInfo);String destFilePath = LOCAL_KMZ_FILE_PATH + fileName + ".kmz";File file = new File(destFilePath);File parentFile = file.getParentFile();if (parentFile != null && !parentFile.exists()) {parentFile.mkdirs();}try (FileOutputStream fileOutputStream = new FileOutputStream(destFilePath);ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream)) {zipOutputStream.setLevel(0); // 0 表示不压缩,存储方式// 创建 wpmz 目录中的 template.kml 文件条目buildZipFile("wpmz/template.kml", zipOutputStream, kml);// 创建 wpmz 目录中的 waylines.wpml 文件条目buildZipFile("wpmz/waylines.wpml", zipOutputStream, wpml);} catch (Exception e) {throw new RuntimeException(e);}return LOCAL_KMZ_FILE_PATH + fileName + ".kmz";}
KmlParams.java
package com.example.djpointdemo.dao.kml;import lombok.Data;
import java.util.List;/*** 航线文件*/
@Data
public class KmlParams {/*** 任务信息*/private KmlMissionConfig kmlMissionConfig;/*** 模板信息:航点飞行模板信息 设置List的理由:每个Folder代表一条可执行的航线。特别的,当使用“倾斜摄影”模板时,将生成5条可执行航线,对应waylines.wpml内的5个Folder元素。*/private List<KmlFolder> folder;
}
2.4 解析 KMZ 文件
/*** 解析kmz文件 本地文件* @param fileUrl* @return*/@Overridepublic KmlInfo parseKmzLocalFile(String fileUrl) {//File是用来操作本地文件系统路径的,不支持 HTTP URLFile file = new File(fileUrl);try (ArchiveInputStream archiveInputStream = new ZipArchiveInputStream(FileUtil.getInputStream(file))) {ArchiveEntry entry;KmlInfo kmlInfo = new KmlInfo();KmlInfo wpmlInfo = new KmlInfo();while (!Objects.isNull(entry = archiveInputStream.getNextEntry())) {String name = entry.getName();if (name.toLowerCase().endsWith(".kml")) {kmlInfo = ParseFileUtils.parseKml(archiveInputStream);} else if (name.toLowerCase().endsWith(".wpml")) {wpmlInfo = ParseFileUtils.parseKml(archiveInputStream);}}return wpmlInfo;} catch (Exception e) {e.printStackTrace();}return null;}/*** kml文件解析** @param inputStream* @return KmlInfo*/public static KmlInfo parseKml(InputStream inputStream) {XStream xStream = new XStream();xStream.allowTypes(new Class[]{KmlInfo.class, KmlWayLineCoordinateSysParam.class, KmlPoint.class});xStream.alias("kml", KmlInfo.class);xStream.processAnnotations(KmlInfo.class);xStream.autodetectAnnotations(true);xStream.ignoreUnknownElements();
// xStream.addImplicitCollection(KmlActionGroup.class, "action");return (KmlInfo) xStream.fromXML(inputStream);}
2.5 测试
/*** 解析kmz文件 本地文件* @param fileUrl* @return*/@GetMapping("/parseKmzLocalFile")public KmlInfo parseKmzLocalFile(@RequestParam("fileUrl") String fileUrl){return djAirLineParseService.parseKmzLocalFile(fileUrl);}/*** 生成kmz文件* @param kmlParams* @return*/@PostMapping("/createKmz")public String createKmz(@RequestBody KmlParams kmlParams){return djAirLineParseService.createKmz(kmlParams);}
请求示例:
2.5.1 生成
post请求:http://localhost:8080/createKmz
传参:
{"kmlMissionConfig":{"flyToWaylineMode": "safely","finishAction": "goHome","exitOnRCLost": "goContinue","executeRCLostAction": "goBack","takeOffSecurityHeight": "20","globalTransitionalSpeed": "15","globalRTHHeight": "100","takeOffRefPoint": "22.579831,113.937935,32.536774", // kml"takeOffRefPointAGLHeight": "0", //kml"droneInfo": {"droneEnumValue": "100","droneSubEnumValue": "0"},"payloadInfo": {"payloadEnumValue": "98","payloadPositionIndex": "0"}},"folder": [{"templateType": "waypoint",//kml"templateId": "0","autoFlightSpeed": "15","waylineCoordinateSysParam": {//kml"coordinateMode": "WGS84","heightMode": "EGM96"},"globalWaypointTurnMode": "toPointAndStopWithDiscontinuityCurvature",//kml"globalUseStraightLine": "1",//kml"gimbalPitchMode": "manual",//kml"globalHeight": "120",//kml"placemarkList": [{"isRisky": "0","kmlPoint": {"coordinates": "\n 113.935612998,22.583152266\n "},"index": "0","useGlobalHeight": "1",//kml"ellipsoidHeight": "123.495649717576",//kml"height": "127",//kml "useGlobalSpeed": "1",//kml"waypointSpeed": "10","useGlobalTurnParam": "1",//kml"useStraightLine": "1","executeHeight": "123.495649717576",//wpml"useGlobalHeadingParam":1},{"isRisky": "0","kmlPoint": {"coordinates": "\n 113.935821421,22.582421727\n "},"index": "1","useGlobalHeight": "1","ellipsoidHeight": "123.497734208512","height": "127", "useGlobalSpeed": "1","waypointSpeed": "10","useGlobalTurnParam": "1","useStraightLine": "1","executeHeight": "123.497734208512","useGlobalHeadingParam":1},{"isRisky": "0","kmlPoint": {"coordinates": "\n 113.936045587,22.581940624\n "},"index": "2","useGlobalHeight": "1","ellipsoidHeight": "123.499437353844","height": "127", "useGlobalSpeed": "1","waypointSpeed": "10","useGlobalTurnParam": "1","useStraightLine": "1","executeHeight": "123.499437353844","useGlobalHeadingParam":1},{"isRisky": "0","kmlPoint": {"coordinates": "\n 113.936284025,22.581568969\n "},"index": "3","useGlobalHeight": "1","ellipsoidHeight": "123.501001190067","height": "127", "useGlobalSpeed": "1","waypointSpeed": "10","useGlobalTurnParam": "1","useStraightLine": "1","executeHeight": "123.501001190067","useGlobalHeadingParam":1},{"isRisky": "0","kmlPoint": {"coordinates": "\n 113.93668438,22.581484696\n "},"index": "4","useGlobalHeight": "1","ellipsoidHeight": "123.502672008343","height": "127", "useGlobalSpeed": "1","waypointSpeed": "10","useGlobalTurnParam": "1","useStraightLine": "1","executeHeight": "123.502672008343","useGlobalHeadingParam":1},{"isRisky": "0","kmlPoint": {"coordinates": "\n 113.937234332,22.581549609\n "},"index": "5","useGlobalHeight": "1","ellipsoidHeight": "123.504647426208","height": "127", "useGlobalSpeed": "1","waypointSpeed": "10","useGlobalTurnParam": "1","useStraightLine": "1","executeHeight": "123.504647426208","useGlobalHeadingParam":1}],"executeHeightMode":"WGS84",//wpml"waylineId": "0"//wpml}]
}
2.5.2 解析
get请求:http://localhost:8080/parseKmzLocalFile?fileUrl=C://Users//Admin//Downloads//test.kmz
3.完整代码
点击此处下载