在开发大疆MSDKV5版本的过程中我遇到个比较egg疼的问题,大疆航线只能通过上传kmz航线文件来执行航线任务。那么以前V4版本写的KML是不能直接使用的,问题来了,他们并没有提供相关的转换接口文档一键转换,号称可以通过大疆司空或者把kml导入pilot2里自动转换,我们的需求大多数都是一套执行的,估计没有几家公司会把自己的航线处理后,再人工导入到pilot2里再人工拿出来手动导入自己的项目里上传执行,都这样玩了直接pilot2不香吗,所以我就自己写了转换。
在线版请参考大疆V5文件KMZ在线生成
这个是离线版本
可定制度比较高,需要自己手动拼接自己想要的参数,相对复杂:
流程是从服务端或者本地生成好的KML航线,把文件提取出来后,使用Dom解析提取出对应的坐标点经、纬、高度,以及对应的航点动作、拍照、悬停、录像、兴趣点等,再Dom合成对应的kmz文件,对应的结构文件如下

编辑MSDKV5航线KMZ文件的时候,结构必须按照上面的结构不能错不能省,里面的res可以不要,template.kml和waylines.wpml两个文件必须得有,最早我是把两个文件的航线执行都写出来了,后来发现大疆文档上说:若是pilot2执行KMZ则只需要编写template.kml,若是通过MSDK执行KMZ可以只把航点和航点动过写在waylines.wpml里,再写个无航点和无动作的template.kml文件打包。
官方提供的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.2">
<Document></Document>
</kml>
然后是template.kml的编写方式,我这里提供相关的编写方式,代码简单看看就好,下面会给文档的
private int createFolder() {
if (docElement != null) {
Element folderElement = docElement.addElement("Folder");
//模板ID,范围:[0, 65535]
folderElement.addElement("wpml:templateId").setText(String.valueOf(flightMission.getMissionId()));
//航线ID 在一条航线中该ID唯一 [0, 65535]
folderElement.addElement("wpml:waylineId").setText(String.valueOf(flightMission.getMissionId()));
//全局航线飞行速度
folderElement.addElement("wpml:autoFlightSpeed").setText(String.valueOf(flightMission.getSpeed()));
//执行高度模式 该元素仅在waylines.wpml中使用
// folderElement.addElement("wpml:executeHeightMode").setText("WGS84");
folderElement.addElement("wpml:executeHeightMode").setText("relativeToStartPoint");
if (pointList != null && pointList.size() > 0) {
//航点信息(包括航点经纬度和高度等)
for (int i = 0; i < pointList.size(); i++) {
addPointToPlacemark(folderElement, pointList.get(i), pointList.size(), i);
}
}
return 0;
} else {
return -1;
}
}
private boolean addPointToPlacemark(Element parentElement, TaskKMLBean missionPoint, int size, int index) {
if (missionPoint == null || missionPoint.getLatitude() == 0 || missionPoint.getLongitude() == 0) {
return false;
} else {
Element placemarkElement = parentElement.addElement("Placemark");
Element point = placemarkElement.addElement("Point");
//航点经纬度<经度,纬度>
point.addElement("coordinates")
.setText(missionPoint.getLongitude() + "," + missionPoint.getLatitude());
//航点序号
placemarkElement.addElement("wpml:index")
.setText(String.valueOf(index));
//航点执行高度
placemarkElement.addElement("wpml:executeHeight")
.setText(String.valueOf(missionPoint.getAltitude()));
//航点飞行速度,当前航点飞向下一个航点的速度
placemarkElement.addElement("wpml:waypointSpeed")
.setText(String.valueOf(missionPoint.getSpeed()));
//偏航角模式参数
Element waypointHeadingParamElememt = placemarkElement.addElement("wpml:waypointHeadingParam");
waypointHeadingParamElememt.addElement("wpml:waypointHeadingMode").setText(getHeadMode(flightMission.getHeadingMode()));
if (flightMission.getHeadingMode() == 3) {//表明为兴趣点
waypointHeadingParamElememt.addElement("wpml:waypointPoiPoint").setText(
missionPoint.getPointAction().getLookatlatitude() + ","
+ missionPoint.getPointAction().getLookatlongitude() + ","
+ missionPoint.getPointAction().getLookataltitude()
);
}
//飞行器偏航角度
waypointHeadingParamElememt.addElement("wpml:waypointHeadingAngle").setText("0");
waypointHeadingParamElememt.addElement("wpml:waypointHeadingPathMode").setText("followBadArc");
//航点类型(航点转弯模式
Element waypointTurnParamElement = placemarkElement.addElement("wpml:waypointTurnParam");
waypointTurnParamElement.addElement("wpml:waypointTurnMode").setText("toPointAndStopWithDiscontinuityCurvature");
waypointTurnParamElement.addElement("wpml:waypointTurnDampingDist").setText("0");
placemarkElement.addElement("wpml:useStraightLine").setText("1");
//*注:该元素用于规划一系列初始动作,在航线开始前执行。航线中断恢复时,先执行初始动作,再执行航点动作
Element actionGroupElement = placemarkElement.addElement("wpml:actionGroup");
//动作组id
//* 注:在一个kmz文件内该ID唯一。建议从0开始单调连续递增。[0, 65535]
actionGroupElement.addElement("wpml:actionGroupId").setText(String.valueOf(index));
//动作组开始生效的航点 [0, 65535]
actionGroupElement.addElement("wpml:actionGroupStartIndex").setText(String.valueOf(index));
actionGroupElement.addElement("wpml:actionGroupEndIndex").setText(String.valueOf(index));
//动作执行模式 sequence:串行执行。即动作组内的动作依次按顺序执行。
actionGroupElement.addElement("wpml:actionGroupMode").setText("sequence");
//动作组触发器
Element actionTriggerElement = actionGroupElement.addElement("wpml:actionTrigger");
actionTriggerElement.addElement("wpml:actionTriggerType").setText("reachPoint");
Element seconAactionElement = actionGroupElement.addElement("wpml:action");
seconAactionElement.addElement("wpml:actionId").setText("0");
seconAactionElement.addElement("wpml:actionActuatorFunc").setText("gimbalRotate");
Element secondActionActuatorFuncParamElement = seconAactionElement.addElement("wpml" +
":actionActuatorFuncParam");
secondActionActuatorFuncParamElement.addElement("wpml:gimbalHeadingYawBase").setText("aircraft");
secondActionActuatorFuncParamElement.addElement("wpml:gimbalRotateMode").setText("absoluteAngle");
secondActionActuatorFuncParamElement.addElement("wpml:gimbalPitchRotateEnable").setText("1");
secondActionActuatorFuncParamElement.addElement("wpml:gimbalPitchRotateAngle").setText(String.valueOf(missionPoint.getPitch()));
secondActionActuatorFuncParamElement.addElement("wpml:gimbalRollRotateEnable").setText("0");
secondActionActuatorFuncParamElement.addElement("wpml:gimbalRollRotateAngle").setText("0");
secondActionActuatorFuncParamElement.addElement("wpml:gimbalYawRotateEnable").setText("0");
secondActionActuatorFuncParamElement.addElement("wpml:gimbalYawRotateAngle").setText("0");
secondActionActuatorFuncParamElement.addElement("wpml:gimbalRotateTimeEnable").setText("0");
secondActionActuatorFuncParamElement.addElement("wpml:gimbalRotateTime").setText("10");
secondActionActuatorFuncParamElement.addElement("wpml:payloadPositionIndex").setText("0");
Element firstActionElement = actionGroupElement.addElement("wpml:action");
firstActionElement.addElement("wpml:actionId").setText("1");
String firstAction = getActionActuatorFunc("1");
firstActionElement.addElement("wpml:actionActuatorFunc").setText(firstAction);
Element firstActionActuatorFuncParamElement = firstActionElement.addElement("wpml:actionActuatorFuncParam");
if (firstAction.equals("takePhoto")) {
firstActionActuatorFuncParamElement.addElement("wpml:payloadPositionIndex").setText("0");
firstActionActuatorFuncParamElement.addElement("wpml:fileSuffix").setText("point" + index);
} else if (firstAction.equals("startRecord")) {
firstActionActuatorFuncParamElement.addElement("wpml:payloadPositionIndex").setText("0");
firstActionActuatorFuncParamElement.addElement("wpml:fileSuffix").setText("point" + index);
firstActionActuatorFuncParamElement.addElement("wpml:payloadLensIndex").setText("wide,ir,narrow_band");
firstActionActuatorFuncParamElement.addElement("wpml:useGlobalPayloadLensIndex").setText("0");
} else if (firstAction.equals("stopRecord")) {
firstActionActuatorFuncParamElement.addElement("wpml:payloadPositionIndex").setText("0");
firstActionActuatorFuncParamElement.addElement("wpml:payloadLensIndex").setText("wide,ir,narrow_band");
} else if (firstAction.equals("hover")) {
firstActionActuatorFuncParamElement.addElement("wpml:hoverTime").setText("5");
} else if (firstAction.equals("rotateYaw")) {
firstActionActuatorFuncParamElement.addElement("wpml:aircraftHeading").setText("180");
firstActionActuatorFuncParamElement.addElement("wpml:aircraftPathMode").setText("clockwise");
}
return true;
}
}
这里只是摘取了部分解析方法,更复杂的动作什么的,大家可以根据官网里的格式自己修改链接如下:https://developer.dji.com/doc/cloud-api-tutorial/cn/api-reference/dji-wpml/overview.html
然后再把文件压缩成指定的kmz文件
private void addFolderToZip(File folder, String parentFolder, ZipOutputStream zos) throws Exception {
String baseFolder = new File(parentFolder).getName(); // 获取基础文件夹名称,例如 "107"
for (File file : folder.listFiles()) {
String relativePath = file.getAbsolutePath().substring(parentFolder.length() + 1); // 获取相对于基础文件夹的路径
if (file.isDirectory()) {
addFolderToZip(file, parentFolder + "/" + file.getName(), zos);
continue;
}
zos.putNextEntry(new ZipEntry(baseFolder + "/" + relativePath)); // 使用相对路径创建 ZipEntry
FileInputStream fis = new FileInputStream(file);
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) > 0) {
zos.write(buffer, 0, length);
}
zos.closeEntry();
fis.close();
}
}
然后可以执行航线任务了,可以查看”大疆V5MSDK航线任务“这篇文档避坑。
发表回复