机型:MT4D 遥控器 dji rc plus2
第一步:核心命令 fly_to_piont

第二步:想要实现航点效果必须要考虑的三个点
1、fly_to_piont、控制权、drc、一键起飞之间有什么关系,执行的前后顺序是什么
这个可以看上云api的指令飞行的任务流程图

2、如何解析kmz文件,并执行航线规定的任务
3、在执行fly_to_piont后如何得知飞机确实已经真的到达了目标点(核心)
三、实现步骤---只对上面三点进行介绍 因为上面很重要
1、fly_to_piont、控制权、drc、一键起飞之间的关系。执行的顺序是 控制权->drc->一键起飞->fly_to_piont
2、如何解析kmz文件,并执行航线规定的任务。这个可以有很多方法,但是最简单的是让前端同时在画航线时将json数据保存一下,然后在执行任务的时候直接穿json。因为kmz也是前端通过json转的,我们何必在将kmz转为json呢。
3、在执行fly_to_piont后如何得知飞机确实已经真的到达了目标点
这个就很重要了,我被困了两天
先贴代码,这是核心判断方法
java
// 如果飞行到目标点了就执行下一个目标点
if (status == FlyToStatusEnum.WAYLINE_OK && method.equals(EventsMethodEnum.FLY_TO_POINT_PROGRESS.getMethod())) {
// 先从Redis获取当前的航点索引
Object indexObj = RedisOpsUtils.get(RedisConst.WAYLINE_NEXT_POINT_INDEX + dockSn);
Object o = RedisOpsUtils.get(RedisConst.WAYLINE_NEXT_FLIGHT_ID + dockSn);
String currentFlightId = Objects.requireNonNullElse(o, "").toString();
int currentIndex = getCacheIndex(indexObj);
// todo 可以通过flightId来判断 当前wayline_ok 是否是同一个任务 同一个不需要保存 nextIndex
if (!Objects.equals(flightId, currentFlightId)){
// 计算下一个航点索引
Integer nextIndex = currentIndex + 1;
// 记录下一个航点
RedisOpsUtils.set(RedisConst.WAYLINE_NEXT_POINT_INDEX + dockSn, nextIndex);
RedisOpsUtils.set(RedisConst.WAYLINE_NEXT_FLIGHT_ID + dockSn, UUID.randomUUID().toString());
// 发布执行航线航点命令
System.out.println("即将要飞向的航点索引》》》》》" + nextIndex);
EventDrone build = EventDrone.builder().gateWaySn(dockSn).nextPointIndex(nextIndex).wayLineFileId(flyToId).build();
publisher.publishEvent(new PubFlightTaskEvent(build));
}
}
首先到达目标点有两种,一个是一键起飞到达目标点,另一个是我们航线到达目标点。
一键起飞到达目标点: 监听的method是takeoff_to_point_progress,这时会在data里有一个status是wayline_ok,但是!!!坑点来了,如果我们一键起飞是在楼顶上,那么在设置目标高度时就会出现虚高,然后会慢慢下降,但是fly_to_piont只能在飞机不动的情况下才能发出命令,负责会失败,所以我们还要先校验当前飞机是否停稳,经过我一天的测试发现当飞机停稳后前五秒和后五秒的高度差不超过0.05,所以我们可以用这种方式判断飞机就是停稳了,没有在执行其他飞行命令,一下代码是判断当前高度的。
/** * 检查高度是否在目标范围内 */ private boolean isHeightInTargetRange(String deviceSn) { // 获取设备高度 Optional<OsdSubDeviceReceiver> deviceOsd = deviceRedisService.getDeviceOsd( deviceSn, OsdSubDeviceReceiver.class ); if (deviceOsd.isEmpty()) { log.error("设备 {} OSD数据不存在", deviceSn); throw new CommonException(CommonErrorEnum.E_210364); } Double currentHeight = deviceOsd.get().getHeight(); sleepForInterval(5000); // 获取设备高度 Optional<OsdSubDeviceReceiver> deviceOsd2 = deviceRedisService.getDeviceOsd( deviceSn, OsdSubDeviceReceiver.class ); if (deviceOsd2.isEmpty()) { log.error("设备 {} OSD数据不存在", deviceSn); throw new CommonException(CommonErrorEnum.E_210364); } Double currentHeight2 = deviceOsd2.get().getHeight(); boolean b = Math.abs(currentHeight2 - currentHeight) <= 0.05; if (b){ log.info("设备 {} 达到目标高度: {}米", deviceSn, currentHeight2); } return b; }
航线到达目标点: 这个就更坑了,在监听method=fly_to_point_progress时,到达一个目标点后上云api竟然一下返回了很多个wayline_ok,这没办法判断是真的到了没呀,然后我就发现
flightId是唯一的,每次请求都是一个uuid,然后这就是一个天然的判断条件,使用类似分布式锁的想法,之所以不用分布式锁,是因为这个锁的时间不好把控。
// 如果飞行到目标点了就执行下一个目标点 if (status == FlyToStatusEnum.WAYLINE_OK && method.equals(EventsMethodEnum.FLY_TO_POINT_PROGRESS.getMethod())) { // 先从Redis获取当前的航点索引 Object indexObj = RedisOpsUtils.get(RedisConst.WAYLINE_NEXT_POINT_INDEX + dockSn); Object o = RedisOpsUtils.get(RedisConst.WAYLINE_NEXT_FLIGHT_ID + dockSn); String currentFlightId = Objects.requireNonNullElse(o, "").toString(); int currentIndex = getCacheIndex(indexObj); // todo 可以通过flightId来判断 当前wayline_ok 是否是同一个任务 同一个不需要保存 nextIndex if (!Objects.equals(flightId, currentFlightId)){ // 计算下一个航点索引 Integer nextIndex = currentIndex + 1; // 记录下一个航点 RedisOpsUtils.set(RedisConst.WAYLINE_NEXT_POINT_INDEX + dockSn, nextIndex); RedisOpsUtils.set(RedisConst.WAYLINE_NEXT_FLIGHT_ID + dockSn, UUID.randomUUID().toString()); // 发布执行航线航点命令 System.out.println("即将要飞向的航点索引》》》》》" + nextIndex); EventDrone build = EventDrone.builder().gateWaySn(dockSn).nextPointIndex(nextIndex).wayLineFileId(flyToId).build(); publisher.publishEvent(new PubFlightTaskEvent(build)); } }
然后最后就是一个执行 fly_to了
java
@Configuration
public class SubFlightLister implements ApplicationListener<PubFlightTaskEvent> {
@Autowired
private IDeviceRedisService deviceRedisService;
@Autowired
private IControlService controlService;
@Override
public void onApplicationEvent(PubFlightTaskEvent event) {
Object source = event.getSource();
if (source instanceof EventDrone eventDrone) {
String gateWaySn = eventDrone.getGateWaySn();
int nextPointIndex = eventDrone.getNextPointIndex() - 1;
String wayLineFileId = eventDrone.getWayLineFileId();
// 向网关发送执行航线航点命令
boolean isOnline = deviceRedisService.checkDeviceOnline(gateWaySn);
if (!isOnline) {
throw new CommonException(CommonErrorEnum.E_210015);
}
WaylineData data = (WaylineData) RedisOpsUtils.get(RedisConst.WAYLINE_PILOT_FLIGHT_TASK + wayLineFileId);
if (data == null) {
throw new CommonException(CommonErrorEnum.E_230108);
}
List<Placemark> placemark = data.getFolder().getPlacemark();
//最后一个航点
Placemark lastPlacemark = placemark.get(placemark.size() - 1);
Integer lastIndex = lastPlacemark.getIndex();
if (nextPointIndex > lastIndex) {
System.out.println("开始返航=====当前航点=====" + nextPointIndex);
// 当前索引等于最后一个索引时,说明已经执行完毕 应该执行返航
ServiceReply serviceReply = controlService.getControlServiceReply(gateWaySn, RemoteDebugMethodEnum.RETURN_HOME.getMethod(),
null, UUID.randomUUID().toString());
if (ResponseResult.CODE_SUCCESS != serviceReply.getResult()) {
throw new CommonException(DJIErrorCodeEnum.find(serviceReply.getResult()));
}
return;
}
// 即将要飞向的航点
Placemark nextPlacemark = placemark.get(nextPointIndex);
// 航点索引 从-0开始
Integer index = nextPlacemark.getIndex();
if (!Objects.equals(index, nextPointIndex)) {
throw new CommonException(CommonErrorEnum.E_230107);
}
Point point = nextPlacemark.getPoint();
Double latitude = point.getLatitude();
Double longitude = point.getLongitude();
String ellipsoidHeight = nextPlacemark.getEllipsoidHeight();
List<PointDTO> pointDTOS = new ArrayList<>();
PointDTO pointDTO = new PointDTO();
pointDTO.setLatitude(latitude);
pointDTO.setLongitude(longitude);
pointDTO.setHeight(Double.valueOf(ellipsoidHeight));
pointDTOS.add(pointDTO);
FlyToPointParam flyToPointParam = new FlyToPointParam();
flyToPointParam.setMaxSpeed(Integer.parseInt(nextPlacemark.getWaypointSpeed()));
flyToPointParam.setPoints(pointDTOS);
flyToPointParam.setFlyToId(wayLineFileId);
// 执行命令
System.out.println("=====当前执行的航点=====" + nextPointIndex);
controlService.flyToPoint(gateWaySn, flyToPointParam);
}
}
}
我在补充一个航线执行任务的方法,这个是任务入口
java
/**
主方法入口
*/
if (JobTypeEnum.IMMEDIATE.getVal() == param.getJobType()) {
if (TaskTypeEnum.GATEWAY.getVal() == taskType) {
// 获取auth 和 drc
drcService.cloudControlAuth(dockSn);
DrcDroneModeParam modeParam = new DrcDroneModeParam();
modeParam.setDockSn(dockSn);
modeParam.setClientId(UUID.randomUUID().toString());
drcService.deviceDrcDroneEnter(workspaceId, modeParam);
// 获取设备高度
Optional<OsdSubDeviceReceiver> deviceOsd = deviceRedisService.getDeviceOsd(
childDeviceSn,
OsdSubDeviceReceiver.class
);
if (deviceOsd.isEmpty()) {
log.error("设备 {} OSD数据不存在", dockSn);
throw new CommonException(CommonErrorEnum.E_210364);
}
// double totalHeight = deviceOsd.get().getHeight() + commonFlight;
// System.out.println("目标高度=====" + totalHeight);
Double latitude = deviceOsd.get().getLatitude();
Double longitude = deviceOsd.get().getLongitude();
if (latitude.equals(0.0) || longitude.equals(0.0)){
throw new CommonException(500,"信号较差无法获取位置信息 请将飞机移到开阔地带");
}
// 先一键起飞
TakeoffToPointParam params = TakeoffToPointParam.builder()
.commander_flight_height(commonFlight)
.commander_flight_mode(CommanderFlightModeEnum.find(1))
.commander_mode_lost_action(CommanderModeLostActionEnum.find(1))
.exitWaylineWhenRcLost(WaylineRcLostActionEnum.find(1))
.maxSpeed(15.0)
.rcLostAction(DroneRcLostActionEnum.find(2))
.rthAltitude(commonFlight.intValue())
.rthMode(RthModeEnum.find(1))
.securityTakeoffHeight(commonFlight)
.targetHeight(commonFlight)
.targetLatitude(latitude)
.targetLongitude(longitude)
.build();
controlServiceImpl.takeoffToPoint(waylineJob.getDockSn(), params);
// 执行航线任务
CompletableFuture<Void> heightFuture = waitForHeightAsync(
childDeviceSn,
waylineJob.getDockSn(),
fileId
);
// 可以继续执行其他逻辑,或者等待这个Future完成
heightFuture.thenRun(() -> {
log.info("高度条件满足,航线开始执行");
});
} else
//执行机场任务
return this.publishOneFlightTask(waylineJob);
}
/**
* 异步等待高度条件(不阻塞当前线程)
*/
private CompletableFuture<Void> waitForHeightAsync(String deviceSn, String dockSn,
String fileId) {
return CompletableFuture.runAsync(() -> {
try {
executeAfterReachingTargetHeight(deviceSn, dockSn, fileId);
} catch (Exception e) {
log.error("异步等待高度条件失败", e);
throw new CompletionException(e);
}
}).exceptionally(throwable -> {
throw new CommonException(CommonErrorEnum.E_210366);
});
}
/**
* 等待设备达到目标高度后执行操作
*/
private void executeAfterReachingTargetHeight(String deviceSn, String dockSn,
String fileId) {
log.info("开始等待设备达到目标高度");
boolean heightReached = false;
while (!heightReached) {
// 检查高度条件
if (isHeightInTargetRange(deviceSn)) {
heightReached = true;
// 执行高度达到后的操作
executeTargetHeightAction(dockSn, fileId);
}
}
}
/**
* 检查高度是否在目标范围内
*/
private boolean isHeightInTargetRange(String deviceSn) {
// 获取设备高度
Optional<OsdSubDeviceReceiver> deviceOsd = deviceRedisService.getDeviceOsd(
deviceSn,
OsdSubDeviceReceiver.class
);
if (deviceOsd.isEmpty()) {
log.error("设备 {} OSD数据不存在", deviceSn);
throw new CommonException(CommonErrorEnum.E_210364);
}
Double currentHeight = deviceOsd.get().getHeight();
sleepForInterval(5000);
// 获取设备高度
Optional<OsdSubDeviceReceiver> deviceOsd2 = deviceRedisService.getDeviceOsd(
deviceSn,
OsdSubDeviceReceiver.class
);
if (deviceOsd2.isEmpty()) {
log.error("设备 {} OSD数据不存在", deviceSn);
throw new CommonException(CommonErrorEnum.E_210364);
}
Double currentHeight2 = deviceOsd2.get().getHeight();
boolean b = Math.abs(currentHeight2 - currentHeight) <= 0.05;
if (b){
log.info("设备 {} 达到目标高度: {}米", deviceSn, currentHeight2);
}
return b;
}
/**
* 安全休眠
*/
private void sleepForInterval(long intervalMs) {
try {
Thread.sleep(intervalMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("等待高度时被中断");
throw new CommonException(CommonErrorEnum.E_230101);
}
}
/**
* 执行目标高度达到后的操作
*/
private void executeTargetHeightAction(String dockSn, String fileId) {
EventDrone event = EventDrone.builder()
.gateWaySn(dockSn)
.nextPointIndex(1)
.wayLineFileId(fileId)
.build();
publisher.publishEvent(new PubFlightTaskEvent(event));
log.info("已发布航点命令 - 设备: {}, 航点索引: 1, 航线ID: {}",
dockSn, fileId);
}
再贴一个kmz解析后的json
java
{
"@class": "com.tbd.smartterm.wayline.model.param.WaylineData",
"createTime": 1766196257130,
"updateTime": 1766196529860,
"missionConfig": {
"@class": "com.tbd.smartterm.wayline.model.param.MissionConfig",
"flyToWaylineMode": "safely",
"finishAction": "goHome",
"exitOnRCLost": "executeLostAction",
"executeRCLostAction": "goBack",
"takeOffSecurityHeight": "100",
"globalTransitionalSpeed": "15",
"droneInfo": {
"@class": "com.tbd.smartterm.wayline.model.param.DroneInfo",
"droneEnumValue": "100",
"droneSubEnumValue": "1"
},
"payloadInfo": {
"@class": "com.tbd.smartterm.wayline.model.param.PayloadInfo",
"payloadEnumValue": "99",
"payloadSubEnumValue": "0",
"payloadPositionIndex": "0"
}
},
"Folder": {
"@class": "com.tbd.smartterm.wayline.model.param.WaylineFolder",
"templateType": "waypoint",
"templateId": "0",
"waylineCoordinateSysParam": {
"@class": "com.tbd.smartterm.wayline.model.param.WaylineCoordinateSysParam",
"coordinateMode": "WGS84",
"heightMode": "relativeToStartPoint"
},
"autoFlightSpeed": "5",
"globalHeight": "100",
"caliFlightEnable": "0",
"gimbalPitchMode": "manual",
"globalWaypointHeadingParam": {
"@class": "com.tbd.smartterm.wayline.model.param.WaypointHeadingParam",
"waypointHeadingMode": "followWayline",
"waypointHeadingAngle": "0",
"waypointPoiPoint": "0.000000,0.000000,0.000000",
"waypointHeadingPoiIndex": "0"
},
"globalWaypointTurnMode": "toPointAndStopWithDiscontinuityCurvature",
"globalUseStraightLine": "0",
"payloadParam": {
"@class": "com.tbd.smartterm.wayline.model.param.PayloadParam",
"payloadPositionIndex": "0",
"imageFormat": "visable,ir"
},
"Placemark": [
"java.util.ArrayList",
[
{
"@class": "com.tbd.smartterm.wayline.model.param.Placemark",
"Point": {
"@class": "com.tbd.smartterm.wayline.model.param.Point",
"longitude": 114.440252119758,
"latitude": 23.0911126946495,
"coordinates": "114.440252119758,23.0911126946495"
},
"index": 0,
"ellipsoidHeight": "100",
"height": "100",
"waypointSpeed": "5",
"waypointHeadingParam": {
"@class": "com.tbd.smartterm.wayline.model.param.WaypointHeadingParam",
"waypointHeadingMode": "followWayline",
"waypointHeadingAngle": "0",
"waypointPoiPoint": "0.000000,0.000000,0.000000",
"waypointHeadingPoiIndex": "0"
},
"waypointTurnParam": {
"@class": "com.tbd.smartterm.wayline.model.param.WaypointTurnParam",
"waypointTurnMode": "toPointAndStopWithDiscontinuityCurvature"
},
"useGlobalHeight": "1",
"useGlobalSpeed": "1",
"useGlobalHeadingParam": "1",
"useGlobalTurnParam": "1",
"useStraightLine": "0",
"isRisky": "0",
"actionGroup": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionGroup",
"action": [
"java.util.ArrayList",
[
{
"@class": "com.tbd.smartterm.wayline.model.param.Action",
"actionId": 0,
"actionActuatorFunc": "hover",
"actionActuatorFuncParam": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionActuatorFuncParam"
}
},
{
"@class": "com.tbd.smartterm.wayline.model.param.Action",
"actionId": 0,
"actionActuatorFunc": "zoom",
"actionActuatorFuncParam": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionActuatorFuncParam",
"payloadPositionIndex": "0"
}
}
]
],
"actionGroupEndIndex": 0,
"actionGroupId": 0,
"actionGroupMode": "sequence",
"actionGroupStartIndex": 0,
"actionTrigger": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionTrigger",
"actionTriggerType": "reachPoint"
}
}
},
{
"@class": "com.tbd.smartterm.wayline.model.param.Placemark",
"Point": {
"@class": "com.tbd.smartterm.wayline.model.param.Point",
"longitude": 114.440240041855,
"latitude": 23.0907883496481,
"coordinates": "114.440240041855,23.0907883496481"
},
"index": 1,
"ellipsoidHeight": "100",
"height": "100",
"waypointSpeed": "5",
"waypointHeadingParam": {
"@class": "com.tbd.smartterm.wayline.model.param.WaypointHeadingParam",
"waypointHeadingMode": "followWayline",
"waypointHeadingAngle": "0",
"waypointPoiPoint": "0.000000,0.000000,0.000000",
"waypointHeadingPoiIndex": "0"
},
"waypointTurnParam": {
"@class": "com.tbd.smartterm.wayline.model.param.WaypointTurnParam",
"waypointTurnMode": "toPointAndStopWithDiscontinuityCurvature"
},
"useGlobalHeight": "1",
"useGlobalSpeed": "1",
"useGlobalHeadingParam": "1",
"useGlobalTurnParam": "1",
"useStraightLine": "0",
"isRisky": "0",
"actionGroup": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionGroup",
"action": [
"java.util.ArrayList",
[
{
"@class": "com.tbd.smartterm.wayline.model.param.Action",
"actionId": 0,
"actionActuatorFunc": "hover",
"actionActuatorFuncParam": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionActuatorFuncParam"
}
},
{
"@class": "com.tbd.smartterm.wayline.model.param.Action",
"actionId": 0,
"actionActuatorFunc": "zoom",
"actionActuatorFuncParam": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionActuatorFuncParam",
"payloadPositionIndex": "0"
}
}
]
],
"actionGroupEndIndex": 1,
"actionGroupId": 1,
"actionGroupMode": "sequence",
"actionGroupStartIndex": 1,
"actionTrigger": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionTrigger",
"actionTriggerType": "reachPoint"
}
}
},
{
"@class": "com.tbd.smartterm.wayline.model.param.Placemark",
"Point": {
"@class": "com.tbd.smartterm.wayline.model.param.Point",
"longitude": 114.439949666376,
"latitude": 23.0907976762578,
"coordinates": "114.439949666376,23.0907976762578"
},
"index": 2,
"ellipsoidHeight": "100",
"height": "100",
"waypointSpeed": "5",
"waypointHeadingParam": {
"@class": "com.tbd.smartterm.wayline.model.param.WaypointHeadingParam",
"waypointHeadingMode": "followWayline",
"waypointHeadingAngle": "0",
"waypointPoiPoint": "0.000000,0.000000,0.000000",
"waypointHeadingPoiIndex": "0"
},
"waypointTurnParam": {
"@class": "com.tbd.smartterm.wayline.model.param.WaypointTurnParam",
"waypointTurnMode": "toPointAndStopWithDiscontinuityCurvature"
},
"useGlobalHeight": "1",
"useGlobalSpeed": "1",
"useGlobalHeadingParam": "1",
"useGlobalTurnParam": "1",
"useStraightLine": "0",
"isRisky": "0",
"actionGroup": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionGroup",
"action": [
"java.util.ArrayList",
[
{
"@class": "com.tbd.smartterm.wayline.model.param.Action",
"actionId": 0,
"actionActuatorFunc": "hover",
"actionActuatorFuncParam": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionActuatorFuncParam"
}
},
{
"@class": "com.tbd.smartterm.wayline.model.param.Action",
"actionId": 0,
"actionActuatorFunc": "zoom",
"actionActuatorFuncParam": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionActuatorFuncParam",
"payloadPositionIndex": "0"
}
}
]
],
"actionGroupEndIndex": 2,
"actionGroupId": 2,
"actionGroupMode": "sequence",
"actionGroupStartIndex": 2,
"actionTrigger": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionTrigger",
"actionTriggerType": "reachPoint"
}
}
},
{
"@class": "com.tbd.smartterm.wayline.model.param.Placemark",
"Point": {
"@class": "com.tbd.smartterm.wayline.model.param.Point",
"longitude": 114.43992414037,
"latitude": 23.0905509954723,
"coordinates": "114.43992414037,23.0905509954723"
},
"index": 3,
"ellipsoidHeight": "100",
"height": "100",
"waypointSpeed": "5",
"waypointHeadingParam": {
"@class": "com.tbd.smartterm.wayline.model.param.WaypointHeadingParam",
"waypointHeadingMode": "followWayline",
"waypointHeadingAngle": "0",
"waypointPoiPoint": "0.000000,0.000000,0.000000",
"waypointHeadingPoiIndex": "0"
},
"waypointTurnParam": {
"@class": "com.tbd.smartterm.wayline.model.param.WaypointTurnParam",
"waypointTurnMode": "toPointAndStopWithDiscontinuityCurvature"
},
"useGlobalHeight": "1",
"useGlobalSpeed": "1",
"useGlobalHeadingParam": "1",
"useGlobalTurnParam": "1",
"useStraightLine": "0",
"isRisky": "0",
"actionGroup": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionGroup",
"action": [
"java.util.ArrayList",
[
{
"@class": "com.tbd.smartterm.wayline.model.param.Action",
"actionId": 0,
"actionActuatorFunc": "hover",
"actionActuatorFuncParam": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionActuatorFuncParam"
}
},
{
"@class": "com.tbd.smartterm.wayline.model.param.Action",
"actionId": 0,
"actionActuatorFunc": "zoom",
"actionActuatorFuncParam": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionActuatorFuncParam",
"payloadPositionIndex": "0"
}
}
]
],
"actionGroupEndIndex": 3,
"actionGroupId": 3,
"actionGroupMode": "sequence",
"actionGroupStartIndex": 3,
"actionTrigger": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionTrigger",
"actionTriggerType": "reachPoint"
}
}
},
{
"@class": "com.tbd.smartterm.wayline.model.param.Placemark",
"Point": {
"@class": "com.tbd.smartterm.wayline.model.param.Point",
"longitude": 114.440421548478,
"latitude": 23.0904717637701,
"coordinates": "114.440421548478,23.0904717637701"
},
"index": 4,
"ellipsoidHeight": "100",
"height": "100",
"waypointSpeed": "5",
"waypointHeadingParam": {
"@class": "com.tbd.smartterm.wayline.model.param.WaypointHeadingParam",
"waypointHeadingMode": "followWayline",
"waypointHeadingAngle": "0",
"waypointPoiPoint": "0.000000,0.000000,0.000000",
"waypointHeadingPoiIndex": "0"
},
"waypointTurnParam": {
"@class": "com.tbd.smartterm.wayline.model.param.WaypointTurnParam",
"waypointTurnMode": "toPointAndStopWithDiscontinuityCurvature"
},
"useGlobalHeight": "1",
"useGlobalSpeed": "1",
"useGlobalHeadingParam": "1",
"useGlobalTurnParam": "1",
"useStraightLine": "0",
"isRisky": "0",
"actionGroup": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionGroup",
"action": [
"java.util.ArrayList",
[
{
"@class": "com.tbd.smartterm.wayline.model.param.Action",
"actionId": 0,
"actionActuatorFunc": "hover",
"actionActuatorFuncParam": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionActuatorFuncParam"
}
},
{
"@class": "com.tbd.smartterm.wayline.model.param.Action",
"actionId": 0,
"actionActuatorFunc": "zoom",
"actionActuatorFuncParam": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionActuatorFuncParam",
"payloadPositionIndex": "0"
}
}
]
],
"actionGroupEndIndex": 4,
"actionGroupId": 4,
"actionGroupMode": "sequence",
"actionGroupStartIndex": 4,
"actionTrigger": {
"@class": "com.tbd.smartterm.wayline.model.param.ActionTrigger",
"actionTriggerType": "reachPoint"
}
}
}
]
]
}
}