Sync with the latest code

This commit is contained in:
Bingkun Li 2026-01-22 13:43:02 +08:00
parent d6a94a8496
commit f80715d5ed
75 changed files with 2547 additions and 538 deletions

View File

@ -11,6 +11,7 @@
<artifactId>skyeye-common-extend</artifactId>
<description>扩展工具包</description>
<version>1.0.0</version>
<dependencies>
<!-- Lombok -->

View File

@ -4,6 +4,7 @@ import com.zhangy.skyeye.common.extend.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
@ -15,6 +16,29 @@ import java.nio.file.StandardCopyOption;
@Slf4j
public class FileUtil {
public static byte[] read(String filePath) {
return read(new File(filePath));
}
/**
* 读取文件
*
* @param file
* @return 文件数据
*/
public static byte[] read(File file) {
if (!file.exists()) {
return null;
}
try (FileInputStream fis = new FileInputStream(file)) {
byte[] data = new byte[fis.available()];
fis.read(data);
return data;
} catch (IOException ex) {
throw ServiceException.errorLog("读取文件[" + file.getName() + "]错误!" + ex.getMessage());
}
}
/**
* 复制文件如果目标文件已存在则会抛异常
* @param src

View File

@ -112,7 +112,7 @@ public class MapUtil {
* @param value
* @param <T>
*/
public static <T> void putList(Map<String, List<T>> map, String key, T value) {
public static <K, T> void putList(Map<K, List<T>> map, K key, T value) {
List<T> list;
if (map.containsKey(key)) {
list = map.get(key);

View File

@ -1,10 +1,20 @@
package com.zhangy.skyeye.common.extend.util;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class NumberUtil {
/**
* 相加
*/
public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).doubleValue();
}
/**
* 判断包装类型是否非空且有效
*

View File

@ -65,7 +65,7 @@ public class ObjectUtil {
* @param value
* @param <T>
*/
public static <T> void put(Map<String, List<T>> map, String key, T value) {
public static <K, T> void put(Map<K, List<T>> map, K key, T value){
MapUtil.putList(map, key, value);
}

View File

@ -16,7 +16,7 @@
<dependency>
<groupId>com.zhangy</groupId>
<artifactId>skyeye-common-extend</artifactId>
<version>1.0.0.RELEASE</version>
<version>1.0.0</version>
</dependency>
<!-- 通用依赖封装 -->
<dependency>
@ -130,6 +130,16 @@
<artifactId>quartz-jobs</artifactId>
<version>2.3.2</version>
</dependency>-->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.13.0</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.13.0</version>
</dependency>
</dependencies>
<repositories>

View File

@ -5,6 +5,7 @@ import com.zhangy.skyeye.common.extend.anno.IgnoreAuth;
import com.zhangy.skyeye.common.extend.enums.EnumUtil;
import com.zhangy.skyeye.common.extend.exception.ServiceException;
import com.zhangy.skyeye.common.extend.util.DateUtil;
import com.zhangy.skyeye.common.pojo.result.Result;
import com.zhangy.skyeye.jm.consts.JmJobModeEnum;
import com.zhangy.skyeye.jm.dto.JmJobDTO;
import com.zhangy.skyeye.jm.dto.JmJobPageDTO;
@ -62,6 +63,15 @@ public class JmJobController {
return jobService.selectDetail(id);
}
/**
* 查询前端详情
*/
@GetMapping("/info")
public Object selectInfo(@RequestParam Long jobId) {
JmJobDTO job = jobService.selectInfo(jobId);
return Result.successData(job == null ? null : job.getInfo1());
}
/**
* 新增
*/

View File

@ -0,0 +1,45 @@
package com.zhangy.skyeye.jm.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.zhangy.skyeye.jm.entity.JmJob;
import com.zhangy.skyeye.jm.entity.JmJobPoint;
import com.zhangy.skyeye.jm.entity.JmJobUav;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 任务 dto用于保存接口参数 详情查询返回值
*/
@Data
public class JmAirlinePlanParam implements Serializable {
/**
* 任务目标 1 2 区域
*/
private Integer targetType = 2;
/**
* 成像模式
* @see com.zhangy.skyeye.sar.consts.SarImageModeEnum
*/
@NotNull(message = "成像模式不能为空")
private Byte imageMode;
/** 任务点 */
@Valid
@NotEmpty(message = "任务点不能为空")
private List<List<JmJobPoint>> pointList;
/** 无人机 */
@Valid
@NotEmpty(message = "无人机不能为空")
private List<JmJobUav> uavList;
}

View File

@ -49,4 +49,7 @@ public class JmAirlineStatusDTO {
/** 载荷ID */
private Long payloadId;
// 前一张右上右下
private Double[] beforeRight;
}

View File

@ -33,15 +33,15 @@ public class JmImage extends GeoTiffDTO {
/** 影像名称,默认文件名 */
private String name;
/** 图像帧序号 */
private Integer frameNo;
/** 文件ID */
private Long fileId;
/** 归一化前最大值 */
private Float max;
/** 航线的图片序号 */
private Integer imageNo;
/*
非数据库字段
*/

View File

@ -27,15 +27,29 @@ public class JmJob implements Serializable {
/** 定时任务表达式 */
private String cronExpression;
/** 航线模式;1 点模式2 区域规划模式3 航线创建 */
/**
* 航线模式
* @see com.zhangy.skyeye.jm.consts.JmJobModeEnum
*/
@NotNull(message = "航线模式不能为空")
private Integer mode;
@NotNull(message = "成像模式不能为空")
private Byte imageMode;
/**
* 任务目标 1 2 区域
*/
private Integer targetType = 2;
/** 创建时间 */
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/** 前端内容 */
private String info1;
/*======================
任务执行字段
=====================*/

View File

@ -45,8 +45,8 @@ public class JmJobPayload implements Serializable {
private Byte direction;
/** 分辨率 */
@NotNull(message = "分辨率不能为空")
private Float resolution;
//@NotNull(message = "分辨率不能为空")
private Float resolution = 0.3f;
/** 极化方式 */
//@NotNull(message = "极化方式不能为空")
@ -59,8 +59,7 @@ public class JmJobPayload implements Serializable {
private byte moto;
/** 成像亮度倍数,用于地面端接收图片后的调整 */
@NotNull(message = "图像亮度不能为空")
private Integer imageLight;
private Integer imageLight = 1;
/** 雷达朝向与飞机朝向夹角 -180~180 */
private Integer headingDiff = 0;
@ -89,7 +88,7 @@ public class JmJobPayload implements Serializable {
private String ip;
/** 载荷类型 */
private String type;
private String type = "SAR";
/** sar低精度图像 */
private List<JmImage> imageList;

View File

@ -1,10 +1,12 @@
package com.zhangy.skyeye.jm.entity;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@NoArgsConstructor
@Data
public class JmJobPoint implements Serializable {
@ -27,4 +29,9 @@ public class JmJobPoint implements Serializable {
/** 纬度 */
@NotNull(message = "任务点纬度不能为空")
private Double latitude;
public JmJobPoint (Double longitude, Double latitude) {
this.longitude = longitude;
this.latitude = latitude;
}
}

View File

@ -66,7 +66,7 @@ public interface JmAirlineExecMapper {
/**
* 按任务删除
*
* @param jobId 任务ID必需
* @param jobId 任务配置ID必需
*/
int deleteByJob(Long... jobId);

View File

@ -2,6 +2,7 @@ package com.zhangy.skyeye.jm.mapper;
import com.zhangy.skyeye.jm.dto.JmJobDTO;
import com.zhangy.skyeye.jm.dto.JmJobQueryDTO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zhangy.skyeye.common.extend.dto.PageDTO;
@ -26,7 +27,12 @@ public interface JmJobMapper {
* 按主键查询
*/
List<JmJobDTO> selectById(Long... id);
/**
* 查询任务的前端结构
*/
JmJobDTO selectInfo(@Param("jobId") Long jobId);
/**
* 新增
*/

View File

@ -50,9 +50,9 @@ public interface JmAirlineExecService {
void updateNotNull(JmAirlineExec e);
/**
* 按任务执行删除
* 按任务删除
*
* @param jobExecId 任务执行ID
* @param jobId 任务配置ID
*/
void deleteByJobExec(Long... jobExecId);
void deleteByJob(Long... jobId);
}

View File

@ -0,0 +1,29 @@
package com.zhangy.skyeye.jm.service;
import com.zhangy.skyeye.jm.consts.JmJobModeEnum;
import com.zhangy.skyeye.jm.dto.JmJobDTO;
import com.zhangy.skyeye.jm.entity.JmAirline;
import com.zhangy.skyeye.jm.entity.JmJobPoint;
import com.zhangy.skyeye.jm.entity.JmJobUav;
import com.zhangy.skyeye.sar.consts.SarImageModeEnum;
import java.util.List;
import java.util.Map;
/**
* 航线规划
*/
public interface JmAirlinePlanService {
/**
* 规划航线
*
* @param jobMode 任务模式
* @param imageMode 成像模式
* @param uavList 无人机
* @param pointList 任务区域
* @return K - uavIdV - airlines
*/
Map<Long, List<JmAirline>> plan(JmJobModeEnum jobMode, SarImageModeEnum imageMode, Integer targetType,
List<JmJobUav> uavList, List<List<JmJobPoint>> pointList);
}

View File

@ -39,7 +39,7 @@ public interface JmImageService {
/**
* 按航线查询低精度图像
*/
JmImage selectLowByAirline(Long airlineExecId);
List<JmImage> selectLowByAirline(Long airlineExecId);
/**
* 按主键查询详情

View File

@ -27,6 +27,8 @@ public interface JmJobService {
*/
List<JmJobDTO> selectById(Long... id);
JmJobDTO selectInfo(Long jobId);
/**
* 查询详情
*/

View File

@ -93,9 +93,9 @@ public class JmAirlineExecServiceImpl implements JmAirlineExecService {
@Transactional
@Override
public void deleteByJobExec(Long... jobExecId) {
if (ObjectUtil.isNotEmpty(jobExecId)) {
airlineExecMapper.deleteByJob(jobExecId);
public void deleteByJob(Long... jobId) {
if (ObjectUtil.isNotEmpty(jobId)) {
airlineExecMapper.deleteByJob(jobId);
}
}
}

View File

@ -0,0 +1,215 @@
package com.zhangy.skyeye.jm.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.zhangy.skyeye.common.extend.exception.ServiceException;
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
import com.zhangy.skyeye.device.consts.PayloadTypeEnum;
import com.zhangy.skyeye.device.entity.SkyeyePayload;
import com.zhangy.skyeye.device.service.IPayloadService;
import com.zhangy.skyeye.jm.consts.JmJobModeEnum;
import com.zhangy.skyeye.jm.dto.JmSarStatusDTO;
import com.zhangy.skyeye.jm.entity.JmAirline;
import com.zhangy.skyeye.jm.entity.JmJobPayload;
import com.zhangy.skyeye.jm.entity.JmJobPoint;
import com.zhangy.skyeye.jm.entity.JmJobUav;
import com.zhangy.skyeye.jm.service.JmAirlinePlanService;
import com.zhangy.skyeye.py.dto.*;
import com.zhangy.skyeye.py.service.IPyAirlineService;
import com.zhangy.skyeye.sar.consts.SarImageModeEnum;
import com.zhangy.skyeye.sar.dto.SarFlightPlanDTO;
import com.zhangy.skyeye.sar.util.SpotlightPlanner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* 航线规划
*/
@Service
public class JmAirlinePlanServiceImpl implements JmAirlinePlanService {
@Autowired
private IPayloadService payloadService;
@Autowired
private IPyAirlineService ktkxService;
@Override
public Map<Long, List<JmAirline>> plan(JmJobModeEnum jobMode, SarImageModeEnum imageMode, Integer targetType,
List<JmJobUav> uavList, List<List<JmJobPoint>> pointList) {
checkParam(jobMode, imageMode, targetType);
// 非航线模式需要调算法生成航线需要从缓存取sar坐标
Map<Long, List<JmAirline>> airlineGroup = null;
// 1聚束
if (imageMode == SarImageModeEnum.JS) {
return planJs(uavList.get(0), pointList);
} else if (jobMode == JmJobModeEnum.QUICK) { // 2快速
return ktkxService.getAirline(toKtkxParam(pointList, uavList));
}
// 3航线创建
return null;
}
/**
* 校验参数
*
* @param jobMode
* @param imageMode
* @param uavList
* @param pointList
*/
private void checkParam(JmJobModeEnum jobMode, SarImageModeEnum imageMode, Integer targetType) {
if (imageMode == SarImageModeEnum.JS) {
if (jobMode == JmJobModeEnum.CREATE) {
throw ServiceException.noLog("航线创建模式不支持聚束模式");
}
if (targetType == 2) {
throw ServiceException.noLog("聚束模式只能使用点模式");
}
}
}
/**
* 聚束模式
*
* @param uav 无人机
* @param points 目标点
* @return 航线
*/
public Map<Long, List<JmAirline>> planJs(JmJobUav uav, List<List<JmJobPoint>> points) {
Map<Long, List<JmAirline>> result = new HashMap<>();
// 补充载荷信息
JmJobPayload sar = uav.getSar0();
SkyeyePayload payload = payloadService.getOne(sar.getPayloadId());
sar.setIp(payload.getIp());
// 取起飞点位置
JmSarStatusDTO statusDTO = payloadService.getLastStatus(sar.getIp());
double startLon = statusDTO.getLongitude();
double startLat = statusDTO.getLatitude();
uav.setStartLon(startLon);
uav.setStartLat(startLat);
JmJobPoint home = new JmJobPoint(startLon, startLat);
for (List<JmJobPoint> targets: points) {
JmJobPoint target = targets.get(0);
if (target == null) {
continue;
}
JmAirline airline = planJs(uav, home, target);
ObjectUtil.put(result, uav.getUavId(), airline);
// 初始化起点
home.setLongitude(airline.getEndLon());
home.setLatitude(airline.getEndLat());
}
return result;
}
/**
* 聚束模式
*
* @param uav 无人机
* @param home 起飞点
* @param point 目标点
* @return 航线
*/
public JmAirline planJs(JmJobUav uav, JmJobPoint home, JmJobPoint target) {
JmJobPayload sar = uav.getSar0();
SarFlightPlanDTO plan = SpotlightPlanner.optimizeRoute(target.getLongitude(), target.getLatitude(),
home.getLongitude(), home.getLatitude(),
uav.getHeight(), sar.getTheta(), sar.getDirection());
JmAirline airline = new JmAirline();
airline.setFlightType((byte)0);
double height = plan.getHeight();
airline.setFlightStartLon(plan.getPowerOnLon());
airline.setFlightStartLat(plan.getPowerOnLat());
airline.setFlightStartHeight(height); // 相对起飞点高度
airline.setFlightEndLon(plan.getPowerOffLon());
airline.setFlightEndLat(plan.getPowerOffLat());
airline.setFlightEndHeight(height); // 相对起飞点高度
airline.setGroundStartLon(target.getLongitude());
airline.setGroundStartLat(target.getLatitude());
airline.setGroundStartHeight(0d); // 相对起飞点高度
airline.setTargetCentroidLon(target.getLongitude());
airline.setTargetCentroidLat(target.getLatitude());
airline.setTargetCentroidHeight(0d); // 相对起飞点高度
airline.setTargetWidth(sar.getWidth());
airline.setTargetLength(sar.getLength());
double sarHeadingAngle = plan.getFlightHeadingAngle() - sar.getDirection() * 90; // 轴向角
airline.setTargetHeading(sarHeadingAngle);
airline.setStartLon(plan.getStartLon());
airline.setStartLat(plan.getStartLat());
airline.setStartHeight(height); // 相对起飞点高度
airline.setEndLon(plan.getEndLon());
airline.setEndLat(plan.getEndLat());
airline.setEndHeight(height); // 相对起飞点高度
airline.setSpeed(uav.getSpeed());
airline.setHeight(height);
airline.setDirection(sar.getDirection());
airline.setSquintAngle(0d);
airline.setGrazingAngle(90 - sar.getTheta());
return airline;
}
/**
* 转为航线算法参数
*/
private PyAirlineParamDTO toKtkxParam(List<List<JmJobPoint>> pointList, List<JmJobUav> uavList) {
List<PyAirlineTargetDTO> pyPointList = new ArrayList<>();
List<PyAirlineUavDTO> pyUavList = new ArrayList<>();
for (int i = 0; i < uavList.size(); i++) {
JmJobUav u = uavList.get(i);
// 区域
List<JmJobPoint> points = pointList.get(i);
double[][] coords = points.stream()
.map(point -> new double[] { point.getLongitude(), point.getLatitude() })
.toArray(double[][]::new);
pyPointList.add(new PyAirlineTargetDTO(i, coords));
// 载荷
JmJobPayload sar = u.getPayloadList()
.stream()
.filter(jp -> {
Long payloadId = jp.getPayloadId();
SkyeyePayload p = payloadService.getOne(payloadId);
if (p != null && p.getType().equals(PayloadTypeEnum.SAR.getCode())) {
jp.setIp(p.getIp());
return true;
}
return false;
})
.findFirst()
.orElseThrow( () -> ServiceException.errorLog("存在无人机缺少sar类型载荷"));
PyAirlineUavDTO uav = BeanUtil.copyProperties(u, PyAirlineUavDTO.class);
JmSarStatusDTO statusDTO = payloadService.getLastStatus(sar.getIp());
double startLon = statusDTO.getLongitude();
double startLat = statusDTO.getLatitude();
if (u.getStartAltitude() == null) {
u.setStartAltitude((double) statusDTO.getAltitude());
}
uav.setId(u.getUavId());
uav.setStartCoord(new double[] {startLon, startLat});
uav.setEndCoord(new double[] {startLon, startLat});
uav.setConstraint(u.getHeight());
PyAirlinePayloadDTO payload = BeanUtil.copyProperties(sar, PyAirlinePayloadDTO.class);
payload.setType(PayloadTypeEnum.SAR.getCode());
uav.setPayload(payload);
pyUavList.add(uav);
}
PyAirlineUavDTO[] uavs = pyUavList.toArray(new PyAirlineUavDTO[pyUavList.size()]);
PyAirlineTargetDTO[] targets = pyPointList.toArray(new PyAirlineTargetDTO[pyPointList.size()]);
PyAirlineParamDTO param = new PyAirlineParamDTO();
param.setTargets(targets);
param.setUavs(uavs);
return param;
}
}

View File

@ -45,8 +45,8 @@ public class JmImageServiceImpl implements JmImageService {
@Autowired
private JmImageItemService imageItemService;
// @Autowired
// private IPyImageService detectService;
@Autowired
private IPyImageService detectService;
@Autowired
private JmJobPayloadService jobPayloadService;
@ -113,9 +113,9 @@ public class JmImageServiceImpl implements JmImageService {
}
@Override
public JmImage selectLowByAirline(Long airlineExecId) {
public List<JmImage> selectLowByAirline(Long airlineExecId) {
List<JmImage> list = imageMapper.selectByAirline(FileTypeEnum.SAR_IMAGE_LOW.getValue(), airlineExecId);
return ObjectUtil.isEmpty(list) ? null : list.get(0);
return list;
}
@Override
@ -293,13 +293,13 @@ public class JmImageServiceImpl implements JmImageService {
// 1.删除已有识别结果
imageItemService.deleteByImage(id);
List<JmImage> list = imageMapper.selectById(id);
// for (JmImage e : list) {
// PyImageDTO[] voArr = detectService.getImageIdentify(e.toDetectParamVo());
// if (ObjectUtil.isNotEmpty(voArr)) {
// List<JmImageItem> itemList = imageItemService.insert(e, voArr);
// e.setItemList(itemList);
// }
// }
for (JmImage e : list) {
PyImageDTO[] voArr = detectService.getImageIdentify(e.toDetectParamVo());
if (ObjectUtil.isNotEmpty(voArr)) {
List<JmImageItem> itemList = imageItemService.insert(e, voArr);
e.setItemList(itemList);
}
}
return list;
}

View File

@ -65,6 +65,9 @@ public class JmJobExecServiceImpl implements JmJobExecService {
@Override
public List<JmJobDTO> selectByConf(Long... confId) {
if (ObjectUtil.isEmpty(confId)) {
return null;
}
return jobExecMapper.selectByConf(confId);
}
@ -181,14 +184,7 @@ public class JmJobExecServiceImpl implements JmJobExecService {
@Override
public void deleteByJobConf(Long... jobConfId) {
Long[] jobExecIds = jobExecMapper.selectByConf(jobConfId).stream().map(e -> e.getId()).toArray(Long[]::new);
jmAirlineExecService.deleteByJobExec(jobExecIds);
jmAirlineExecService.deleteByJob(jobConfId);
jobExecMapper.deleteByConf(jobConfId);
for (Long id : jobExecIds) { // 删除非文件管理的文件
FileUtil.delete(
fileTypeService.getDirectoryPath(FileTypeEnum.UAV_KMZ, id)[0],
fileTypeService.getDirectoryPath(FileTypeEnum.UAV_TXT, id)[0],
fileTypeService.getDirectoryPath(FileTypeEnum.SAR_WAVE, id)[0]
);
}
}
}

View File

@ -26,17 +26,16 @@ import com.zhangy.skyeye.publics.consts.FileTypeEnum;
import com.zhangy.skyeye.publics.consts.UavAirlineUploadEnum;
import com.zhangy.skyeye.publics.service.SysFileTypeService;
import com.zhangy.skyeye.publics.utils.CoordUtil;
import com.zhangy.skyeye.py.dto.PyAirlineParamDTO;
import com.zhangy.skyeye.py.dto.PyAirlinePayloadDTO;
import com.zhangy.skyeye.py.dto.PyAirlineTargetDTO;
import com.zhangy.skyeye.py.dto.PyAirlineUavDTO;
import com.zhangy.skyeye.py.service.IPyAirlineService;
import com.zhangy.skyeye.quartz.service.QuartzService;
import com.zhangy.skyeye.sar.consts.SarImageModeEnum;
import com.zhangy.skyeye.sar.dto.SarControlParamDTO;
import com.zhangy.skyeye.sar.enums.SarControlTypeEnum;
import com.zhangy.skyeye.sar.enums.SarDirectionEnum;
import com.zhangy.skyeye.sar.enums.SarIMUStatusEnum;
import com.zhangy.skyeye.sar.service.ISarControlService;
import com.zhangy.skyeye.sar.service.ISarMtiPointService;
import com.zhangy.skyeye.sar.service.ISarMtiTrailService;
//import com.zhangy.skyeye.smp.dto.SmpSubscriptResDTO;
//import com.zhangy.skyeye.smp.dto.SmpWayPointDTO;
//import com.zhangy.skyeye.smp.service.ISmpSubscriptService;
@ -53,7 +52,6 @@ import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@Slf4j
@Service
@ -77,10 +75,12 @@ public class JmJobServiceImpl implements JmJobService {
// @Autowired
// private ISmpSubscriptService smpSubscriptService;
@Autowired
private ISarControlService controlInfoService;
@Autowired
private ISarControlService sarControlService;
@Autowired
private ISarMtiPointService sarMtiPointService;
@Autowired
private ISarMtiTrailService sarMtiTrailService;
@Autowired
private IPayloadService payloadService;
@ -95,6 +95,9 @@ public class JmJobServiceImpl implements JmJobService {
@Autowired
private QuartzService quartzService;
@Autowired
private JmAirlinePlanService jmAirlinePlanService;
@Override
public IPage<JmJobDTO> selectPage(JmJobPageDTO param) {
@ -136,6 +139,11 @@ public class JmJobServiceImpl implements JmJobService {
return jobMapper.selectById(id);
}
@Override
public JmJobDTO selectInfo(Long jobId) {
return jobMapper.selectInfo(jobId);
}
@Override
public JmJobDTO selectDetail(Long id) {
Map<Long, List<List<JmJobPoint>>> pointGroup = jobPointService.selectMapByJob(id);
@ -148,15 +156,11 @@ public class JmJobServiceImpl implements JmJobService {
@Transactional
@Override
public JmJobDTO save(JmJobModeEnum mode, JmJobDTO e) {
// 查询本次所有载荷信息
Long[] payloadIds = e.getUavList()
.stream()
.flatMap(uav -> uav.getPayloadList().stream().map(JmJobPayload::getPayloadId))
.toArray(Long[]::new);
public JmJobDTO save(JmJobModeEnum jobMode, JmJobDTO e) {
SarImageModeEnum imageMode = EnumUtil.parseEx(SarImageModeEnum.class, e.getImageMode());
// 非航线模式需要调算法生成航线需要从缓存取sar坐标
Map<Long, List<JmAirline>> airlineGroup = mode == JmJobModeEnum.CREATE
? null : ktkxService.getAirline(toKtkxParam(e));
Map<Long, List<JmAirline>> airlineGroup = jmAirlinePlanService.plan(jobMode, imageMode, e.getTargetType(),
e.getUavList(), e.getPointList());
// 校验并加载数据
checkAndSetUav(e.getUavList(), airlineGroup);
return insert(e);
@ -190,66 +194,6 @@ public class JmJobServiceImpl implements JmJobService {
return e;
}
/**
* 转为航线算法参数
*/
private PyAirlineParamDTO toKtkxParam(JmJobDTO e) {
List<List<JmJobPoint>> pointList = e.getPointList();
// 任务区域
PyAirlineTargetDTO[] targets = IntStream.range(0, pointList.size())
.mapToObj(i -> {
List<JmJobPoint> list = pointList.get(i);
double[][] coords = list.stream()
.map(point -> new double[] { point.getLongitude(), point.getLatitude() })
.toArray(double[][]::new);
return new PyAirlineTargetDTO(i, coords);
})
.toArray(PyAirlineTargetDTO[]::new);
// 无人机
PyAirlineUavDTO[] uavs = e.getUavList().stream()
.map(u -> {
// 载荷
JmJobPayload sar = u.getPayloadList()
.stream()
.filter(jp -> {
Long payloadId = jp.getPayloadId();
SkyeyePayload p = payloadService.getOne(payloadId);
if (p != null && p.getType().equals(PayloadTypeEnum.SAR.getCode())) {
jp.setIp(p.getIp());
return true;
}
return false;
})
.findFirst()
.orElseThrow( () -> ServiceException.errorLog("存在无人机缺少sar类型载荷"));
PyAirlineUavDTO uav = BeanUtil.copyProperties(u, PyAirlineUavDTO.class);
JmSarStatusDTO statusDTO = payloadService.getLastStatus(sar.getIp());
double startLon = statusDTO.getLongitude();
double startLat = statusDTO.getLatitude();
u.setStartLon(startLon);
u.setStartLat(startLat);
u.setEndLon(startLon);
u.setEndLat(startLat);
if (u.getStartAltitude() == null) {
u.setStartAltitude((double) statusDTO.getAltitude());
}
uav.setId(u.getUavId());
uav.setStartCoord(new double[] {startLon, startLat});
uav.setEndCoord(new double[] {startLon, startLat});
uav.setConstraint(u.getHeight());
PyAirlinePayloadDTO payload = BeanUtil.copyProperties(sar, PyAirlinePayloadDTO.class);
payload.setType(PayloadTypeEnum.SAR.getCode());
uav.setPayload(payload);
return uav;
})
.toArray(PyAirlineUavDTO[]::new);
PyAirlineParamDTO param = new PyAirlineParamDTO();
param.setTargets(targets);
param.setUavs(uavs);
return param;
}
/**
* 校验载荷有且只能有一个sar并补充载荷信息
*
@ -351,7 +295,7 @@ public class JmJobServiceImpl implements JmJobService {
airline.setDirection(sar.getDirection());
}
// 每条航线同高度故计算坐标点距离不考虑高度
int airlineDistance = CoordUtil.getPlaneDistanceInt(airline.getStartLon(), airline.getStartLat(), airline.getEndLon(), airline.getEndLat());
int airlineDistance = (int) CoordUtil.calculateDistance(airline.getStartLon(), airline.getStartLat(), airline.getEndLon(), airline.getEndLat());
airline.setDistance(airlineDistance);
com.zhangy.skyeye.common.extend.util.BeanUtil.validationEx(airline);
});
@ -360,11 +304,30 @@ public class JmJobServiceImpl implements JmJobService {
@Transactional
@Override
public int delete(Long... ids) {
Long[] jobExecIds = jmJobExecService.selectByConf(ids).stream().map(e -> e.getId()).toArray(Long[]::new);
List<JmJobDTO> list = jobMapper.selectById(ids);
jobStatusService.remove(ids);
jobPointService.deleteByJob(ids);
jobUavService.deleteByJob(ids);
jmJobExecService.deleteByJobConf(ids);
sarMtiPointService.deleteByJob(ids);
sarMtiTrailService.deleteByJob(ids);
// 目录
if (jobExecIds != null) {
for (Long id : jobExecIds) {
FileUtil.delete(
fileTypeService.getDirectoryPath(FileTypeEnum.UAV_KMZ, id)[0],
fileTypeService.getDirectoryPath(FileTypeEnum.SAR_WAVE, id)[0],
fileTypeService.getDirectoryPath(FileTypeEnum.SAR_IMAGE_LOW, id)[0],
fileTypeService.getDirectoryPath(FileTypeEnum.SAR_IMAGE_LOW_SRC, id)[0],
fileTypeService.getDirectoryPath(FileTypeEnum.SAR_IMAGE_HIGH, id)[0],
fileTypeService.getDirectoryPath(FileTypeEnum.SAR_IMAGE_HIGH_SRC, id)[0],
fileTypeService.getDirectoryPath(FileTypeEnum.SAR_MTI_LIB, id)[0]
);
}
}
for (JmJobDTO dto : list) {
if (dto.getType() > 1) {
quartzService.deleteJob(dto.getId().toString(), JmScheduleDTO.GROUP);
@ -474,7 +437,7 @@ public class JmJobServiceImpl implements JmJobService {
SarControlParamDTO controlParam = new SarControlParamDTO();
controlParam.setControlType(SarControlTypeEnum.ENDALL);
controlParam.setIp(ip);
controlInfoService.sendUdp(controlParam);
sarControlService.sendUdp(controlParam);
// 标记缓存状态确保断连重新发送请求时不会重复执行
// uav.setSarStatus(ExecStatusEnum.OVER);
});
@ -485,6 +448,11 @@ public class JmJobServiceImpl implements JmJobService {
job.setId(id);
job.setStatus(ExecStatusEnum.OVER.getValue());
jobMapper.updateNotNull(job);
JmJobExec jobExec = new JmJobExec();
jobExec.setConfId(id);
jobExec.setStatus(ExecStatusEnum.OVER.getValue());
jmJobExecService.updateNotNull(jobExec);
}
@Override

View File

@ -72,7 +72,7 @@ public class JmJobStatusServiceImpl implements JmJobStatusService {
public JmAirlineStatusDTO getCurrAirline(String payloadIp) {
for (JmJobStatusDTO jvo : jobStatusMap.values()) {
for (JmUavStatusDTO uvo : jvo.getUavMap().values()) {
if (uvo.getSarIp().endsWith(payloadIp)) {
if (uvo.getSarIp().equals(payloadIp)) {
for (JmAirlineStatusDTO a : uvo.getAirlineList()) {
if (a.getStatus() == ExecStatusEnum.PROCESSING) {
return a;
@ -237,7 +237,7 @@ public class JmJobStatusServiceImpl implements JmJobStatusService {
ExecStatusEnum aStatus = avo.getStatus();
switch (aStatus) {
case PROCESSING:
distanced += CoordUtil.getPlaneDistanceInt(
distanced += CoordUtil.calculateDistance(
curr.getLongitude(), curr.getLatitude(),
avo.getStartLon(), avo.getStartLat());
case NOT:

View File

@ -50,7 +50,7 @@ public class JmJobUavServiceImpl implements JmJobUavService {
@Getter
private UavAirlineUploadEnum airlineUploadType;
@Value("${ld.uav.upload}")
@Value("${skyeye.uav.upload}")
public void setAirlineUploadType(String value) {
this.airlineUploadType = EnumUtil.parseEx(UavAirlineUploadEnum.class, value);
}
@ -125,8 +125,7 @@ public class JmJobUavServiceImpl implements JmJobUavService {
})
.toArray(JmJobUav[]::new)
);
String[] kmzDirPath = fileTypeService.getDirectoryPath(FileTypeEnum.UAV_KMZ, jobId);
String[] txtFilePath = fileTypeService.getFilePath(FileTypeEnum.UAV_TXT, jobId, "航线.txt");
list.forEach(uav -> {
Long uavId = uav.getUavId();
jobPayloadService.insert(jobId, uavId, uav.getPayloadList());

View File

@ -3,6 +3,7 @@ package com.zhangy.skyeye.jm.task;
import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.device.entity.SkyeyePayload;
import com.zhangy.skyeye.device.service.IPayloadService;
import com.zhangy.skyeye.jm.dto.JmJobStatusDTO;
import com.zhangy.skyeye.publics.consts.CacheKey;
import com.zhangy.skyeye.publics.service.ISysLoginService;
import com.zhangy.skyeye.sar.enums.SarControlTypeEnum;
@ -10,13 +11,16 @@ import com.zhangy.skyeye.jm.service.JmJobStatusService;
import com.zhangy.skyeye.sar.exception.SarConnectException;
import com.zhangy.skyeye.sar.service.ISarControlService;
// import com.zhangy.skyeye.smp.dto.SmpUavStatusWsDTO;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
/**
@ -26,6 +30,10 @@ import java.util.List;
@Service
public class JmTaskScheduler {
@Value("${skyeye.debug:false}")
@Setter
private boolean isDebug;
@Autowired
private RedisUtil redisUtil;
@ -58,11 +66,11 @@ public class JmTaskScheduler {
// if (!loginService.hasLogged()) {
// return;
// }
// // 从缓存查询所有sar
// List<SztkPayload> sarList = payloadService.getSar(null);
// if (ObjectUtil.isEmpty(sarList)) {
// return;
// }
// 从缓存查询所有sar
// Collection<JmJobStatusDTO> list = jobStatusService.getAll();
// Map<Long, JmUavStatusDTO> jobSarMap = list.stream()
// .flatMap(job -> job.getUavMap().values().stream())
// .collect(Collectors.toMap(JmUavStatusDTO::getSarId, Function.identity(), (k1, k2) -> k1));
// // 从内存查询所有任务中的雷达
// Map<Long, JmUavStatusDTO> jobSarMap = jobStatusService.getAll().stream()
// .flatMap(job -> job.getUavMap().values().stream())
@ -107,7 +115,7 @@ public class JmTaskScheduler {
*/
@Scheduled(fixedRate = 10000)
public void connectSar() {
if (!loginService.hasLogged()) { // 断开
if (!loginService.hasLogged() || isDebug) { // 断开
/*Map<Object, Object> map = redisUtil.hmget(CacheKey.SAR_CONNECTED);
if (map != null && map.size() > 0) {
map.keySet().forEach(ip -> controlInfoService.sendUdp((String) ip, SarControlTypeEnum.DISCONNECT));

View File

@ -23,8 +23,8 @@ public class AopConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 允许所有路径
.allowedOrigins("*") // 允许所有来源生产环境指定域名
// .allowedOriginPatterns("*")
// .allowedOrigins("*") // 允许所有来源生产环境指定域名
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的 HTTP 方法
.allowedHeaders("*") // 允许所有请求头
.allowCredentials(false) // 是否允许发送 Cookietrue 时需要指定具体 origins

View File

@ -24,8 +24,8 @@ public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint(new String[]{"/ws"})
.setAllowedOrigins(new String[]{"*"})
// .setAllowedOriginPatterns(new String[]{"*"})
// .setAllowedOrigins(new String[]{"*"})
.setAllowedOriginPatterns(new String[]{"*"})
.withSockJS();
}

View File

@ -5,13 +5,13 @@ package com.zhangy.skyeye.publics.consts;
public class CacheKey {
/*==========================================
publics 模块用 ld: 前缀
publics 模块用 skyeye: 前缀
==========================================*/
/**
* 用户token30分钟
*/
private static final String USER_TOEKN = "ld:user:token:%s@%s";
private static final String USER_TOEKN = "skyeye:user:token:%s@%s";
/**
* 获取token key
@ -21,32 +21,32 @@ public class CacheKey {
}
/*==========================================
分布式锁用 ld:lock: 前缀
分布式锁用 skyeye:lock: 前缀
==========================================*/
/**
* 上传sar图像锁
*/
public static final String SAR_IMAGE_UPLOAD_LOCK = "ld:lock:image";
public static final String SAR_IMAGE_UPLOAD_LOCK = "skyeye:lock:image";
/*==========================================
sar 模块用 ld:sar: 前缀
sar 模块用 skyeye:sar: 前缀
==========================================*/
/**
* 控制回包1秒
*/
private static final String SAR_CONTROL_BACK = "ld:sar:control";
private static final String SAR_CONTROL_BACK = "skyeye:sar:control";
/**
* sar状态1秒
*/
private static final String SAR_STATUS = "ld:sar:status:";
private static final String SAR_STATUS = "skyeye:sar:status:";
/**
* 已执行连接命令的sar永久
*/
public static final String SAR_CONNECTED = "ld:sar:connected";
public static final String SAR_CONNECTED = "skyeye:sar:connected";
/**
* 获取控制回包key加ip
@ -63,50 +63,50 @@ public class CacheKey {
}
/*==========================================
device 模块用 ld:device: 前缀
device 模块用 skyeye:device: 前缀
==========================================*/
/**
* sar载荷永久
*/
public static final String DEVICE_SAR = "ld:device:sar";
public static final String DEVICE_SAR = "skyeye:device:sar";
/**
* 无人机永久
*/
public static final String DEVICE_UAV = "ld:device:uav";
public static final String DEVICE_UAV = "skyeye:device:uav";
/*==========================================
smp 模块用 ld:smp: 前缀
smp 模块用 skyeye:smp: 前缀
==========================================*/
/**
* 运动规划响应1秒
*/
public static final String SMP_WAYPOINT_RES = "ld:smp:waypoint:";
public static final String SMP_WAYPOINT_RES = "skyeye:smp:waypoint:";
/**
* 飞行控制响应1秒
*/
public static final String SMP_FLIGHT_RES = "ld:smp:flight:";
public static final String SMP_FLIGHT_RES = "skyeye:smp:flight:";
/**
* 云台控制响应1秒
*/
public static final String SMP_GIMBALMGR_RES = "ld:smp:gimbalmgr:";
public static final String SMP_GIMBALMGR_RES = "skyeye:smp:gimbalmgr:";
/**
* 数据订阅响应1秒
*/
public static final String SMP_SUBSCRIPT_RES = "ld:smp:subscript:";
public static final String SMP_SUBSCRIPT_RES = "skyeye:smp:subscript:";
/**
* 数据订阅回传数据1秒
*/
public static final String SMP_SUBSCRIPT_DATA = "ld:smp:subscript:";
public static final String SMP_SUBSCRIPT_DATA = "skyeye:smp:subscript:";
/**
* 相机视频流响应1秒
*/
public static final String SMP_VIDEO_RES = "ld:smp:video:";
public static final String SMP_VIDEO_RES = "skyeye:smp:video:";
}

View File

@ -27,11 +27,7 @@ public enum FileTypeEnum implements CodeEnum<Integer> {
SAR_WAVE(8, "SAR波形", "/sar/wave"),
// ========================
// 个别项目专用文件类型
// ========================
UAV_TXT(101, "无人机txt", "/sar/uav/txt"), // 青岛港专用人工拷贝航线数据到控制台
SAR_MTI_LIB(9, "SAR运动轨迹", "/sar/mti"),
;
/** 值 */

View File

@ -19,6 +19,11 @@ public interface WebSocketKey {
*/
String SAR_BACK_IMAGE = "/topic/image";
/**
* 回传动点
*/
String SAR_BACK_GMTI = "/topic/gmti";
/*===============================
任务管理
===============================*/

View File

@ -13,7 +13,7 @@ public class SysFileTypeService {
private String fileRoot;
// 去掉末尾的路径分隔符
@Value("${ld.file-root}")
@Value("$skyeye.file-root}")
public void setFileRoot(String fileRoot) {
this.fileRoot = fileRoot.endsWith("/") || fileRoot.endsWith("\\") ? fileRoot.substring(0, fileRoot.length() - 1) : fileRoot;
}

View File

@ -22,7 +22,7 @@ public class SysLoginServiceImpl implements ISysLoginService {
return Boolean.TRUE.equals(redisTemplate.execute((RedisCallback<Boolean>) connection -> {
try (Cursor<byte[]> cursor = connection.scan(
ScanOptions.scanOptions()
.match("ld:user:token:*")
.match("skyeye:user:token:*")
.count(1) // 只需要找到1个匹配项
.build())) {
return cursor.hasNext();
@ -38,7 +38,7 @@ public class SysLoginServiceImpl implements ISysLoginService {
return (int) redisTemplate.execute((RedisCallback<Integer>) connection -> {
int count = 0;
ScanOptions options = ScanOptions.scanOptions()
.match("ld:user:token:*")
.match("skyeye:user:token:*")
.count(100) // 合理的COUNT值
.build();

View File

@ -7,8 +7,6 @@ import com.zhangy.skyeye.common.extend.util.NumberUtil;
*/
public class CoordUtil {
// 圆周率
private static final double M_PI = 3.14159265358979323846;
// 地球平均半径
private static final double EARTH_RADIUS = 6371000;
// 地球赤道半径
@ -19,22 +17,58 @@ public class CoordUtil {
private static final double ECCENTRICITY_SQUARED = 6.69437999014e-3;
/**
* 弧度转经纬度
*
* @param rad 弧度值
* @return
* 将角度转换为弧度
*/
public static Double radToCoord(Double rad) {
if (!NumberUtil.isValid(rad)) {
return null;
}
return rad * 180 / M_PI;
public static double toRadians(double degrees) {
return degrees * Math.PI / 180.0;
}
/**
* 将弧度转换为角度
*/
public static double toDegrees(double radians) {
return radians * 180.0 / Math.PI;
}
/**
* 计算两点之间的方位角从点1到点2北为0°东为90°
*/
public static double calculateBearing(double lat1, double lon1, double lat2, double lon2) {
double lat1Rad = toRadians(lat1);
double lat2Rad = toRadians(lat2);
double lonDiffRad = toRadians(lon2 - lon1);
double y = Math.sin(lonDiffRad) * Math.cos(lat2Rad);
double x = Math.cos(lat1Rad) * Math.sin(lat2Rad) -
Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(lonDiffRad);
double bearing = Math.atan2(y, x);
return (toDegrees(bearing) + 360) % 360; // 转换为0-360度
}
/**
* 计算给定起点方位角和距离的终点坐标
*/
public static double[] calculateDestination(double lat, double lon, double bearing, double distance) {
double latRad = toRadians(lat);
double lonRad = toRadians(lon);
double bearingRad = toRadians(bearing);
double angularDistance = distance / EARTH_RADIUS;
double lat2Rad = Math.asin(Math.sin(latRad) * Math.cos(angularDistance) +
Math.cos(latRad) * Math.sin(angularDistance) * Math.cos(bearingRad));
double lon2Rad = lonRad + Math.atan2(Math.sin(bearingRad) * Math.sin(angularDistance) * Math.cos(latRad),
Math.cos(angularDistance) - Math.sin(latRad) * Math.sin(lat2Rad));
return new double[]{toDegrees(lat2Rad), toDegrees(lon2Rad)};
}
/**
* 两坐标点二维距离大圆距离
*/
public static double getDistance(double lat1, double lon1, double lat2, double lon2) {
public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
// 将经纬度转换为弧度
double latDistance = Math.toRadians(lat2 - lat1);
double lonDistance = Math.toRadians(lon2 - lon1);
@ -51,7 +85,7 @@ public class CoordUtil {
/**
* 两坐标点二维距离平面距离
*/
public static double getPlaneDistance(double lat1, double lon1, double lat2, double lon2) {
public static double calculatePlaneDistance(double lat1, double lon1, double lat2, double lon2) {
// 将经纬度转换为弧度
double lat1Rad = Math.toRadians(lat1);
double lon1Rad = Math.toRadians(lon1);
@ -66,13 +100,6 @@ public class CoordUtil {
return Math.sqrt(x * x + y * y);
}
/**
* 两坐标点二维距离平面距离
*/
public static int getPlaneDistanceInt(double lat1, double lon1, double lat2, double lon2) {
return (int) Math.round(getPlaneDistance(lat1, lon1, lat2, lon2));
}
/**
* 将经纬度高度转换为ECEF坐标系下的XYZ坐标
* @param latitude 纬度
@ -105,7 +132,7 @@ public class CoordUtil {
* @param alt2 点2高度
* @return 三维直线距离
*/
public static double get3dDistance(
public static double calculate3dDistance(
double lat1, double lon1, double alt1,
double lat2, double lon2, double alt2) {

View File

@ -34,7 +34,7 @@ public class PyAirlineServiceImpl implements IPyAirlineService {
* 航线规划算法路径
*/
@Setter
@Value("${ld.py.ktkxUrl}")
@Value("${skyeye.py.ktkxUrl}")
private String url;
// 判断是否为空并剔除无需执行任务的无人机

View File

@ -21,7 +21,7 @@ import java.net.http.HttpResponse;
* 影像识别
*/
@Slf4j
// @Service
@Service
public class PyImageServiceImpl implements IPyImageService {
/**
@ -39,7 +39,7 @@ public class PyImageServiceImpl implements IPyImageService {
}
@Setter
@Value("${ld.py.detectUrl}")
@Value("${skyeye.py.detectUrl}")
private String url;
@Override

View File

@ -0,0 +1,29 @@
package com.zhangy.skyeye.sar.consts;
import com.zhangy.skyeye.common.extend.enums.CodeEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* SAR成像模式
*/
@Getter
@AllArgsConstructor
public enum SarImageModeEnum implements CodeEnum<Byte> {
TD((byte) 0, "条带"),
JS((byte) 1, "聚束"),
GMTI((byte) 4, "GMTI")
;
private byte value;
private String text;
@Override
public Byte getCode() {
return value;
}
}

View File

@ -64,7 +64,7 @@ public class SarControlContext {
/**
* 应答超时
*/
@Value("${ld.sar.image.type:0}")
@Value("${skyeye.sar.image.type:0}")
private byte imgType;
@PostConstruct

View File

@ -26,7 +26,8 @@ public class SarControlUploadStrategy implements ISarControlStrategy {
/**
* 有效的分辨率
*/
private final List<Float> VALID_RESOLUTIONS = Arrays.asList(0.1f, 0.2f, 0.3f, 0.5f, 1f, 3f);
private final List<Float> VALID_RESOLUTIONS = Arrays.asList(0.15f, 0.2f, 0.3f, 0.5f, 1f, 3f, 10f);
@Override
public boolean supports(SarControlParamDTO param) {
@ -41,9 +42,10 @@ public class SarControlUploadStrategy implements ISarControlStrategy {
throw ServiceException.noLog("航线参数airlineList不能为空");
}
float resolution = param.getResolution();
if (!VALID_RESOLUTIONS.contains(resolution)) {
throw ServiceException.noLog("无效的分辨率:" + resolution);
}
byte imageMode = param.getImageMode();
// if (!VALID_RESOLUTIONS.contains(resolution)) {
// throw ServiceException.noLog("无效的分辨率:" + resolution);
// }
return list.stream()
.map(line -> {
// 复制航线参数
@ -52,6 +54,7 @@ public class SarControlUploadStrategy implements ISarControlStrategy {
BeanUtil.copyProperties(param, sar, false);
sar.setSubImageMode((byte)(resolution / 0.05));
sar.setControlType(SarControlTypeEnum.UPLOAD);
sar.setImageMode(imageMode);
return sar;
})
.collect(Collectors.toList());

View File

@ -0,0 +1,67 @@
package com.zhangy.skyeye.sar.controller;
import com.zhangy.skyeye.common.extend.util.FileUtil;
import com.zhangy.skyeye.sar.dto.SarMtiPointGroupDTO;
import com.zhangy.skyeye.sar.dto.SarMtiQueryParam;
import com.zhangy.skyeye.sar.service.ISarMtiPointService;
import com.zhangy.skyeye.sar.service.ISarMtiTrailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.Arrays;
@Validated
@RestController
@RequestMapping("/sar/mti")
public class SarMtiController {
@Autowired
private ISarMtiPointService sarMtiPointService;
@Autowired
private ISarMtiTrailService sarMtiTrailService;
/**
* 点列表查询
*/
@RequestMapping("/point/list")
public Object selectPointList(@Valid @RequestBody SarMtiQueryParam param) {
return sarMtiPointService.selectList(param);
}
/**
* 轨迹列表查询
*/
@RequestMapping("/trail/list")
public Object selectTrailList(@Valid @RequestBody SarMtiQueryParam param) {
return sarMtiTrailService.selectList(param);
}
@Autowired
private ISarMtiPointService mtiPointService;
@GetMapping("/testTrail")
public Object testTrail() {
// 测试数据
long airlineId = System.currentTimeMillis();
long payloadId = 10001l;
long jobId = 9999l;
// 取文件前8k数据作为参数
byte[] td= FileUtil.read("D:\\测试数据\\mti1.dat");
for (int i = 1; i*8192 < td.length; i++) {
byte[] data = Arrays.copyOfRange(td, (i-1)*8192, i*8192);
SarMtiPointGroupDTO group = SarMtiPointGroupDTO.parse(data, payloadId, airlineId);
// 保存
if (group == null)
continue;
mtiPointService.save(jobId, jobId, payloadId, airlineId, group, data);
}
return "ok";
}
}

View File

@ -1,6 +1,7 @@
package com.zhangy.skyeye.sar.dto;
import cn.hutool.core.bean.BeanUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.zhangy.skyeye.common.extend.enums.EnumUtil;
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
import com.zhangy.skyeye.sar.enums.SarControlTypeEnum;
@ -55,6 +56,11 @@ public class SarControlParamDTO implements Serializable, IBaseCheckService {
*/
private byte imageBit;
/**
* 成像模式
*/
private byte imageMode;
/**
* 航线信息若不为空则无需调用航线算法此时可忽略参数 airlineParam
*/
@ -69,6 +75,7 @@ public class SarControlParamDTO implements Serializable, IBaseCheckService {
* 转实体
* @return
*/
@JsonIgnore
public SarControlDTO getSarControl() {
SarControlDTO e = BeanUtil.copyProperties(this, SarControlDTO.class);
return e;

View File

@ -0,0 +1,51 @@
package com.zhangy.skyeye.sar.dto;
import lombok.Data;
@Data
public class SarFlightPlanDTO {
// 输入参数
private double targetLat;
private double targetLon;
private double takeoffLat;
private double takeoffLon;
private double height;
private double lookAngle;
private int radarSide;
// 计算结果
private double slantRange; // 斜距R
private double apertureLength; // 合成孔径长度La
private double sideOffset; // 侧视偏移D_offset
private double flightHeadingAngle; // 航线方向
// 关键点坐标
private double p0Lat; // 航迹投影点P0
private double p0Lon;
private double startLat; // 航线起点S
private double startLon;
private double endLat; // 航线终点E
private double endLon;
private double powerOnLat; // 开机点A
private double powerOnLon;
private double powerOffLat; // 关机点B
private double powerOffLon;
// 距离信息
private double routeLength; // 航线段长度S->E
private double totalDistance; // 总飞行距离
@Override
public String toString() {
return "起点:" + startLon + "," + startLat + "\n" +
"开机点:" + powerOnLon + "," + powerOnLat + "\n" +
"投影点:" + p0Lon + "," + p0Lat + "\n" +
"关机点:" + powerOffLon + "," + powerOffLat + "\n" +
"终点:" + endLon + "," + endLat + "\n" +
"侧视方向:" +(radarSide == 1 ? "" : "") + "\n" +
"斜距:" + slantRange + "\n" +
"孔径:" + apertureLength + "\n" +
"航线长度:" + routeLength;
}
}

View File

@ -72,9 +72,6 @@ public class SarImagePacketDTO extends SarBackPacketDTO {
return null;
}
if (pkt.isLast) {
int x = 1;
}
// 创建Packet对象
pkt.data = pktData;
return pkt;
@ -86,4 +83,13 @@ public class SarImagePacketDTO extends SarBackPacketDTO {
crc.update(data);
return crc.getValue();
}
@Override
public String toString() {
return "帧ID" + sessionId +
",帧序号:" + packetNo +
",数据大小:" + size +
",是否尾包:" + (isLast ? "" : "") +
",校验:" + crc32;
}
}

View File

@ -0,0 +1,68 @@
package com.zhangy.skyeye.sar.dto;
import com.zhangy.skyeye.sar.entity.SarMtiPoint;
import lombok.Data;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Date;
/**
* SAR回传点
*/
@Data
public class SarMtiPointGroupDTO {
private Long jobConfId;
private Long jobExecId;
private Long airlineExecId;
private byte[] data;
private Long payloadId;
//--------------
/**
* 目标数量最大384
*/
private int pointNum;
/**
* 目标点
*/
private SarMtiPoint[] points;
public static SarMtiPointGroupDTO parse(byte[] data, Long payloadId, Long airlineId) {
SarMtiPointGroupDTO dto = new SarMtiPointGroupDTO();
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
//System.out.println(Integer.toHexString(Short.toUnsignedInt(buffer.getShort())));
buffer.position(510);
dto.pointNum = Short.toUnsignedInt(buffer.getShort());
if (dto.pointNum <= 0) {
return dto;
}
dto.points = new SarMtiPoint[dto.pointNum];
byte[] tarData = new byte[SarMtiPoint.SIZE];
Date createTime = new Date();
for (int i = 0; i < dto.pointNum; i++) {
buffer.get(tarData);
dto.points[i] = new SarMtiPoint(tarData, payloadId, airlineId, createTime);
}
return dto;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("目标数量:").append(pointNum).append("\n");
if (pointNum > 0) {
for (SarMtiPoint tar : points) {
builder.append("\t").append(tar).append("\n");
}
}
return builder.toString();
}
}

View File

@ -0,0 +1,33 @@
package com.zhangy.skyeye.sar.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.zhangy.skyeye.common.extend.dto.QueryDTO;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* 列表查询参数
*/
@Data
public class SarMtiQueryParam extends QueryDTO {
/**
* 载荷
*/
@NotNull(message = "载荷不能为空")
private Long payloadId;
/**
* 开始时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date beginTime;
/**
* 结束时间
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
}

View File

@ -0,0 +1,55 @@
package com.zhangy.skyeye.sar.dto;
import com.zhangy.skyeye.sar.entity.SarMtiTrail;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Date;
/**
* mti-lib 返回结构
*/
@Slf4j
@Data
public class SarMtiTrailGroupDTO {
/**
* 轨迹个数最大334
*/
private int trailNum;
/**
* 目标轨迹
*/
private SarMtiTrail[] trails;
public SarMtiTrailGroupDTO(Long payloadId, Long airlineId, byte[] data) {
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
buffer.position(510);
this.trailNum = Short.toUnsignedInt(buffer.getShort());
this.trails = new SarMtiTrail[trailNum];
Date date = new Date();
for (int i = 0; i < trailNum; i++) {
byte[] pointData = new byte[20];
try {
buffer.get(pointData);
} catch (BufferUnderflowException ex) {
log.error("轨迹生成错误:字节数不够,包数据量" + data.length + ",轨迹数" + trailNum + ",i=" + i + ",剩余" + buffer.remaining());
}
trails[i] = new SarMtiTrail(payloadId, airlineId, pointData, date);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("轨迹数:").append(trailNum);
for (SarMtiTrail trail : trails) {
builder.append("\t").append(trail).append("\n");
}
return builder.toString();
}
}

View File

@ -0,0 +1,43 @@
package com.zhangy.skyeye.sar.dto;
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
import com.zhangy.skyeye.sar.entity.SarMtiPoint;
import com.zhangy.skyeye.sar.entity.SarMtiTrail;
import lombok.Data;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 推送运动点
*/
@Data
public class SarMtiWsDTO {
/** 任务配置ID */
private Long jobId;
/** 载荷ID */
private Long payloadId;
/**
* 点迹
*/
private SarMtiPoint[] points;
/**
* 轨迹
*/
private Collection<List<SarMtiTrail>> trails;
public SarMtiWsDTO(Long jobId, Long payloadId, SarMtiPoint[] points, SarMtiTrail[] trails) {
this.jobId = jobId;
this.payloadId = payloadId;
this.points = points;
if (ObjectUtil.isNotEmpty(trails)) {
this.trails = Stream.of(trails).collect(Collectors.groupingBy(SarMtiTrail::getNo)).values();
}
}
}

View File

@ -0,0 +1,19 @@
package com.zhangy.skyeye.sar.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class SarMtiBase implements Serializable {
/** 载荷ID */
protected Long payloadId;
/** 执行航线ID */
protected Long airlineId;
/** 创建时间 */
protected Date createTime;
}

View File

@ -0,0 +1,78 @@
package com.zhangy.skyeye.sar.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.zhangy.skyeye.common.extend.util.NumberUtil;
import lombok.Data;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Date;
@Data
public class SarMtiPoint extends SarMtiBase {
@JsonIgnore
public static final int SIZE = 20;
/**
* 编号 0-65535
*/
private int no;
/**
* 距离
*/
private int rg;
/**
* 方位角 LSB=0.01°
*/
private float azimuth;
/**
* 速度 LSB=0.01m/s
*/
private float speed;
/**
* 高度
*/
private short alt;
/**
* 幅度 LSB=0.01dB
*/
private float amp;
/**
* 经度 LBS=1.0e-6°
*/
private double lon;
/**
* 纬度 LBS=1.0e-6°
*/
private double lat;
public SarMtiPoint(byte[] data, Long payloadId, Long airlineId, Date createTime) {
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
this.no = Short.toUnsignedInt(buffer.getShort());
this.rg = Short.toUnsignedInt(buffer.getShort());
this.azimuth = buffer.getShort() / 100f;
this.speed = buffer.getShort() / 100f;
this.alt = buffer.getShort();
this.amp = Short.toUnsignedInt(buffer.getShort()) / 100f;
this.lon = buffer.getInt() / 1_000_000.0;
this.lat = buffer.getInt() / 1_000_000.0;
this.createTime = createTime;
this.payloadId = payloadId;
this.airlineId = airlineId;
}
@Override
public String toString() {
return "目标编号:" + no + ",距离:" + rg + ",方位角:" + azimuth + ",幅度:" + amp +
",速度:" + speed + ",高度:" + alt + ",经度:" + lon + ",纬度:" + lat;
}
}

View File

@ -0,0 +1,74 @@
package com.zhangy.skyeye.sar.entity;
import com.zhangy.skyeye.common.extend.util.NumberUtil;
import lombok.Data;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Date;
@Data
public class SarMtiTrail extends SarMtiBase {
/**
* 航迹批号 UINT16 0-65535
*/
private int no;
/**
* 航迹点高度
*/
private short alt;
/**
* 航迹点经度
*/
private double lon;
/**
* 航迹点纬度
*/
private double lat;
/**
* 速度
*/
private float speed;
/**
* 航向角
*/
private float headingAngle;
/**
* 幅度 LSB=0.01dB
*/
private float amp;
// 预留
public SarMtiTrail(Long payloadId, Long airlineId, byte[] data, Date date) {
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
this.no = Short.toUnsignedInt(buffer.getShort());
this.alt = buffer.getShort();
this.lon = buffer.getInt() / 1_000_000.0;
this.lat = buffer.getInt() / 1_000_000.0;
this.speed = buffer.getShort() / 10f;
this.headingAngle = buffer.getShort() / 10f;
this.amp = buffer.getShort() / 100f;
this.payloadId = payloadId;
this.airlineId = airlineId;
this.createTime = date;
}
@Override
public String toString() {
return "轨迹批号:" + no +
",经度:" + lon +
",纬度:" + lat +
",高度:" + alt +
",幅度:" + amp +
",航向角:" + headingAngle;
}
}

View File

@ -0,0 +1,49 @@
package com.zhangy.skyeye.sar.lib;
import com.sun.jna.Memory;
import com.zhangy.skyeye.common.extend.util.FileUtil;
/**
* lib调用处理
*/
public class SarMtiLibProcessor {
private static final SarMtiLibrary lib = SarMtiLibrary.INSTANCE;
private static final int OUTPUT_SIZE = 8192;
/**
* 调用 MTIDotProc
*/
public static byte[] mtiDotProc(byte[] inputData, String outputPath) {
// 验证参数
if (inputData == null || inputData.length == 0) {
throw new IllegalArgumentException("输入数据不能为空");
}
if (outputPath == null || outputPath.trim().isEmpty()) {
throw new IllegalArgumentException("输出路径不能为空");
}
// 确保输出目录存在
FileUtil.createDir(outputPath, true);
// 执行
try (Memory inputMem = allocateAndFill(inputData);
Memory outputMem = new Memory(OUTPUT_SIZE)) {
lib.MTIDotProc(inputMem, outputMem, outputPath);
byte[] result = new byte[OUTPUT_SIZE];
outputMem.read(0, result, 0, OUTPUT_SIZE);
return result;
} catch (Exception e) {
throw new RuntimeException("MTIDotProc调用失败", e);
}
}
/**
* 分配内存
*/
private static Memory allocateAndFill(byte[] data) {
Memory mem = new Memory(data.length);
mem.write(0, data, 0, data.length);
return mem;
}
}

View File

@ -0,0 +1,60 @@
package com.zhangy.skyeye.sar.lib;
import com.sun.jna.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public interface SarMtiLibrary extends Library {
/**
* 功能获取头位置LZX算法
* 输入dataAr - 数据数组指针
* arLen - 数组长度
* 返回头位置
*/
int GetHeadPosition_LZX(Pointer dataAr, long arLen);
/**
* 功能处理动目标检测和凝聚后的点迹完成测角和航迹起始和跟踪
* 输入dataAr - 按照WAS-GMTI格式打包的点迹信息和辅助数据
* dataRes - 处理结果输出缓冲区
* outpath - 航迹输出文件路径
*/
void MTIDotProc(Pointer dataAr, Pointer dataRes, String outpath);
/**
* 功能根据图像四个角点的经纬度计算任意点的经纬度位置
* 输入pos_info - 图像四个角的经纬度位置数组
* grid_info - 图像四个角的坐标位置数组
* px - 像素行位置
* py - 像素列位置
* 输出latlogn - 输出的经纬度位置数组长度为2[经度, 纬度]
*/
void ArbitaryPosCal(Pointer pos_info, Pointer grid_info, long px, long py, Pointer latlogn);
// 自定义FunctionMapper
class CustomFunctionMapper implements FunctionMapper {
@Override
public String getFunctionName(NativeLibrary library, Method method) {
switch (method.getName()) {
case "MTIDotProc": return "?MTIDotProc@@YAXPEAE0PEAD@Z";
case "GetHeadPosition_LZX": return "?GetHeadPosition_LZX@@YAHPEAEJ@Z";
case "ArbitaryPosCal": return "?ArbitaryPosCal@@YAXPEANPEAJJJ0@Z";
default: return method.getName();
}
}
}
// 配置选项
Map<String, Object> OPTIONS = new HashMap<>() {{
put(Library.OPTION_FUNCTION_MAPPER, new CustomFunctionMapper());
put(Library.OPTION_STRING_ENCODING, "UTF-8"); // 字符串编码Windows中文用GBK
}};
// 加载库
SarMtiLibrary INSTANCE = Native.load("MulTarTrk", SarMtiLibrary.class, OPTIONS);
}

View File

@ -1,5 +1,7 @@
package com.zhangy.skyeye.sar.listen;
import com.zhangy.skyeye.sar.task.DiscardOldestPolicyWithLog;
import com.zhangy.skyeye.sar.task.PriorityThreadFactory;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.PostConstruct;
@ -12,6 +14,11 @@ import java.net.SocketTimeoutException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.*;
/**
* 监听器抽象使用线程池处理udp数据
*/
@ -179,4 +186,23 @@ public abstract class SarAbstractListener {
return status.toString();
}
/**
* 发送ACK包
*
* @param ack 当前包序号
* @param ackAc 当前最大包序号
*/
protected void sendAck(DatagramSocket socket, InetAddress addr, int port, int ack, int ackAc) {
try {
ByteBuffer bb = ByteBuffer.allocate(8);
bb.order(ByteOrder.BIG_ENDIAN);
bb.putInt(ack);
bb.putInt(ackAc);
DatagramPacket ackPacket = new DatagramPacket(bb.array(), bb.array().length, addr, port);
socket.send(ackPacket);
} catch (IOException e) {
log.error("" + addr.getHostAddress() + "发送ACK失败: " + e.getMessage());
}
}
}

View File

@ -71,34 +71,18 @@ public class SarImageListener extends SarAbstractListener {
return;
}
SarImagePacketGroupDTO group = sarImageService.putPacket(ip, backPackDTO);
if (group != null) {
Integer maxNo = group.getMaxNo();
// 发送ACK
if (maxNo != null) {
sendAck(socket, packet.getAddress(), packet.getPort(), backPackDTO.getPacketNo(), maxNo);
}
}
}
/**
* 发送ACK包
* @param socket
* @param addr
* @param port
* @param ack 当前包序号
* @param ackAc 当前最大包序号
*/
private static void sendAck(DatagramSocket socket, InetAddress addr, int port, int ack, int ackAc) {
try {
ByteBuffer bb = ByteBuffer.allocate(8); // 2 * sizeof(uint32_t)
bb.order(ByteOrder.BIG_ENDIAN);
bb.putInt(ack);
bb.putInt(ackAc);
DatagramPacket ackPacket = new DatagramPacket(bb.array(), bb.array().length, addr, port);
socket.send(ackPacket);
} catch (IOException e) {
log.error("" + addr.getHostAddress() + "发送ACK失败: " + e.getMessage());
SarImagePacketGroupDTO group = sarImageService.putPacket(ip, backPackDTO);
if (group != null) {
Integer maxNo = group.getMaxNo();
// 发送ACK
if (maxNo != null) {
sendAck(socket, packet.getAddress(), packet.getPort(), backPackDTO.getPacketNo(), maxNo);
}
}
} catch (Exception ex) {
log.error("接收图像包错误:" + ex.getMessage(), ex);
return;
}
}
}

View File

@ -0,0 +1,79 @@
package com.zhangy.skyeye.sar.listen;
import com.zhangy.skyeye.device.service.IPayloadService;
import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO;
import com.zhangy.skyeye.jm.dto.JmUavStatusDTO;
import com.zhangy.skyeye.jm.service.JmJobStatusService;
import com.zhangy.skyeye.sar.dto.SarImagePacketDTO;
import com.zhangy.skyeye.sar.dto.SarMtiPointGroupDTO;
import com.zhangy.skyeye.sar.service.ISarMtiPointService;
import com.zhangy.skyeye.sar.task.CircularBufferQueue;
import com.zhangy.skyeye.sar.task.DiscardOldestPolicyWithLog;
import com.zhangy.skyeye.sar.task.PriorityThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.DatagramPacket;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 回传动点
*
*/
@Slf4j
@Component
public class SarMtiListener extends SarAbstractListener {
private final int port = 39004;
private final int size = 1472 * 30;
@Autowired
private IPayloadService payloadService;
@Autowired
private ISarMtiPointService mtiPointService;
@Autowired
private JmJobStatusService jmJobStatusService;
@Autowired
private SarMtiUdpProcessor sarMtiUdpProcessor;
@Override
protected void init() {
super.running = true;
super.port = this.port;
super.size = this.size;
executor = new ThreadPoolExecutor(
1, 5, 30, TimeUnit.SECONDS,
new CircularBufferQueue(50),
new PriorityThreadFactory("SarMti-", Thread.MAX_PRIORITY - 1),
new DiscardOldestPolicyWithLog()
);
}
@Override
protected void processData(DatagramPacket packet) throws IOException {
socket.receive(packet);
// 过滤无效数据包丢弃过小的包
if (packet.getLength() == 0 || packet.getLength() < 17) {
return;
}
String ip = packet.getAddress().getHostAddress();
// 处理接收到的数据
SarImagePacketDTO packetDTO = null;
packetDTO = SarImagePacketDTO.parse(packet.getData(), packet.getLength());
if (packetDTO == null) {
return;
}
int packetNo = packetDTO.getPacketNo();
sendAck(socket, packet.getAddress(), packet.getPort(), packetNo, packetNo);
sarMtiUdpProcessor.putPacket(ip, packetDTO);
}
}

View File

@ -0,0 +1,194 @@
package com.zhangy.skyeye.sar.listen;
import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO;
import com.zhangy.skyeye.jm.dto.JmUavStatusDTO;
import com.zhangy.skyeye.jm.entity.JmImage;
import com.zhangy.skyeye.jm.service.JmJobStatusService;
import com.zhangy.skyeye.sar.dto.*;
import com.zhangy.skyeye.sar.service.ISarImageService;
import com.zhangy.skyeye.sar.service.ISarMtiPointService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 雷达回传图像处理统一转为4通道png格式
*/
@Primary
@Slf4j
@Service
public class SarMtiUdpProcessor extends SarAbstractUdpProcessor<SarImagePacketGroupDTO, SarImagePacketDTO> {
@Autowired
private ISarImageService sarImageService;
@Autowired
private JmJobStatusService jobStatusService;
@Autowired
private ISarMtiPointService mtiPointService;
private BlockingQueue<SarMtiPointGroupDTO> packetQueue = new LinkedBlockingQueue<>(1000);
private Thread mtiProcessThread = new Thread(this::startProcessing);
public SarMtiUdpProcessor() {
super.running = true;
this.mtiProcessThread.start();
}
protected void afterJoin(byte[] framePacketData, SarImagePacketGroupDTO group) {
JmUavStatusDTO uav = jobStatusService.getCurrUav(group.getSourceIp());
if (uav == null) {
log.info("没有任务中的雷达丢弃GMTI包");
return;
}
JmAirlineStatusDTO airlineDTO = uav.getCurrAirline();
if (airlineDTO == null) {
log.info("没有进行中的航线丢弃GMTI包");
return;
}
Long payloadId = uav.getSarId();
Long airlineExecId = airlineDTO.getExecId();
try {
SarMtiPointGroupDTO mtiFrame = SarMtiPointGroupDTO.parse(framePacketData, payloadId, airlineExecId);
if (mtiFrame == null) {
log.info("无法解析丢弃GMTI包");
return;
}
mtiFrame.setJobConfId(uav.getJobId());
mtiFrame.setJobExecId(uav.getJobExecId());
mtiFrame.setPayloadId(payloadId);
mtiFrame.setAirlineExecId(airlineExecId);
mtiFrame.setData(framePacketData);
// 放入队列非阻塞方式
if (!packetQueue.offer(mtiFrame, 10, TimeUnit.MILLISECONDS)) {
log.warn("接收队列已满,丢弃数据包");
}
} catch (Exception ex) {
log.error("接收gmti包错误" + ex.getMessage(), ex);
return;
}
}
/**
* 判断是否可以合并回图帧首包不能丢
* @param group
* @return
*/
public boolean canJoin(SarImagePacketGroupDTO group) {
Map<Integer, SarImagePacketDTO> packets = group.getPackets();
//log.info("定时判断合并udp" + group.getSessionId() + "" + packets.keySet());
// 首包包含图片格式信息不能丢否则图片无法打开
return packets.get(0) != null;
}
/**
* 将upd分包数据合并为完整帧帧数据 + 头尾校验因为无法确定分包大小分包缺失无法补充0
* 合并后删除frameMap的分包数据
* 使用ByteBuffer替代ByteArrayOutputStream以减少内存分配ByteArrayOutputStream toByteArray()底层用Arrays.copyOf实现
* 有性能开销ByteBuffer的array()无需拷贝数组
* @param group
* @return
*/
private byte[] join(SarImagePacketGroupDTO group) {
ByteBuffer buffer = ByteBuffer.allocate(group.getTotalSize());
buffer.order(ByteOrder.BIG_ENDIAN);
Map<Integer, SarImagePacketDTO> packets = group.getPackets();
// 使用ByteBuffer的批量操作减少系统调用
for (int i = 0; i <= group.getLastPacket().getPacketNo(); i++) {
SarImagePacketDTO packet = packets.get(i);
if (packet != null) {
buffer.put(packet.getData(), 0, packet.getSize());
}
}
return buffer.array();
}
@Override
protected String getText() {
return "雷达回图";
}
@Override
public SarImagePacketGroupDTO putPacket(String sourceIp, SarImagePacketDTO packet) {
// 1.包放入缓冲区
long sessionId = packet.getSessionId();
SarImagePacketGroupDTO group;
if (contains(sourceIp, sessionId)) {
group = get(sourceIp, sessionId);
group.put(packet);
} else {
group = SarImagePacketGroupDTO.init(sourceIp, packet);
// 将帧与航线绑定当航线2开机但航线1的图片未传完时用航线ID判断生成的图片是属于哪条航线
JmAirlineStatusDTO currAirline = jobStatusService.getCurrAirline(sourceIp);
if (currAirline == null) {
return null; // 没有在执行的任务或航线
}
group.setAirlineExecId(currAirline.getExecId());
put(sourceIp, sessionId, group);
}
// 2.判断是否全部包到达是则合并
SarImagePacketDTO last = group.getLastPacket();
// 若所有包已收到则合并
if (last != null && last.isLast() && group.getPackets().size() == group.getMaxNo() + 1) {
log.info("[UDP] sesionid="+ packet.getSessionId() + "全部到达合并!");
byte[] framePacketData = join(group);
afterJoin(framePacketData, group);
remove(sourceIp, sessionId);
}
return group;
}
@Override
protected String getProcessorName() {
return "sarImageProcessor";
}
@Override
protected void expireProcess(SarImagePacketGroupDTO group) {
// 定时判断超过 PACKET_TIMEOUT 时间未收到新包且符合生成条件则合成图
if (canJoin(group)) {
log.info("[UDP] sesionid="+ group.getSessionId() + "超时合并!");
byte[] framePacketData = join(group);
afterJoin(framePacketData, group);
} else { // 超时且无法合并丢弃
Map<Integer, SarImagePacketDTO> packets = group.getPackets();
if (packets.size() == 1 && packets.values().iterator().next().isLast()) {
// 雷达传图有bug每帧的尾包会多发一个丢弃且不打印日志
} else {
log.warn(getText() + "-移除超时帧: {}", group.getSessionId());
}
}
}
/**
* 启动处理线程从队列消费数据
*/
private void startProcessing() {
while (running) {
try {
SarMtiPointGroupDTO group = packetQueue.poll(100, TimeUnit.MILLISECONDS);
if (group != null) {
try {
mtiPointService.save(group.getJobConfId(), group.getJobExecId(), group.getPayloadId(), group.getAirlineExecId(), group, group.getData());
} catch (Exception ex) {
log.error("处理gmti包错误" + ex.getMessage(), ex);
}
}
} catch (Exception e) {
log.error("mti处理线程异常", e);
}
}
}
}

View File

@ -40,13 +40,13 @@ public class SarStatusListener extends SarAbstractListener {
/**
* 应答超时
*/
@Value("${ld.sar.udp.status.answer-timeout:1}")
@Value("${skyeye.sar.udp.status.answer-timeout:1}")
private int answerTimeout;
/**
* 连接状态超时
*/
@Value("${ld.sar.udp.status.connect-timeout:15}")
@Value("${skyeye.sar.udp.status.connect-timeout:15}")
private int connectTimeout;
@Autowired

View File

@ -0,0 +1,28 @@
package com.zhangy.skyeye.sar.mapper;
import org.springframework.stereotype.Repository;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zhangy.skyeye.common.extend.dto.PageDTO;
import com.zhangy.skyeye.common.extend.dto.QueryDTO;
import com.zhangy.skyeye.sar.entity.SarMtiPoint;
import java.util.List;
@Repository
public interface SarMtiPointMapper {
/**
* 列表查询
*/
List<SarMtiPoint> selectList(QueryDTO param);
/**
* 新增
*/
int insert(SarMtiPoint... array);
/**
* 按任务配置删除
*/
int deleteByJob(Long... jobId);
}

View File

@ -0,0 +1,28 @@
package com.zhangy.skyeye.sar.mapper;
import org.springframework.stereotype.Repository;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zhangy.skyeye.common.extend.dto.PageDTO;
import com.zhangy.skyeye.common.extend.dto.QueryDTO;
import com.zhangy.skyeye.sar.entity.SarMtiTrail;
import java.util.List;
@Repository
public interface SarMtiTrailMapper {
/**
* 列表查询
*/
List<SarMtiTrail> selectList(QueryDTO param);
/**
* 新增
*/
int insert(SarMtiTrail... array);
/**
* 按任务配置删除
*/
int deleteByJob(Long... jobId);
}

View File

@ -0,0 +1,36 @@
package com.zhangy.skyeye.sar.service;
import com.zhangy.skyeye.common.extend.dto.QueryDTO;
import com.zhangy.skyeye.sar.dto.SarMtiPointGroupDTO;
import com.zhangy.skyeye.sar.entity.SarMtiPoint;
import java.util.List;
public interface ISarMtiPointService {
/**
* 列表查询
*/
List<SarMtiPoint> selectList(QueryDTO param);
/**
* 新增
*/
void insert(SarMtiPoint... e);
/**
* 解析并保存
*
* @param jobId 执行任务ID
* @param airlineId 执行航线ID
* @param payloadId 载荷ID
* @param group 点迹分组
* @param pointGroupData 雷达回传的点数据
*/
void save(Long jobConfId, Long jobExecId, Long payloadId, Long airlineId, SarMtiPointGroupDTO group, byte[] pointGroupData);
/**
* 按任务配置删除
*/
void deleteByJob(Long... jobId);
}

View File

@ -0,0 +1,26 @@
package com.zhangy.skyeye.sar.service;
import java.util.List;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zhangy.skyeye.common.extend.dto.PageDTO;
import com.zhangy.skyeye.common.extend.dto.QueryDTO;
import com.zhangy.skyeye.sar.entity.SarMtiTrail;
public interface ISarMtiTrailService {
/**
* 列表查询
*/
List<SarMtiTrail> selectList(QueryDTO param);
/**
* 新增
*/
void insert(SarMtiTrail... e);
/**
* 按任务配置删除
*/
void deleteByJob(Long... jobId);
}

View File

@ -2,6 +2,8 @@ package com.zhangy.skyeye.sar.service.impl;
import com.zhangy.skyeye.common.extend.util.MathUtil;
import com.zhangy.skyeye.jm.dto.JmAirlineStatusDTO;
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
import com.zhangy.skyeye.redis.utils.RedisUtil;
import com.zhangy.skyeye.jm.dto.JmImageRotateDTO;
import com.zhangy.skyeye.jm.dto.JmUavStatusDTO;
import com.zhangy.skyeye.jm.entity.JmImage;
@ -15,13 +17,16 @@ import com.zhangy.skyeye.sar.dto.SarBackImageFrameDTO;
import com.zhangy.skyeye.sar.service.ISarImageService;
import com.zhangy.skyeye.sar.service.SarWsAsyncService;
import com.zhangy.skyeye.sar.util.SarImageToneAdjuster;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.opencv.core.Mat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.Date;
import java.util.List;
@Slf4j
@Service
@ -39,10 +44,116 @@ public class SarImageServiceImpl implements ISarImageService {
@Autowired
private SarWsAsyncService sarWsAsyncService;
@Autowired
private RedisUtil redisUtil;
// 图片最大宽度前端说和电脑有关4096保险点一般是4096 8192 16384
@Value("${skyeye.sar.image.max:4096}")
@Setter
private int IMG_MAX_WITH;
// 起始帧号
private final String CACHE_FIELD_START_FRAME_NO = "startFrameNo";
// 当前帧号
private final String CACHE_FIELD_CURR_FRAME_NO = "currFrameNo";
// 缓存超时
private final long CACHE_EXPIRE_SECOND = 24 * 3600;
/**
* 获取基准图像信息
*
* @param airlineId 航线执行ID
* @param singleWidth 单条图片宽度
* @param frameNo 当前帧号
* @return 返回非空的图像信息其字段 imageNo 一定有值
*/
private JmImage getBaseImage(Long airlineId, int singleWidth, int frameNo) {IMG_MAX_WITH=1;
List<JmImage> imageList = imageService.selectLowByAirline(airlineId);
String cacheKey = "jmImgJoin-" + airlineId;
JmImage base = null;
// 情况1航线第一张图
if (ObjectUtil.isEmpty(imageList)) {
base = new JmImage();
base.setImageNo(1);
redisUtil.hset(cacheKey, CACHE_FIELD_START_FRAME_NO, frameNo, CACHE_EXPIRE_SECOND);
return base;
}
// 情况2如果最后一张还能拼图则直接返回继续拼
JmImage last = imageList.get(imageList.size() - 1);
Integer startFrameNo = (Integer) redisUtil.hget(cacheKey, CACHE_FIELD_START_FRAME_NO);
int currWidth = startFrameNo == null ? 0 : singleWidth * (frameNo - startFrameNo + 1); // 图宽当前图+基准图
int surplusNum = (IMG_MAX_WITH - currWidth) / singleWidth; // 还可以拼图片数
Integer baseNo = (Integer) redisUtil.hget("jmImgJoin-" + airlineId, CACHE_FIELD_CURR_FRAME_NO);
if (startFrameNo == null || currWidth < IMG_MAX_WITH ||
baseNo == null || (frameNo - baseNo + 1 <= surplusNum)) { // 当前图+填充 不能超过允许拼接数
log.info("当前宽度:" + currWidth + " < " + IMG_MAX_WITH + " 可以继续拼接");
return last;
}
// 情况3已经拼接到最大数量或者当前图+填充数量超过允许拼接数量创建新图像文件
log.info("当前宽度:" + currWidth + " > " + IMG_MAX_WITH + " 重新拼接,当前帧号" + frameNo + "作为首帧");
base = new JmImage();
redisUtil.hset(cacheKey, CACHE_FIELD_START_FRAME_NO, frameNo, CACHE_EXPIRE_SECOND);
base.setImageNo(last.getImageNo() + 1);
return base;
}
private void modCoord(SarBackImageFrameDTO imageFrame, boolean lostImage, JmAirlineStatusDTO currAirline) {
JmImageRotateDTO rotateDTO = JmImageRotateDTO.rotate(currAirline.getTargetHeading(), currAirline.getDirection());
Double[] before = currAirline.getBeforeRight();
boolean isFirst = before == null;
if (isFirst) {
before = new Double[4];
currAirline.setBeforeRight(before);
}
// 使用前一张图的右侧坐标作为后一张图的左侧前提是没丢图
if (!isFirst && !lostImage) {
/*imageFrame.setLon1(before[0]);
imageFrame.setLat1(before[1]);
imageFrame.setLon4(before[2]);
imageFrame.setLat4(before[3]);*/
}
before[0] = imageFrame.getLon5();
before[1] = imageFrame.getLat5();
before[2] = imageFrame.getLon8();
before[3] = imageFrame.getLat8();
/*switch (rotateDTO.getType()) {
case 0:
case 1:
// 使用前一张图的右侧坐标作为后一张图的左侧前提是没丢图
if (!isFirst && !lostImage) {
imageFrame.setLon1(before[0]);
imageFrame.setLat1(before[1]);
imageFrame.setLon4(before[2]);
imageFrame.setLat4(before[3]);
} else if (before == null) {
before = new Double[4];
currAirline.setBeforeRight(before);
}
before[0] = imageFrame.getLon5();
before[1] = imageFrame.getLat5();
before[2] = imageFrame.getLon8();
before[3] = imageFrame.getLat8();
break;
case 2:
case 3:
if (!isFirst && !lostImage) {
imageFrame.setLon5(before[0]);
imageFrame.setLat5(before[1]);
imageFrame.setLon8(before[2]);
imageFrame.setLat8(before[3]);
}
before[0] = imageFrame.getLon1();
before[1] = imageFrame.getLat1();
before[2] = imageFrame.getLon4();
before[3] = imageFrame.getLat4();
break;
} */
}
/**
* 将原始图像数据保存为dat文件图像转png并将路径推送给前端
*
* @param group 图像包分组
* @param sourceIp
* @param airlineExecId
* @param frameData 图像帧数据
* @param imageFrame 图像帧
*/
@ -67,30 +178,50 @@ public class SarImageServiceImpl implements ISarImageService {
long start = System.currentTimeMillis();
// 1.保存dat异步
sarWsAsyncService.saveImageDat(jobExecId, airlineExecId + "-" + frameNo + ".dat", frameData);
// 2.保存图像png用航线ID命名
String imageName = airlineExecId + ".png";
String[] imagePath = sysFileTypeService.getFilePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId, imageName);
String currPath = imagePath[0];
JmImage base = imageService.selectLowByAirline(airlineExecId);
// 3.生成当前图像矩阵
// 2.生成当前图像矩阵
Mat currImage = loadCurrImageMat(frameData, imageFrame);
if (currImage == null) {
return null;
}
// 3.保存图像png用航线ID+序号命名
JmImage base = getBaseImage(airlineExecId, currImage.width(), imageFrame.getFrameNo());
String imageName = airlineExecId + "-" + base.getImageNo() +".png";
String[] imagePath = sysFileTypeService.getFilePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId, imageName);
String currPath = imagePath[0];
System.out.println("帧:" + frameNo);
// 4.保存基准图同步用于下次拼接
String basePath = sysFileTypeService.getAbsolutePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId, airlineExecId + "-base.png");
Mat baseMat = generateBaseMat(base, currImage, frameNo, imageFrame.getMax(), basePath);
Integer baseNo = (Integer) redisUtil.hget("jmImgJoin-" + airlineExecId, CACHE_FIELD_CURR_FRAME_NO);
boolean lostImage = baseNo != null && (frameNo - baseNo > 1); // 判断是否丢图
String basePath = sysFileTypeService.getAbsolutePath(FileTypeEnum.SAR_IMAGE_LOW, jobExecId,
airlineExecId + "-" + base.getImageNo() +"-base.png");
Mat baseMat = generateBaseMat(base, currImage, frameNo, imageFrame.getMax(), basePath, baseNo);
if (baseMat == null) { // 拼接失败 基准图生成失败则跳过按丢图处理
return null;
}
if (lostImage) {
log.warn("丢图"+(frameNo - baseNo)+"张!当前帧" + frameNo + ",前帧" + baseNo);
}
//modCoord(imageFrame, lostImage, currAirline);
redisUtil.hset("jmImgJoin-" + airlineExecId, CACHE_FIELD_CURR_FRAME_NO, frameNo, CACHE_EXPIRE_SECOND);// 更新帧号
// ### 亮度调整用于可靠udp版本图像固定使用系数0.5
SarImageToneAdjuster.autoToneWithClipping(baseMat, 0.5f);
// 拆分多张图片去掉自适应调整
if (IMG_MAX_WITH > 20000) {
SarImageToneAdjuster.autoToneWithClipping(baseMat, 0.5f);
}
// 5.保存后处理图异步用于前端显示
generateAfterMat(currAirline, baseMat, currPath, uav.getSarImageLight());
// 6.更新基准图坐标和帧号
JmImage imageInfo = saveImage(uav, airlineExecId, base, imagePath, imageFrame, frameNo);
JmImage imageInfo = saveImage(uav, airlineExecId, base, imagePath, imageFrame, frameNo, lostImage);
long end = System.currentTimeMillis();
log.info("生成"+imageFrame.getImageBitDeep()+"位雷达回传图像:帧序号" + frameNo + "," + imageInfo.getRelativePath() + ",耗时" + (end - start)/1000 + "");
log.info("生成" + imageFrame.getImageBitDeep()+"位雷达回传图像:帧序号" + frameNo + "," +
imageInfo.getRelativePath() + ",耗时" + (end - start)/1000 + "");
return imageInfo;
}
@ -137,11 +268,10 @@ public class SarImageServiceImpl implements ISarImageService {
* @param imagePath 基准图路径
* @return
*/
private Mat generateBaseMat(JmImage base, Mat currImage, int currNo, float currMax, String imagePath) {
private Mat generateBaseMat(JmImage base, Mat currImage, int currNo, float currMax, String imagePath, Integer baseNo) {
Mat baseImage = OpenCVUtil.read(imagePath); // 从硬盘加载基准图
Integer baseNo = base == null ? null : base.getFrameNo();
// 归一化调整
if (base != null) {
if (base.getId() != null) {
float baseMax = base.getMax();
//System.out.println("基准图:" + baseMax + ",当前图:" + currMax);
if (baseMax < currMax) {
@ -162,7 +292,7 @@ public class SarImageServiceImpl implements ISarImageService {
*
* @param currAirline 当前航线信息
* @param baseMat 原基准图处理后释放资源
* @param currPath 后处理图路径每条航线对应一张图每次生成会覆盖
* @param imagePath 后处理图路径每条航线对应一张图每次生成会覆盖
* @param imageLight 图像亮度倍数0是不调整
*/
private void generateAfterMat(JmAirlineStatusDTO currAirline, Mat baseMat, String imagePath, int imageLight) {
@ -179,11 +309,9 @@ public class SarImageServiceImpl implements ISarImageService {
* @param airlineId 航线执行ID
*/
private JmImage saveImage(JmUavStatusDTO uav, Long airlineId, JmImage image, String[] imagePath,
SarBackImageFrameDTO imageFrame, int frameNo) {
boolean isFirst = image == null;
if (isFirst) {
image = new JmImage();
}
SarBackImageFrameDTO imageFrame, int frameNo, boolean lostImage) {
boolean isFirst = image.getId() == null;
// 拼接后的图片的四角坐标
double minLon = isFirst ? MathUtil.min(imageFrame.getLon1(), imageFrame.getLon4(), imageFrame.getLon5(), imageFrame.getLon8())
: MathUtil.min(imageFrame.getLon1(), imageFrame.getLon4(), imageFrame.getLon5(), imageFrame.getLon8(), image.getLeft1Lon(), image.getRight1Lon());
@ -194,8 +322,8 @@ public class SarImageServiceImpl implements ISarImageService {
double maxLat = isFirst ? MathUtil.max(imageFrame.getLat1(), imageFrame.getLat4(), imageFrame.getLat5(), imageFrame.getLat8())
: MathUtil.max(imageFrame.getLat1(), imageFrame.getLat4(), imageFrame.getLat5(), imageFrame.getLat8(), image.getLeft1Lat(), image.getLeft2Lat());
// 更新坐标和序号
image.setFrameNo(frameNo);
image.updateCoord(minLon, maxLon, minLat, maxLat);
Float currMax = !isFirst && image.getMax() > imageFrame.getMax() ? image.getMax() : imageFrame.getMax();
image.setMax(currMax);
image.setImageTime(imageFrame.getDate());

View File

@ -0,0 +1,89 @@
package com.zhangy.skyeye.sar.service.impl;
import com.zhangy.skyeye.common.extend.dto.QueryDTO;
import com.zhangy.skyeye.common.extend.util.FileUtil;
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
import com.zhangy.skyeye.device.service.IPayloadService;
import com.zhangy.skyeye.publics.consts.FileTypeEnum;
import com.zhangy.skyeye.publics.consts.WebSocketKey;
import com.zhangy.skyeye.publics.service.SysFileTypeService;
import com.zhangy.skyeye.sar.dto.SarMtiPointGroupDTO;
import com.zhangy.skyeye.sar.dto.SarMtiTrailGroupDTO;
import com.zhangy.skyeye.sar.dto.SarMtiWsDTO;
import com.zhangy.skyeye.sar.entity.SarMtiPoint;
import com.zhangy.skyeye.sar.entity.SarMtiTrail;
import com.zhangy.skyeye.sar.lib.SarMtiLibProcessor;
import com.zhangy.skyeye.sar.mapper.SarMtiPointMapper;
import com.zhangy.skyeye.sar.service.ISarMtiPointService;
import com.zhangy.skyeye.sar.service.ISarMtiTrailService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@Service
public class SarMtiPointServiceImpl implements ISarMtiPointService {
@Autowired
private SarMtiPointMapper sarMtiPointMapper;
@Autowired
private IPayloadService payloadService;
@Autowired
private ISarMtiTrailService sarMtiTrailService;
@Autowired
private SimpMessagingTemplate simpMessageingTemplate;
@Autowired
private SysFileTypeService sysFileTypeService;
@Override
public List<SarMtiPoint> selectList(QueryDTO param) {
return sarMtiPointMapper.selectList(param);
}
@Transactional
@Override
public void insert(SarMtiPoint... e) {
if (ObjectUtil.isNotEmpty(e)) {
sarMtiPointMapper.insert(e);
}
}
@Override
public void save(Long jobConfId, Long jobExecId, Long payloadId, Long airlineId, SarMtiPointGroupDTO group, byte[] pointGroupData) {
// 保存点迹
SarMtiPoint[] points = group.getPoints();
insert(points);
// 生成并保存轨迹
String dirPath = jobExecId + "/" + airlineId;
String outputPath = sysFileTypeService.getDirectory(FileTypeEnum.SAR_MTI_LIB, dirPath).getAbsolutePath();
byte[] result = null;
try {
result = SarMtiLibProcessor.mtiDotProc(pointGroupData, outputPath);
} catch (Throwable ex) {
log.error("生成GMTI轨迹错误" + ex.getMessage());
ex.printStackTrace();
}
SarMtiTrailGroupDTO trailGroup = new SarMtiTrailGroupDTO(payloadId, airlineId, result);
log.info(trailGroup.toString());
SarMtiTrail[] trails = trailGroup.getTrails();
sarMtiTrailService.insert(trails);
// 推送
SarMtiWsDTO ws = new SarMtiWsDTO(jobConfId, payloadId, points, trails);
simpMessageingTemplate.convertAndSend(WebSocketKey.SAR_BACK_GMTI, ws);
}
@Override
public void deleteByJob(Long... jobId) {
if (ObjectUtil.isNotEmpty(jobId)) {
sarMtiPointMapper.deleteByJob(jobId);
}
}
}

View File

@ -0,0 +1,39 @@
package com.zhangy.skyeye.sar.service.impl;
import java.util.List;
import com.zhangy.skyeye.common.extend.util.ObjectUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.zhangy.skyeye.common.extend.dto.QueryDTO;
import com.zhangy.skyeye.sar.entity.SarMtiTrail;
import com.zhangy.skyeye.sar.mapper.SarMtiTrailMapper;
import com.zhangy.skyeye.sar.service.ISarMtiTrailService;
@Service
public class SarMtiTrailServiceImpl implements ISarMtiTrailService {
@Autowired
private SarMtiTrailMapper sarMtiTrailMapper;
@Override
public List<SarMtiTrail> selectList(QueryDTO param) {
return sarMtiTrailMapper.selectList(param);
}
@Transactional
@Override
public void insert(SarMtiTrail... e) {
if (ObjectUtil.isNotEmpty(e)) {
sarMtiTrailMapper.insert(e);
}
}
@Override
public void deleteByJob(Long... jobId) {
if (ObjectUtil.isNotEmpty(jobId)) {
sarMtiTrailMapper.deleteByJob(jobId);
}
}
}

View File

@ -0,0 +1,130 @@
package com.zhangy.skyeye.sar.util;
import com.zhangy.skyeye.publics.utils.CoordUtil;
import com.zhangy.skyeye.sar.dto.SarFlightPlanDTO;
public class SpotlightPlanner {
/**
* 规划SAR聚束模式航线
* @param targetLat 目标点纬度
* @param targetLon 目标点经度
* @param takeoffLat 起飞点纬度
* @param takeoffLon 起飞点经度
* @param height 飞行高度相对地面
* @param lookAngle 下视角从竖直方向算起
* @param radarSide 雷达侧视方向1 -1
* @param flightHeadingAngle 航线方向0=90=如果为null则自动计算
*/
public static SarFlightPlanDTO planRoute(double targetLat, double targetLon,
double takeoffLat, double takeoffLon,
double height, double lookAngle,
int radarSide, Double flightHeadingAngle) {
SarFlightPlanDTO plan = new SarFlightPlanDTO();
plan.setTargetLat(targetLat);
plan.setTargetLon(targetLon);
plan.setTakeoffLat(takeoffLat);
plan.setTakeoffLon(takeoffLon);
plan.setHeight(height);
plan.setLookAngle(lookAngle);
plan.setRadarSide(radarSide);
// 1. 计算斜距R
double lookAngleRad = CoordUtil.toRadians(lookAngle);
double R = height / Math.cos(lookAngleRad);
// 2. 计算合成孔径长度La
double La = (7.2 * Math.PI * R) / 180.0;
// 3. 计算侧视偏移距离
double D_offset = height * Math.tan(lookAngleRad);
// 4. 确定航线方向
double finalFlightHeadingAngle;
if (flightHeadingAngle != null) {
finalFlightHeadingAngle = flightHeadingAngle;
} else {
// 自动计算航线方向与起飞点到目标的连线方向相同或相反
double bearingToTarget = CoordUtil.calculateBearing(takeoffLat, takeoffLon, targetLat, targetLon);
// 选择使得航线起点靠近起飞点的方向
// 这里简单选择与目标方位相同的方向
finalFlightHeadingAngle = bearingToTarget;
}
plan.setFlightHeadingAngle(finalFlightHeadingAngle);
// 5. 计算航迹投影点P0飞机在此点时斜距最近
// 首先计算垂直于航线方向的方向
double perpendicularDirection = finalFlightHeadingAngle - (radarSide == -1 ? 90 : -90);
// 将目标点沿垂直方向移动D_offset距离得到P0
double[] P0 = CoordUtil.calculateDestination(targetLat, targetLon, perpendicularDirection, D_offset);
plan.setP0Lat(P0[0]);
plan.setP0Lon(P0[1]);
// 6. 计算航线起点S和终点E
// 从P0沿航线反方向移动(1*La + 100)得到S
double reverseDirection = (finalFlightHeadingAngle + 180) % 360;
double offsetDistance = La + 100.0;
double[] S = CoordUtil.calculateDestination(P0[0], P0[1], reverseDirection, offsetDistance);
plan.setStartLat(S[0]);
plan.setStartLon(S[1]);
// 从P0沿航线方向移动(1*La + 100)得到E
double[] E = CoordUtil.calculateDestination(P0[0], P0[1], finalFlightHeadingAngle, offsetDistance);
plan.setEndLat(E[0]);
plan.setEndLon(E[1]);
// 7. 计算开机点A和关机点B
// A点从S沿航线方向移动100米
double[] A = CoordUtil.calculateDestination(S[0], S[1], finalFlightHeadingAngle, 100.0);
plan.setPowerOnLat(A[0]);
plan.setPowerOnLon(A[1]);
// B点从E沿航线反方向移动100米
double[] B = CoordUtil.calculateDestination(E[0], E[1], reverseDirection, 100.0);
plan.setPowerOffLat(B[0]);
plan.setPowerOffLon(B[1]);
// 8. 计算距离信息
plan.setSlantRange(R);
plan.setApertureLength(La);
plan.setSideOffset(D_offset);
plan.setRouteLength(CoordUtil.calculateDistance(S[0], S[1], E[0], E[1]));
// 计算总飞行距离起飞点->S->E
double distanceTakeoffToS = CoordUtil.calculateDistance(takeoffLat, takeoffLon, S[0], S[1]);
double distanceSE = plan.getRouteLength();
// double distanceEToTakeoff = CoordUtil.calculateDistance(E[0], E[1], takeoffLat, takeoffLon);
plan.setTotalDistance(distanceTakeoffToS + distanceSE /*+ distanceEToTakeoff*/);
return plan;
}
/**
* 优化航线方向以最小化总飞行距离
*/
public static SarFlightPlanDTO optimizeRoute(double targetLon, double targetLat,
double takeoffLon, double takeoffLat,
double height, double lookAngle,
int radarSide) {
double bestDirection = 0;
double minDistance = Double.MAX_VALUE;
SarFlightPlanDTO bestPlan = null;
// 尝试36个方向每10度一个
for (int i = 0; i < 36; i++) {
double direction = i * 10.0;
SarFlightPlanDTO plan = planRoute(targetLat, targetLon, takeoffLat, takeoffLon,
height, lookAngle, radarSide, direction);
if (plan.getTotalDistance() < minDistance) {
minDistance = plan.getTotalDistance();
bestDirection = direction;
bestPlan = plan;
}
}
return bestPlan;
}
}

View File

@ -21,7 +21,7 @@ import java.net.http.HttpResponse;
public class WeatherServiceImpl implements IWeatherService {
@Setter
@Value("${ld.weather.cityCode}")
@Value("${skyeye.weather.cityCode}")
private String cityCode;
/** 天气服务地址 */

View File

@ -37,7 +37,7 @@ spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/zhangy-skyeye?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
username: root
password: 'root'
password: 'P@ssw0rd'
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
@ -50,7 +50,7 @@ spring:
database: 0
host: 127.0.0.1
port: 6379
#password: 'P@ssw0rd'
password: 'P@ssw0rd'
mybatis-plus:
mapper-locations: classpath*:mapping/**/*Mapping.xml
@ -68,20 +68,22 @@ jasypt:
encryptor:
# 指定加密算法,例如 PBEWithMD5AndDES, PBEWithHMACSHA512AndAES_256 等
algorithm: PBEWithMD5AndDES
ld:
skyeye:
file-root: "d:/1/"
debug: false
uav:
upload: kmz
sar:
image:
type: 1
max: 409600000
udp:
status:
answer-timeout: 1
answer-timeout: 2
connect-timeout: 15
py:
ktkxUrl: http://127.0.0.1:18090/ktkx/UavPlanning/SAR
# detectUrl: http://127.0.0.1:18091/ktkx/detect/cpu/SARCoord
detectUrl: http://127.0.0.1:18091/ktkx/detect/cpu/SARCoord
weather:
cityCode: 101120201
@ -89,5 +91,5 @@ logging:
file:
name: "logs/"
level:
com.zhangy: info
com.zhangy: debug
org.springframework.messaging.simp.broker.SimpleBrokerMessageHandler: OFF

View File

@ -3,4 +3,4 @@ spring:
name: zy-skyeye-service
profiles:
active: dev
debug: true
debug: false

File diff suppressed because one or more lines are too long

View File

@ -140,10 +140,14 @@
</delete>
<delete id="deleteByJob">
delete from jm_airline_exec where job_id in
select *
from jm_airline_exec ae
where exists(
select 1 from jm_job_exec je where ae.job_id = je.id and je.conf_id in
<foreach item="item" collection="array" open="(" separator="," close=")">
#{item}
</foreach>
)
</delete>
<delete id="deleteByUav">

View File

@ -11,7 +11,7 @@
m.payload_id,
m.airline_id,
m.file_id,
m.frame_no,
m.image_no,
m.max,
m.left1_lon,
m.left1_lat,
@ -83,6 +83,7 @@
<if test="type != null">
and f.type = #{type}
</if>
order by m.image_no
</select>
<select id="selectById" resultType="com.zhangy.skyeye.jm.entity.JmImage">
@ -116,7 +117,7 @@
payload_id,
airline_id,
file_id,
frame_no,
image_no,
max,
left1_lon,
left1_lat,
@ -138,7 +139,7 @@
#{item.payloadId},
#{item.airlineId},
#{item.fileId},
#{item.frameNo},
#{item.imageNo},
#{item.max},
#{item.left1Lon},
#{item.left1Lat},
@ -163,7 +164,6 @@
<if test="payloadId != null">payload_id = #{payloadId},</if>
<if test="airlineId != null">airline_id = #{airlineId},</if>
<if test="fileId != null">file_id = #{fileId},</if>
<if test="frameNo != null">frame_no = #{frameNo},</if>
<if test="max != null">max = #{max},</if>
<if test="left1Lon != null">left1_lon = #{left1Lon},</if>
<if test="left1Lat != null">left1_lat = #{left1Lat},</if>
@ -188,7 +188,6 @@
payload_id = #{payloadId},
airline_id = #{airlineId},
file_id = #{fileId},
frame_no = #{frameNo},
max = #{max},
left1_lon = #{left1Lon},
left1_lat = #{left1Lat},

View File

@ -9,6 +9,8 @@
j.name,
j.status,
j.mode,
j.image_mode,
j.target_type,
j.begin_time,
j.end_time,
j.create_time,
@ -46,6 +48,14 @@
order by j.create_time desc
</select>
<select id="selectInfo" resultType="com.zhangy.skyeye.jm.dto.JmJobDTO">
select
j.id,
j.info1
from jm_job j
where j.id = #{jobId}
</select>
<select id="selectById" resultType="com.zhangy.skyeye.jm.dto.JmJobDTO">
<include refid="selectSql"/>
where j.id in
@ -59,24 +69,30 @@
id,
name,
mode,
image_mode,
target_type,
status,
begin_time,
end_time,
create_time,
type,
cron_expression
cron_expression,
info1
) values
<foreach item="item" index="index" collection="array" separator=",">
(
#{item.id},
#{item.name},
#{item.mode},
#{item.imageMode},
#{item.targetType},
#{item.status},
#{item.beginTime},
#{item.endTime},
#{item.createTime},
#{item.type},
#{item.cronExpression}
#{item.cronExpression},
#{item.info1}
)
</foreach>
</insert>
@ -87,10 +103,13 @@
<if test="name != null">name = #{name},</if>
<if test="status != null">status = #{status},</if>
<if test="mode != null">mode = #{mode},</if>
<if test="imageMode != null">image_mode = #{imageMode},</if>
<if test="targetType != null">target_type = #{targetType},</if>
<if test="beginTime != null">begin_time = #{beginTime},</if>
<if test="endTime != null">end_time = #{endTime},</if>
<if test="type != null">type = #{type},</if>
<if test="cronExpression != null and cronExpression != ''">cron_expression = #{cronExpression},</if>
<if test="info1 != null and info1 != ''">info1 = #{info1},</if>
</trim>
where id = #{id}
</update>
@ -100,11 +119,14 @@
<trim prefix="SET" suffixOverrides=",">
name = #{name},
status = #{status},
target_type = #{targetType},
image_mode = #{imageMode},
mode = #{mode},
begin_time = #{beginTime},
end_time = #{endTime},
type = #{type},
cron_expression = #{cronExpression},
info1 = #{info1},
</trim>
where id = #{id}
</update>

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhangy.skyeye.sar.mapper.SarMtiPointMapper">
<sql id="selectSql">
select
t.payload_id,
t.airline_id,
t.no,
t.rg,
t.azimuth,
t.lon,
t.lat,
t.alt,
t.amp,
t.speed,
t.create_time
from sar_mti_point t
</sql>
<select id="selectList" resultType="com.zhangy.skyeye.sar.entity.SarMtiPoint">
<include refid="selectSql"/>
where 1 = 1
<if test="payloadId != null">
and t.payload_id = #{payloadId}
</if>
<if test="beginTime != null">
and t.create_time &gt;= #{beginTime}
</if>
<if test="endTime != null">
and t.create_time &lt;= #{endTime}
</if>
order by t.id
</select>
<insert id="insert">
insert into sar_mti_point(
payload_id,
airline_id,
no,
rg,
azimuth,
lon,
lat,
alt,
amp,
speed,
create_time
) values
<foreach item="item" index="index" collection="array" separator=",">
(
#{item.payloadId},
#{item.airlineId},
#{item.no},
#{item.rg},
#{item.azimuth},
#{item.lon},
#{item.lat},
#{item.alt},
#{item.amp},
#{item.speed},
#{item.createTime}
)
</foreach>
</insert>
<delete id="deleteByJob">
delete
from sar_mti_point mp
where exists(select 1
from jm_airline_exec jae
join jm_job_exec je on jae.job_id = je.id
where mp.airline_id = jae.id
and je.conf_id in
<foreach item="item" index="index" collection="array" separator="," open="(" close=")">
#{item}
</foreach>
)
</delete>
</mapper>

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhangy.skyeye.sar.mapper.SarMtiTrailMapper">
<sql id="selectSql">
select
t.payload_id,
t.airline_id,
t.no,
t.lon,
t.lat,
t.alt,
t.amp,
t.speed,
t.heading_angle,
t.create_time
from sar_mti_trail t
</sql>
<select id="selectList" resultType="com.zhangy.skyeye.sar.entity.SarMtiTrail">
<include refid="selectSql"/>
where 1 = 1
<if test="payloadId != null">
and t.payload_id = #{payloadId}
</if>
<if test="beginTime != null">
and t.create_time &gt;= #{beginTime}
</if>
<if test="endTime != null">
and t.create_time &lt;= #{endTime}
</if>
order by t.id
</select>
<insert id="insert">
insert into sar_mti_trail(
payload_id,
airline_id,
no,
lon,
lat,
alt,
amp,
speed,
heading_angle,
create_time
) values
<foreach item="item" index="index" collection="array" separator=",">
(
#{item.payloadId},
#{item.airlineId},
#{item.no},
#{item.lon},
#{item.lat},
#{item.alt},
#{item.amp},
#{item.speed},
#{item.headingAngle},
#{item.createTime}
)
</foreach>
</insert>
<delete id="deleteByJob">
delete
from sar_mti_trail mp
where exists(select 1
from jm_airline_exec jae
join jm_job_exec je on jae.job_id = je.id
where mp.airline_id = jae.id
and je.conf_id in
<foreach item="item" index="index" collection="array" separator="," open="(" close=")">
#{item}
</foreach>
)
</delete>
</mapper>