From 04a270ff2b5919127524913ffc45c59c6c6d9201 Mon Sep 17 00:00:00 2001
From: liusuyi <1951119284@qq.com>
Date: Wed, 22 Apr 2026 14:22:30 +0800
Subject: [PATCH] 增加部门区域管理业务和无人机实时位置所属区域推送
---
ard-common/src/main/java/com/ard/common/constant/RegionConstants.java | 15
ard-work/src/main/java/com/ard/work/service/impl/UavRegionDetectService.java | 43 +
ard-system/src/main/resources/mapper/data/ArdDeptRegionMapper.xml | 210 +++++++++
ard-admin/src/main/java/com/ard/web/controller/data/ArdDeptRegionController.java | 145 ++++++
ard-system/src/main/java/com/ard/data/region/domain/ConflictRegion.java | 17
ard-system/src/main/java/com/ard/data/region/domain/Coordinate.java | 40 +
ard-work/src/main/java/com/ard/work/component/websocket/handler/UavAllManager.java | 10
ard-work/src/main/java/com/ard/work/service/impl/TyServiceImpl.java | 16
ard-system/src/main/java/com/ard/data/region/service/IArdDeptRegionService.java | 64 ++
ard-system/src/main/java/com/ard/data/region/service/impl/ArdDeptRegionServiceImpl.java | 298 +++++++++++++
ard-system/src/main/java/com/ard/data/region/domain/ArdDeptRegion.java | 62 ++
ard-system/src/main/java/com/ard/data/region/domain/ConflictCheckResult.java | 18
ard-system/src/main/java/com/ard/data/region/domain/PointQueryDTO.java | 18
ard-system/src/main/java/com/ard/data/region/utils/RegionCoordinateUtils.java | 154 +++++++
ard-work/src/main/java/com/ard/work/service/impl/UavPositionChangeService.java | 89 ++++
ard-work/src/main/java/com/ard/work/domain/cache/UavRegionCache.java | 30 +
ard-system/src/main/java/com/ard/data/region/mapper/ArdDeptRegionMapper.java | 89 ++++
17 files changed, 1,311 insertions(+), 7 deletions(-)
diff --git a/ard-admin/src/main/java/com/ard/web/controller/data/ArdDeptRegionController.java b/ard-admin/src/main/java/com/ard/web/controller/data/ArdDeptRegionController.java
new file mode 100644
index 0000000..424a872
--- /dev/null
+++ b/ard-admin/src/main/java/com/ard/web/controller/data/ArdDeptRegionController.java
@@ -0,0 +1,145 @@
+package com.ard.web.controller.data;
+
+import com.ard.common.annotation.Log;
+import com.ard.common.core.controller.BaseController;
+import com.ard.common.core.domain.AjaxResult;
+import com.ard.common.core.page.TableDataInfo;
+import com.ard.common.enums.BusinessType;
+import com.ard.common.utils.poi.ExcelUtil;
+import com.ard.data.region.domain.ArdDeptRegion;
+import com.ard.data.region.domain.ConflictCheckResult;
+import com.ard.data.region.domain.PointQueryDTO;
+import com.ard.data.region.service.IArdDeptRegionService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 部门区域Controller
+ *
+ * @author ruoyi
+ */
+@Api(tags = "部门区域接口")
+@RestController
+@RequestMapping("/data/region")
+public class ArdDeptRegionController extends BaseController {
+ @Autowired
+ private IArdDeptRegionService ardDeptRegionService;
+
+ /**
+ * 查询部门区域列表
+ */
+ @ApiOperation("查询部门区域列表")
+ @PreAuthorize("@ss.hasPermi('data:region:list')")
+ @GetMapping("/list")
+ public TableDataInfo list(ArdDeptRegion ardDeptRegion) {
+ startPage();
+ List<ArdDeptRegion> list = ardDeptRegionService.selectArdDeptRegionList(ardDeptRegion);
+ return getDataTable(list);
+ }
+
+ /**
+ * 导出部门区域列表
+ */
+ @ApiOperation("导出部门区域列表")
+ @PreAuthorize("@ss.hasPermi('data:region:export')")
+ @Log(title = "部门区域", businessType = BusinessType.EXPORT)
+ @GetMapping("/export")
+ public AjaxResult export(ArdDeptRegion ardDeptRegion) {
+ List<ArdDeptRegion> list = ardDeptRegionService.selectArdDeptRegionList(ardDeptRegion);
+ ExcelUtil<ArdDeptRegion> util = new ExcelUtil<ArdDeptRegion>(ArdDeptRegion.class);
+ return util.exportExcel(list, "部门区域数据");
+ }
+
+ /**
+ * 获取部门区域详细信息
+ */
+ @ApiOperation("获取部门区域详细信息")
+ @PreAuthorize("@ss.hasPermi('data:region:query')")
+ @GetMapping(value = "/{id}")
+ public AjaxResult getInfo(@PathVariable("id") Long id) {
+ return success(ardDeptRegionService.selectArdDeptRegionById(id));
+ }
+
+ /**
+ * 检查区域冲突(画区域时调用)
+ */
+ @ApiOperation("检查区域冲突")
+ @PostMapping("/check")
+ public AjaxResult checkConflict(@Valid @RequestBody ArdDeptRegion ardDeptRegion) {
+ ConflictCheckResult result = ardDeptRegionService.checkConflict(ardDeptRegion);
+ if (result.getHasConflict()) {
+ return AjaxResult.error(result.getMessage(), result);
+ }
+ return AjaxResult.success(result.getMessage(), result);
+ }
+
+ /**
+ * 新增部门区域
+ */
+ @ApiOperation("新增部门区域")
+ @PreAuthorize("@ss.hasPermi('data:region:add')")
+ @Log(title = "部门区域", businessType = BusinessType.INSERT)
+ @PostMapping("/add")
+ public AjaxResult add(@Valid @RequestBody ArdDeptRegion ardDeptRegion) {
+ if (!ardDeptRegionService.checkRegionNameUnique(ardDeptRegion)) {
+ return error("新增区域'" + ardDeptRegion.getRegionName() + "'失败,区域名称已存在");
+ }
+ return toAjax(ardDeptRegionService.insertArdDeptRegion(ardDeptRegion));
+ }
+
+ /**
+ * 修改部门区域
+ */
+ @ApiOperation("修改部门区域")
+ @PreAuthorize("@ss.hasPermi('data:region:edit')")
+ @Log(title = "部门区域", businessType = BusinessType.UPDATE)
+ @PutMapping("/edit")
+ public AjaxResult edit(@RequestBody ArdDeptRegion ardDeptRegion) {
+ if (!ardDeptRegionService.checkRegionNameUnique(ardDeptRegion)) {
+ return error("修改区域'" + ardDeptRegion.getRegionName() + "'失败,区域名称已存在");
+ }
+ return toAjax(ardDeptRegionService.updateArdDeptRegion(ardDeptRegion));
+ }
+
+ /**
+ * 修改状态(启用/禁用)
+ */
+ @ApiOperation("修改状态")
+ @PreAuthorize("@ss.hasPermi('data:region:edit')")
+ @Log(title = "部门区域", businessType = BusinessType.UPDATE)
+ @PutMapping("/changeStatus")
+ public AjaxResult changeStatus(@RequestBody ArdDeptRegion ardDeptRegion)
+ {
+ return toAjax(ardDeptRegionService.updateStatus(ardDeptRegion.getId(), ardDeptRegion.getStatus()));
+ }
+
+ /**
+ * 删除部门区域
+ */
+ @ApiOperation("删除部门区域")
+ @PreAuthorize("@ss.hasPermi('data:region:remove')")
+ @Log(title = "部门区域", businessType = BusinessType.DELETE)
+ @DeleteMapping("/remove/{ids}")
+ public AjaxResult remove(@PathVariable String[] ids) {
+ return toAjax(ardDeptRegionService.deleteArdDeptRegionByIds(ids));
+ }
+
+ /**
+ * 根据坐标查询所属区域
+ */
+ @ApiOperation("根据坐标查询所属区域")
+ @PostMapping("/getByPoint")
+ public AjaxResult getRegionByPoint(@RequestBody PointQueryDTO query) {
+ ArdDeptRegion region = ardDeptRegionService.selectRegionByPoint(query.getLat(), query.getLng());
+ if (region == null) {
+ return error("该坐标不在任何区域内");
+ }
+ return success(region);
+ }
+}
\ No newline at end of file
diff --git a/ard-common/src/main/java/com/ard/common/constant/RegionConstants.java b/ard-common/src/main/java/com/ard/common/constant/RegionConstants.java
new file mode 100644
index 0000000..da50de8
--- /dev/null
+++ b/ard-common/src/main/java/com/ard/common/constant/RegionConstants.java
@@ -0,0 +1,15 @@
+package com.ard.common.constant;
+
+/**
+ * 区域常量
+ *
+ * @author ruoyi
+ */
+public class RegionConstants
+{
+ /** 区域名称唯一 */
+ public static final boolean UNIQUE = true;
+
+ /** 区域名称不唯一 */
+ public static final boolean NOT_UNIQUE = false;
+}
\ No newline at end of file
diff --git a/ard-system/src/main/java/com/ard/data/region/domain/ArdDeptRegion.java b/ard-system/src/main/java/com/ard/data/region/domain/ArdDeptRegion.java
new file mode 100644
index 0000000..11e5a3d
--- /dev/null
+++ b/ard-system/src/main/java/com/ard/data/region/domain/ArdDeptRegion.java
@@ -0,0 +1,62 @@
+package com.ard.data.region.domain;
+
+import com.ard.common.annotation.Excel;
+import com.ard.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.List;
+
+/**
+ * 部门区域对象 ard_dept_region
+ *
+ * @author ruoyi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ArdDeptRegion extends BaseEntity
+{
+ private static final long serialVersionUID = 1L;
+
+ /** 区域ID */
+ private Long id;
+
+ /** 部门ID */
+ @Excel(name = "部门ID")
+ private Long deptId;
+
+ /** 区域名称 */
+ @Excel(name = "区域名称")
+ private String regionName;
+
+ /** 区域颜色(十六进制颜色码) */
+ @Excel(name = "区域颜色")
+ private String regionColor;
+
+ /** 区域类型(POINT/LINE/POLYGON,非数据库字段,自动判断) */
+ private String regionType;
+
+ /** 区域边界WKT(非数据库字段,用于传输) */
+ private String boundaryWkt;
+
+ /** 中心纬度 */
+ @Excel(name = "中心纬度")
+ private Double centerLat;
+
+ /** 中心经度 */
+ @Excel(name = "中心经度")
+ private Double centerLng;
+
+ /** 面积(平方米) */
+ @Excel(name = "面积")
+ private Double areaSize;
+
+ /** 坐标集合(非数据库字段,用于接收前端坐标) */
+ private List<Coordinate> coordinates;
+
+ /** 状态(0正常 1停用) */
+ @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
+ private String status;
+}
\ No newline at end of file
diff --git a/ard-system/src/main/java/com/ard/data/region/domain/ConflictCheckResult.java b/ard-system/src/main/java/com/ard/data/region/domain/ConflictCheckResult.java
new file mode 100644
index 0000000..26303ad
--- /dev/null
+++ b/ard-system/src/main/java/com/ard/data/region/domain/ConflictCheckResult.java
@@ -0,0 +1,18 @@
+package com.ard.data.region.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 冲突检查结果
+ *
+ * @author ruoyi
+ */
+@Data
+public class ConflictCheckResult
+{
+ private Boolean hasConflict;
+ private String message;
+ private List<ConflictRegion> conflictRegions;
+}
\ No newline at end of file
diff --git a/ard-system/src/main/java/com/ard/data/region/domain/ConflictRegion.java b/ard-system/src/main/java/com/ard/data/region/domain/ConflictRegion.java
new file mode 100644
index 0000000..34e7837
--- /dev/null
+++ b/ard-system/src/main/java/com/ard/data/region/domain/ConflictRegion.java
@@ -0,0 +1,17 @@
+package com.ard.data.region.domain;
+
+import lombok.Data;
+
+/**
+ * 冲突区域信息
+ *
+ * @author ruoyi
+ */
+@Data
+public class ConflictRegion
+{
+ private Long id;
+ private String regionName;
+ private String regionColor;
+ private String relationship;
+}
\ No newline at end of file
diff --git a/ard-system/src/main/java/com/ard/data/region/domain/Coordinate.java b/ard-system/src/main/java/com/ard/data/region/domain/Coordinate.java
new file mode 100644
index 0000000..36e6b43
--- /dev/null
+++ b/ard-system/src/main/java/com/ard/data/region/domain/Coordinate.java
@@ -0,0 +1,40 @@
+package com.ard.data.region.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 坐标实体类
+ *
+ * @author ruoyi
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Coordinate
+{
+ /** 经度 */
+ private Double lng;
+
+ /** 纬度 */
+ private Double lat;
+
+ /**
+ * 转换为 MySQL 需要的格式 (纬度 经度)
+ */
+ public String toLatLngString()
+ {
+ return String.format("%.8f %.8f", lat, lng);
+ }
+
+ /**
+ * 验证坐标是否有效
+ */
+ public boolean isValid()
+ {
+ return lng != null && lat != null
+ && lng >= -180 && lng <= 180
+ && lat >= -90 && lat <= 90;
+ }
+}
\ No newline at end of file
diff --git a/ard-system/src/main/java/com/ard/data/region/domain/PointQueryDTO.java b/ard-system/src/main/java/com/ard/data/region/domain/PointQueryDTO.java
new file mode 100644
index 0000000..4606d52
--- /dev/null
+++ b/ard-system/src/main/java/com/ard/data/region/domain/PointQueryDTO.java
@@ -0,0 +1,18 @@
+package com.ard.data.region.domain;
+
+import lombok.Data;
+
+/**
+ * 坐标查询参数
+ *
+ * @author ruoyi
+ */
+@Data
+public class PointQueryDTO
+{
+ /** 纬度 */
+ private Double lat;
+
+ /** 经度 */
+ private Double lng;
+}
\ No newline at end of file
diff --git a/ard-system/src/main/java/com/ard/data/region/mapper/ArdDeptRegionMapper.java b/ard-system/src/main/java/com/ard/data/region/mapper/ArdDeptRegionMapper.java
new file mode 100644
index 0000000..207ab75
--- /dev/null
+++ b/ard-system/src/main/java/com/ard/data/region/mapper/ArdDeptRegionMapper.java
@@ -0,0 +1,89 @@
+package com.ard.data.region.mapper;
+
+import com.ard.data.region.domain.ArdDeptRegion;
+import com.ard.data.region.domain.ConflictRegion;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 部门区域Mapper接口
+ *
+ * @author ruoyi
+ */
+public interface ArdDeptRegionMapper
+{
+ /**
+ * 查询部门区域
+ */
+ public ArdDeptRegion selectArdDeptRegionById(Long id);
+
+ /**
+ * 查询部门区域列表
+ */
+ public List<ArdDeptRegion> selectArdDeptRegionList(ArdDeptRegion ardDeptRegion);
+
+ /**
+ * 新增部门区域
+ */
+ public int insertArdDeptRegion(ArdDeptRegion ardDeptRegion);
+
+ /**
+ * 修改部门区域
+ */
+ public int updateArdDeptRegion(ArdDeptRegion ardDeptRegion);
+
+ /**
+ * 启用/禁用区域
+ */
+ public int updateStatus(@Param("id") Long id, @Param("status") String status);
+
+ /**
+ * 删除部门区域
+ */
+ public int deleteArdDeptRegionById(Long id);
+
+ /**
+ * 批量删除部门区域
+ */
+ public int deleteArdDeptRegionByIds(String[] ids);
+
+ /**
+ * 根据区域名称查询区域(用于唯一性校验)
+ */
+ public ArdDeptRegion checkRegionNameUnique(@Param("regionName") String regionName);
+
+
+ /**
+ * 检查点冲突(1个坐标)
+ */
+ public List<ConflictRegion> checkPointConflict(@Param("deptId") Long deptId,
+ @Param("wkt") String wkt,
+ @Param("excludeId") Long excludeId);
+
+ /**
+ * 检查线冲突(2个坐标)
+ */
+ public List<ConflictRegion> checkLineConflict(@Param("deptId") Long deptId,
+ @Param("wkt") String wkt,
+ @Param("excludeId") Long excludeId);
+
+ /**
+ * 检查面冲突(3个以上坐标)
+ */
+ public List<ConflictRegion> checkPolygonConflict(@Param("deptId") Long deptId,
+ @Param("wkt") String wkt,
+ @Param("excludeId") Long excludeId);
+
+ /**
+ * 获取边界WKT
+ */
+ public String selectBoundaryWkt(@Param("id") Long id);
+
+ /**
+ * 根据坐标查询所属区域
+ */
+ public ArdDeptRegion selectRegionByPoint(@Param("pointWkt") String pointWkt);
+
+
+}
\ No newline at end of file
diff --git a/ard-system/src/main/java/com/ard/data/region/service/IArdDeptRegionService.java b/ard-system/src/main/java/com/ard/data/region/service/IArdDeptRegionService.java
new file mode 100644
index 0000000..31ba676
--- /dev/null
+++ b/ard-system/src/main/java/com/ard/data/region/service/IArdDeptRegionService.java
@@ -0,0 +1,64 @@
+package com.ard.data.region.service;
+
+import com.ard.data.region.domain.ArdDeptRegion;
+import com.ard.data.region.domain.ConflictCheckResult;
+
+import java.util.List;
+
+/**
+ * 部门区域Service接口
+ *
+ * @author ruoyi
+ */
+public interface IArdDeptRegionService
+{
+ /**
+ * 查询部门区域
+ */
+ public ArdDeptRegion selectArdDeptRegionById(Long id);
+
+ /**
+ * 查询部门区域列表
+ */
+ public List<ArdDeptRegion> selectArdDeptRegionList(ArdDeptRegion ardDeptRegion);
+
+ /**
+ * 新增部门区域
+ */
+ public int insertArdDeptRegion(ArdDeptRegion ardDeptRegion);
+
+ /**
+ * 修改部门区域
+ */
+ public int updateArdDeptRegion(ArdDeptRegion ardDeptRegion);
+
+ /**
+ * 修改状态(启用/禁用)
+ */
+ public int updateStatus(Long id, String status);
+
+ /**
+ * 批量删除部门区域
+ */
+ public int deleteArdDeptRegionByIds(String[] ids);
+
+ /**
+ * 删除部门区域信息
+ */
+ public int deleteArdDeptRegionById(Long id);
+
+ /**
+ * 校验区域名称是否唯一
+ */
+ public boolean checkRegionNameUnique(ArdDeptRegion ardDeptRegion);
+
+ /**
+ * 检查区域冲突
+ */
+ public ConflictCheckResult checkConflict(ArdDeptRegion ardDeptRegion);
+
+ /**
+ * 根据坐标查询所属区域
+ */
+ public ArdDeptRegion selectRegionByPoint(Double lat, Double lng);
+}
\ No newline at end of file
diff --git a/ard-system/src/main/java/com/ard/data/region/service/impl/ArdDeptRegionServiceImpl.java b/ard-system/src/main/java/com/ard/data/region/service/impl/ArdDeptRegionServiceImpl.java
new file mode 100644
index 0000000..43fc008
--- /dev/null
+++ b/ard-system/src/main/java/com/ard/data/region/service/impl/ArdDeptRegionServiceImpl.java
@@ -0,0 +1,298 @@
+package com.ard.data.region.service.impl;
+
+import com.ard.common.constant.RegionConstants;
+import com.ard.common.utils.DateUtils;
+import com.ard.common.utils.SecurityUtils;
+import com.ard.common.utils.StringUtils;
+import com.ard.data.region.domain.ArdDeptRegion;
+import com.ard.data.region.domain.ConflictCheckResult;
+import com.ard.data.region.domain.ConflictRegion;
+import com.ard.data.region.domain.Coordinate;
+import com.ard.data.region.mapper.ArdDeptRegionMapper;
+import com.ard.data.region.service.IArdDeptRegionService;
+import com.ard.data.region.utils.RegionCoordinateUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 部门区域Service业务层处理
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Service
+public class ArdDeptRegionServiceImpl implements IArdDeptRegionService
+{
+ @Autowired
+ private ArdDeptRegionMapper ardDeptRegionMapper;
+
+ @Override
+ public ArdDeptRegion selectArdDeptRegionById(Long id)
+ {
+ ArdDeptRegion region = ardDeptRegionMapper.selectArdDeptRegionById(id);
+ if (region != null)
+ {
+ region.setCoordinates(RegionCoordinateUtils.toCoordinates(region.getBoundaryWkt()));
+ }
+ return region;
+ }
+
+ @Override
+ public List<ArdDeptRegion> selectArdDeptRegionList(ArdDeptRegion ardDeptRegion)
+ {
+ List<ArdDeptRegion> list = ardDeptRegionMapper.selectArdDeptRegionList(ardDeptRegion);
+ list.forEach(region -> {
+ region.setCoordinates(RegionCoordinateUtils.toCoordinates(region.getBoundaryWkt()));
+ });
+ return list;
+ }
+
+ /**
+ * 校验区域名称是否唯一
+ */
+ @Override
+ public boolean checkRegionNameUnique(ArdDeptRegion ardDeptRegion)
+ {
+ Long id = StringUtils.isNull(ardDeptRegion.getId()) ? -1L : ardDeptRegion.getId();
+ ArdDeptRegion info = ardDeptRegionMapper.checkRegionNameUnique(ardDeptRegion.getRegionName());
+ if (StringUtils.isNotNull(info) && info.getId().longValue() != id.longValue())
+ {
+ return RegionConstants.NOT_UNIQUE;
+ }
+ return RegionConstants.UNIQUE;
+ }
+
+ /**
+ * 检查区域冲突
+ */
+ @Override
+ public ConflictCheckResult checkConflict(ArdDeptRegion ardDeptRegion)
+ {
+ ConflictCheckResult result = new ConflictCheckResult();
+
+ try
+ {
+ // 1. 验证坐标
+ List<Coordinate> coordinates = ardDeptRegion.getCoordinates();
+ RegionCoordinateUtils.validateCoordinates(coordinates);
+
+ // 2. 根据坐标数量自动判断类型
+ String regionType = RegionCoordinateUtils.getRegionTypeByCoordinates(coordinates);
+ ardDeptRegion.setRegionType(regionType);
+
+ // 3. 转换为WKT
+ String wkt = RegionCoordinateUtils.toWkt(coordinates);
+ ardDeptRegion.setBoundaryWkt(wkt);
+
+ // 4. 根据类型检查冲突
+ List<ConflictRegion> conflicts = null;
+ Long excludeId = ardDeptRegion.getId();
+
+ switch (regionType)
+ {
+ case "POINT":
+ conflicts = ardDeptRegionMapper.checkPointConflict(
+ ardDeptRegion.getDeptId(), wkt, excludeId);
+ break;
+ case "LINE":
+ conflicts = ardDeptRegionMapper.checkLineConflict(
+ ardDeptRegion.getDeptId(), wkt, excludeId);
+ break;
+ case "POLYGON":
+ conflicts = ardDeptRegionMapper.checkPolygonConflict(
+ ardDeptRegion.getDeptId(), wkt, excludeId);
+ break;
+ default:
+ result.setHasConflict(true);
+ result.setMessage("不支持的几何类型");
+ return result;
+ }
+
+ if (!CollectionUtils.isEmpty(conflicts))
+ {
+ result.setHasConflict(true);
+ result.setMessage(String.format("该%s与%d个已有区域存在冲突",
+ getRegionTypeName(regionType), conflicts.size()));
+ result.setConflictRegions(conflicts);
+ }
+ else
+ {
+ result.setHasConflict(false);
+ result.setMessage("无冲突,可以添加");
+ }
+ }
+ catch (Exception e)
+ {
+ log.error("检查冲突失败", e);
+ result.setHasConflict(true);
+ result.setMessage("检查失败: " + e.getMessage());
+ }
+
+ return result;
+ }
+
+ @Override
+ @Transactional
+ public int insertArdDeptRegion(ArdDeptRegion ardDeptRegion)
+ {
+ // 1. 检查冲突
+ ConflictCheckResult checkResult = checkConflict(ardDeptRegion);
+ if (checkResult.getHasConflict())
+ {
+ throw new RuntimeException(checkResult.getMessage());
+ }
+
+ // 2. 计算中心点和面积
+ List<Coordinate> coordinates = ardDeptRegion.getCoordinates();
+ Coordinate center = RegionCoordinateUtils.calculateCenter(coordinates);
+ ardDeptRegion.setCenterLat(center != null ? center.getLat() : null);
+ ardDeptRegion.setCenterLng(center != null ? center.getLng() : null);
+
+ // 只有面类型计算面积
+ if ("POLYGON".equals(ardDeptRegion.getRegionType()))
+ {
+ ardDeptRegion.setAreaSize(RegionCoordinateUtils.calculateArea(coordinates));
+ }
+ else
+ {
+ ardDeptRegion.setAreaSize(0.0);
+ }
+
+ // 3. 设置默认颜色
+ if (ardDeptRegion.getRegionColor() == null || ardDeptRegion.getRegionColor().isEmpty())
+ {
+ ardDeptRegion.setRegionColor("#3c8dbc");
+ }
+
+ // 4. 插入数据
+ ardDeptRegion.setCreateBy(SecurityUtils.getUsername());
+ ardDeptRegion.setCreateTime(DateUtils.getNowDate());
+ ardDeptRegion.setStatus("0");
+
+ return ardDeptRegionMapper.insertArdDeptRegion(ardDeptRegion);
+ }
+
+ @Override
+ @Transactional
+ public int updateArdDeptRegion(ArdDeptRegion ardDeptRegion)
+ {
+ // 1. 获取原区域
+ ArdDeptRegion oldRegion = selectArdDeptRegionById(ardDeptRegion.getId());
+ if (oldRegion == null)
+ {
+ throw new RuntimeException("区域不存在");
+ }
+
+ // 2. 如果坐标有变化,检查冲突
+ if (ardDeptRegion.getCoordinates() != null && !ardDeptRegion.getCoordinates().isEmpty())
+ {
+ List<Coordinate> coordinates = ardDeptRegion.getCoordinates();
+ RegionCoordinateUtils.validateCoordinates(coordinates);
+
+ String regionType = RegionCoordinateUtils.getRegionTypeByCoordinates(coordinates);
+ ardDeptRegion.setRegionType(regionType);
+
+ String wkt = RegionCoordinateUtils.toWkt(coordinates);
+ ardDeptRegion.setBoundaryWkt(wkt);
+
+ // 根据类型检查冲突
+ List<ConflictRegion> conflicts = null;
+
+ switch (regionType)
+ {
+ case "POINT":
+ conflicts = ardDeptRegionMapper.checkPointConflict(
+ ardDeptRegion.getDeptId(), wkt, ardDeptRegion.getId());
+ break;
+ case "LINE":
+ conflicts = ardDeptRegionMapper.checkLineConflict(
+ ardDeptRegion.getDeptId(), wkt, ardDeptRegion.getId());
+ break;
+ case "POLYGON":
+ conflicts = ardDeptRegionMapper.checkPolygonConflict(
+ ardDeptRegion.getDeptId(), wkt, ardDeptRegion.getId());
+ break;
+ }
+
+ if (!CollectionUtils.isEmpty(conflicts))
+ {
+ throw new RuntimeException("更新后的区域与已有区域存在冲突");
+ }
+
+ // 重新计算中心点和面积
+ Coordinate center = RegionCoordinateUtils.calculateCenter(coordinates);
+ ardDeptRegion.setCenterLat(center != null ? center.getLat() : null);
+ ardDeptRegion.setCenterLng(center != null ? center.getLng() : null);
+
+ if ("POLYGON".equals(regionType))
+ {
+ ardDeptRegion.setAreaSize(RegionCoordinateUtils.calculateArea(coordinates));
+ }
+ else
+ {
+ ardDeptRegion.setAreaSize(0.0);
+ }
+ }
+
+ ardDeptRegion.setUpdateBy(SecurityUtils.getUsername());
+ ardDeptRegion.setUpdateTime(DateUtils.getNowDate());
+
+ return ardDeptRegionMapper.updateArdDeptRegion(ardDeptRegion);
+ }
+
+ /**
+ * 修改状态(启用/禁用)
+ */
+ @Override
+ public int updateStatus(Long id, String status)
+ {
+ // 检查区域是否存在
+ ArdDeptRegion region = selectArdDeptRegionById(id);
+ if (region == null)
+ {
+ throw new RuntimeException("区域不存在");
+ }
+
+ // 更新状态
+ return ardDeptRegionMapper.updateStatus(id, status);
+ }
+
+ @Override
+ public ArdDeptRegion selectRegionByPoint(Double lat, Double lng)
+ {
+ String pointWkt = String.format("POINT(%f %f)", lat, lng);
+ return ardDeptRegionMapper.selectRegionByPoint(pointWkt);
+ }
+
+ @Override
+ public int deleteArdDeptRegionByIds(String[] ids)
+ {
+ return ardDeptRegionMapper.deleteArdDeptRegionByIds(ids);
+ }
+
+ @Override
+ public int deleteArdDeptRegionById(Long id)
+ {
+ return ardDeptRegionMapper.deleteArdDeptRegionById(id);
+ }
+
+ /**
+ * 获取区域类型中文名
+ */
+ private String getRegionTypeName(String type)
+ {
+ switch (type)
+ {
+ case "POINT": return "点";
+ case "LINE": return "线";
+ case "POLYGON": return "面";
+ default: return type;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ard-system/src/main/java/com/ard/data/region/utils/RegionCoordinateUtils.java b/ard-system/src/main/java/com/ard/data/region/utils/RegionCoordinateUtils.java
new file mode 100644
index 0000000..602ad4d
--- /dev/null
+++ b/ard-system/src/main/java/com/ard/data/region/utils/RegionCoordinateUtils.java
@@ -0,0 +1,154 @@
+package com.ard.data.region.utils;
+
+import com.ard.data.region.domain.Coordinate;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 区域坐标工具类
+ *
+ * @author ruoyi
+ */
+public class RegionCoordinateUtils {
+ /**
+ * 根据坐标数量自动判断区域类型
+ *
+ * @param coordinates 坐标集合
+ * @return POINT/LINE/POLYGON
+ */
+ public static String getRegionTypeByCoordinates(List<Coordinate> coordinates) {
+ if (CollectionUtils.isEmpty(coordinates)) {
+ throw new RuntimeException("坐标集合不能为空");
+ }
+
+ int size = coordinates.size();
+ if (size == 1) {
+ return "POINT";
+ } else if (size == 2) {
+ return "LINE";
+ } else {
+ return "POLYGON";
+ }
+ }
+
+ /**
+ * 将坐标集合转换为 WKT 格式(自动判断类型)
+ */
+ public static String toWkt(List<Coordinate> coordinates) {
+ if (CollectionUtils.isEmpty(coordinates)) {
+ throw new RuntimeException("坐标集合不能为空");
+ }
+
+ int size = coordinates.size();
+
+ if (size == 1) {
+ // 点
+ Coordinate coord = coordinates.get(0);
+ return String.format("POINT(%s)", coord.toLatLngString());
+ } else if (size == 2) {
+ // 线
+ List<String> points = coordinates.stream()
+ .map(Coordinate::toLatLngString)
+ .collect(Collectors.toList());
+ return String.format("LINESTRING(%s, %s)", points.get(0), points.get(1));
+ } else {
+ // 面(多边形)
+ List<String> points = coordinates.stream()
+ .map(Coordinate::toLatLngString)
+ .collect(Collectors.toList());
+ String allPoints = String.join(", ", points);
+ String firstPoint = points.get(0);
+ return String.format("POLYGON((%s, %s))", allPoints, firstPoint);
+ }
+ }
+
+ /**
+ * 将 WKT 字符串转换为坐标集合
+ *
+ * @param wkt 如:POLYGON((45.718839 126.670721, 45.708756 126.670151, ...))
+ * @return 坐标集合
+ */
+ public static List<Coordinate> toCoordinates(String wkt) {
+ List<Coordinate> coordinates = new ArrayList<>();
+ if (wkt == null || wkt.isEmpty()) {
+ return coordinates;
+ }
+ // 提取括号内的坐标字符串
+ String coordStr = wkt;
+ if (wkt.startsWith("POLYGON")) {
+ coordStr = wkt.substring(wkt.indexOf("((") + 2, wkt.lastIndexOf("))"));
+ } else if (wkt.startsWith("LINESTRING")) {
+ coordStr = wkt.substring(wkt.indexOf("(") + 1, wkt.lastIndexOf(")"));
+ } else if (wkt.startsWith("POINT")) {
+ coordStr = wkt.substring(wkt.indexOf("(") + 1, wkt.lastIndexOf(")"));
+ }
+
+ // 分割坐标点
+ String[] points = coordStr.split(",");
+ for (String point : points) {
+ String[] lngLat = point.trim().split(" ");
+ if (lngLat.length >= 2) {
+ Double lat = Double.parseDouble(lngLat[0]);
+ Double lng = Double.parseDouble(lngLat[1]);
+ coordinates.add(new Coordinate(lng, lat));
+ }
+ }
+ return coordinates;
+ }
+
+ /**
+ * 计算中心点
+ */
+ public static Coordinate calculateCenter(List<Coordinate> coordinates) {
+ if (CollectionUtils.isEmpty(coordinates)) {
+ return null;
+ }
+ double sumLng = 0;
+ double sumLat = 0;
+ for (Coordinate coord : coordinates) {
+ sumLng += coord.getLng();
+ sumLat += coord.getLat();
+ }
+ return new Coordinate(sumLng / coordinates.size(), sumLat / coordinates.size());
+ }
+
+ /**
+ * 计算面积(仅面类型有效)
+ */
+ public static double calculateArea(List<Coordinate> coordinates) {
+ if (coordinates == null || coordinates.size() < 3) {
+ return 0;
+ }
+
+ double area = 0;
+ int n = coordinates.size();
+ for (int i = 0; i < n; i++) {
+ Coordinate current = coordinates.get(i);
+ Coordinate next = coordinates.get((i + 1) % n);
+ area += current.getLng() * next.getLat() - next.getLng() * current.getLat();
+ }
+ area = Math.abs(area) / 2;
+
+ double lat = coordinates.get(0).getLat();
+ double meterPerDegree = 111000 * Math.cos(Math.toRadians(lat));
+ return area * meterPerDegree * 111000;
+ }
+
+ /**
+ * 验证坐标集合
+ */
+ public static void validateCoordinates(List<Coordinate> coordinates) {
+ if (CollectionUtils.isEmpty(coordinates)) {
+ throw new RuntimeException("坐标集合不能为空");
+ }
+
+ for (Coordinate coord : coordinates) {
+ if (!coord.isValid()) {
+ throw new RuntimeException("无效坐标");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ard-system/src/main/resources/mapper/data/ArdDeptRegionMapper.xml b/ard-system/src/main/resources/mapper/data/ArdDeptRegionMapper.xml
new file mode 100644
index 0000000..d9bb961
--- /dev/null
+++ b/ard-system/src/main/resources/mapper/data/ArdDeptRegionMapper.xml
@@ -0,0 +1,210 @@
+<?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.ard.data.region.mapper.ArdDeptRegionMapper">
+
+ <resultMap type="ArdDeptRegion" id="ArdDeptRegionResult">
+ <id property="id" column="id" />
+ <result property="deptId" column="dept_id" />
+ <result property="regionName" column="region_name" />
+ <result property="regionColor" column="region_color" />
+ <result property="centerLat" column="center_lat" />
+ <result property="centerLng" column="center_lng" />
+ <result property="areaSize" column="area_size" />
+ <result property="status" column="status" />
+ <result property="createBy" column="create_by" />
+ <result property="createTime" column="create_time" />
+ <result property="updateBy" column="update_by" />
+ <result property="updateTime" column="update_time" />
+ <result property="remark" column="remark" />
+ </resultMap>
+
+ <sql id="selectArdDeptRegionVo">
+ SELECT r.id, r.dept_id, r.region_name, r.region_color,
+ ST_AsText(r.region_boundary) as boundary_wkt,
+ r.center_lat, r.center_lng, r.area_size,
+ r.status, r.create_by, r.create_time, r.update_by, r.update_time, r.remark,
+ d.dept_name
+ FROM ard_dept_region r
+ LEFT JOIN sys_dept d ON r.dept_id = d.dept_id
+ </sql>
+
+ <!-- 查询部门区域列表(查询所有,不过滤状态) -->
+ <select id="selectArdDeptRegionList" parameterType="ArdDeptRegion" resultMap="ArdDeptRegionResult">
+ <include refid="selectArdDeptRegionVo"/>
+ <where>
+ <if test="deptId != null"> AND r.dept_id = #{deptId}</if>
+ <if test="regionName != null and regionName != ''"> AND r.region_name like concat('%', #{regionName}, '%')</if>
+ <if test="status != null and status != ''"> AND r.status = #{status}</if>
+ </where>
+ ORDER BY r.create_time DESC
+ </select>
+
+ <!-- 查询部门区域(根据ID,查询所有) -->
+ <select id="selectArdDeptRegionById" parameterType="Long" resultMap="ArdDeptRegionResult">
+ <include refid="selectArdDeptRegionVo"/>
+ WHERE r.id = #{id}
+ </select>
+
+ <!-- 获取边界WKT -->
+ <select id="selectBoundaryWkt" parameterType="Long" resultType="String">
+ SELECT ST_AsText(region_boundary)
+ FROM ard_dept_region
+ WHERE id = #{id}
+ </select>
+
+ <!-- 根据区域名称查询区域(用于唯一性校验,查询所有) -->
+ <select id="checkRegionNameUnique" parameterType="String" resultMap="ArdDeptRegionResult">
+ SELECT id, dept_id, region_name, region_color,
+ center_lat, center_lng, area_size,
+ status, create_by, create_time, update_by, update_time, remark
+ FROM ard_dept_region
+ WHERE region_name = #{regionName}
+ LIMIT 1
+ </select>
+
+ <!-- 检查点冲突(只查启用的区域) -->
+ <select id="checkPointConflict" resultType="ConflictRegion">
+ SELECT
+ id,
+ region_name as regionName,
+ region_color as regionColor,
+ CASE
+ WHEN ST_Contains(region_boundary, ST_GeomFromText(#{wkt}, 4326)) THEN '包含该点'
+ WHEN ST_Intersects(region_boundary, ST_GeomFromText(#{wkt}, 4326)) THEN '相交'
+ ELSE '无冲突'
+ END as relationship
+ FROM ard_dept_region
+ WHERE status = '0'
+ <if test="deptId != null">
+ AND dept_id = #{deptId}
+ </if>
+ <if test="excludeId != null">
+ AND id != #{excludeId}
+ </if>
+ AND ST_Intersects(region_boundary, ST_GeomFromText(#{wkt}, 4326))
+ </select>
+
+ <!-- 检查线冲突(只查启用的区域) -->
+ <select id="checkLineConflict" resultType="ConflictRegion">
+ SELECT
+ id,
+ region_name as regionName,
+ region_color as regionColor,
+ CASE
+ WHEN ST_Intersects(region_boundary, ST_GeomFromText(#{wkt}, 4326)) THEN '相交'
+ WHEN ST_Contains(region_boundary, ST_GeomFromText(#{wkt}, 4326)) THEN '包含该线'
+ ELSE '无冲突'
+ END as relationship
+ FROM ard_dept_region
+ WHERE status = '0'
+ <if test="deptId != null">
+ AND dept_id = #{deptId}
+ </if>
+ <if test="excludeId != null">
+ AND id != #{excludeId}
+ </if>
+ AND ST_Intersects(region_boundary, ST_GeomFromText(#{wkt}, 4326))
+ </select>
+
+ <!-- 检查面冲突(只查启用的区域) -->
+ <select id="checkPolygonConflict" resultType="ConflictRegion">
+ SELECT
+ id,
+ region_name as regionName,
+ region_color as regionColor,
+ CASE
+ WHEN ST_Contains(region_boundary, ST_GeomFromText(#{wkt}, 4326)) THEN '包含该面'
+ WHEN ST_Within(ST_GeomFromText(#{wkt}, 4326), region_boundary) THEN '在该面内部'
+ WHEN ST_Overlaps(region_boundary, ST_GeomFromText(#{wkt}, 4326)) THEN '重叠'
+ WHEN ST_Intersects(region_boundary, ST_GeomFromText(#{wkt}, 4326)) THEN '相交'
+ ELSE '无冲突'
+ END as relationship
+ FROM ard_dept_region
+ WHERE status = '0'
+ <if test="deptId != null">
+ AND dept_id = #{deptId}
+ </if>
+ <if test="excludeId != null">
+ AND id != #{excludeId}
+ </if>
+ AND ST_Intersects(region_boundary, ST_GeomFromText(#{wkt}, 4326))
+ </select>
+
+ <!-- 根据坐标查询所属区域(查询所有区域,包括禁用的) -->
+ <select id="selectRegionByPoint" resultMap="ArdDeptRegionResult">
+ <include refid="selectArdDeptRegionVo"/>
+ WHERE ST_Contains(r.region_boundary, ST_GeomFromText(#{pointWkt}, 4326))
+ LIMIT 1
+ </select>
+
+ <!-- 新增部门区域(默认状态为启用0) -->
+ <insert id="insertArdDeptRegion" parameterType="ArdDeptRegion" useGeneratedKeys="true" keyProperty="id">
+ INSERT INTO ard_dept_region
+ <trim prefix="(" suffix=")" suffixOverrides=",">
+ <if test="deptId != null">dept_id,</if>
+ <if test="regionName != null">region_name,</if>
+ <if test="regionColor != null">region_color,</if>
+ <if test="boundaryWkt != null">region_boundary,</if>
+ <if test="centerLat != null">center_lat,</if>
+ <if test="centerLng != null">center_lng,</if>
+ <if test="areaSize != null">area_size,</if>
+ <if test="createBy != null">create_by,</if>
+ <if test="remark != null">remark,</if>
+ status, create_time
+ </trim>
+ VALUES
+ <trim prefix="(" suffix=")" suffixOverrides=",">
+ <if test="deptId != null">#{deptId},</if>
+ <if test="regionName != null">#{regionName},</if>
+ <if test="regionColor != null">#{regionColor},</if>
+ <if test="boundaryWkt != null">ST_GeomFromText(#{boundaryWkt}, 4326),</if>
+ <if test="centerLat != null">#{centerLat},</if>
+ <if test="centerLng != null">#{centerLng},</if>
+ <if test="areaSize != null">#{areaSize},</if>
+ <if test="createBy != null">#{createBy},</if>
+ <if test="remark != null">#{remark},</if>
+ '0', NOW()
+ </trim>
+ </insert>
+
+ <!-- 修改部门区域 -->
+ <update id="updateArdDeptRegion" parameterType="ArdDeptRegion">
+ UPDATE ard_dept_region
+ <set>
+ <if test="regionName != null">region_name = #{regionName},</if>
+ <if test="regionColor != null">region_color = #{regionColor},</if>
+ <if test="boundaryWkt != null">region_boundary = ST_GeomFromText(#{boundaryWkt}, 4326),</if>
+ <if test="centerLat != null">center_lat = #{centerLat},</if>
+ <if test="centerLng != null">center_lng = #{centerLng},</if>
+ <if test="areaSize != null">area_size = #{areaSize},</if>
+ <if test="status != null">status = #{status},</if>
+ <if test="updateBy != null">update_by = #{updateBy},</if>
+ <if test="remark != null">remark = #{remark},</if>
+ update_time = NOW()
+ </set>
+ WHERE id = #{id}
+ </update>
+
+ <!-- 启用/禁用区域 -->
+ <update id="updateStatus">
+ UPDATE ard_dept_region
+ SET status = #{status}, update_time = NOW()
+ WHERE id = #{id}
+ </update>
+
+ <!-- 物理删除区域 -->
+ <delete id="deleteArdDeptRegionById" parameterType="Long">
+ DELETE FROM ard_dept_region WHERE id = #{id}
+ </delete>
+
+ <!-- 批量物理删除区域 -->
+ <delete id="deleteArdDeptRegionByIds" parameterType="String">
+ DELETE FROM ard_dept_region WHERE id IN
+ <foreach item="id" collection="array" open="(" separator="," close=")">
+ #{id}
+ </foreach>
+ </delete>
+
+</mapper>
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/work/component/websocket/handler/UavAllManager.java b/ard-work/src/main/java/com/ard/work/component/websocket/handler/UavAllManager.java
index 9e60142..103d511 100644
--- a/ard-work/src/main/java/com/ard/work/component/websocket/handler/UavAllManager.java
+++ b/ard-work/src/main/java/com/ard/work/component/websocket/handler/UavAllManager.java
@@ -3,11 +3,10 @@
import com.ard.common.constant.CacheConstants;
import com.ard.common.core.domain.entity.SysUser;
import com.ard.common.core.redis.RedisCache;
+import com.ard.data.region.domain.ArdDeptRegion;
+import com.ard.data.region.service.IArdDeptRegionService;
import com.ard.work.config.RouteStateMachineService;
-import com.ard.work.domain.cache.UavSignalInfoCache;
-import com.ard.work.domain.cache.UavStateCache;
-import com.ard.work.domain.cache.UavTelemetryDataCache;
-import com.ard.work.domain.cache.UavWaylineCache;
+import com.ard.work.domain.cache.*;
import com.ard.work.domain.dto.signal.SignalInfo;
import com.ard.work.domain.dto.state.UavState;
import com.ard.work.domain.dto.telemetry.TelemetryData;
@@ -69,6 +68,9 @@
// 将载荷当前控制者用户ID添加到映射中
map.put("payloadController", payloadUser);
}
+ // 获取当前无人机所属区域
+ ArdDeptRegion ardDeptRegion = UavRegionCache.get(boxSn);
+ map.put("region", ardDeptRegion);
return map;
}
diff --git a/ard-work/src/main/java/com/ard/work/domain/cache/UavRegionCache.java b/ard-work/src/main/java/com/ard/work/domain/cache/UavRegionCache.java
new file mode 100644
index 0000000..cc96b99
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/domain/cache/UavRegionCache.java
@@ -0,0 +1,30 @@
+package com.ard.work.domain.cache;
+
+import com.ard.data.region.domain.ArdDeptRegion;
+import lombok.extern.slf4j.Slf4j;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 无人机区域缓存(简化版)
+ *
+ * @author 刘苏义
+ */
+@Slf4j
+public class UavRegionCache {
+
+ // 无人机当前区域名称缓存 Map<无人机ID, 区域>
+ private static final ConcurrentHashMap<String, ArdDeptRegion> cache = new ConcurrentHashMap<>();
+
+ // 存储数据到缓存
+ public static void put(String boxSn, ArdDeptRegion region) {cache.put(boxSn, region);}
+
+ // 从缓存中获取数据
+ public static ArdDeptRegion get(String boxSn) {
+ return cache.get(boxSn);
+ }
+
+ // 删除缓存中的数据
+ public static void remove(String boxSn) {
+ cache.remove(boxSn);
+ }
+}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/work/service/impl/TyServiceImpl.java b/ard-work/src/main/java/com/ard/work/service/impl/TyServiceImpl.java
index 83e24dd..2fc7464 100644
--- a/ard-work/src/main/java/com/ard/work/service/impl/TyServiceImpl.java
+++ b/ard-work/src/main/java/com/ard/work/service/impl/TyServiceImpl.java
@@ -86,10 +86,10 @@
private TyClient tyClient;
@Autowired
private RouteStateMachineService stateMachineService;
- @Autowired
- private IArdFlightTaskPartService ardFlightTaskPartService;
@Resource
- private MediamtxConfig mediamtxConfig;
+ private UavPositionChangeService uavPositionChangeService;
+ @Resource
+ private UavRegionDetectService uavRegionDetectService;
/**
* 获取边缘服务器token信息
*
@@ -981,6 +981,16 @@
// 处理无人机遥测消息
UavTelemetryDataCmd uavTelemetryDataCmd = (UavTelemetryDataCmd) tyCommand;
TelemetryData telemetryData = uavTelemetryDataCmd.getTelemetryData();
+
+ // 判断是否需要触发区域检测
+ if (uavPositionChangeService.shouldTriggerRegionCheck(telemetryData)) {
+ log.debug("无人机 {} 位置变化,触发区域检测", telemetryData.getBoxSn());
+ // 异步触发区域检测(不阻塞)
+ uavRegionDetectService.detectRegionAsync(telemetryData);
+ } else {
+ log.debug("无人机 {} 位置未变化,跳过区域检测", telemetryData.getBoxSn());
+ }
+
UavTelemetryDataCache.put(telemetryData);
//logPrint("无人机遥测", uavTelemetryDataCmd.getTelemetryData().getBoxSn());
//WebSocketUtil.sendMessageAll(uavTelemetryDataCmd.getTelemetryData());
diff --git a/ard-work/src/main/java/com/ard/work/service/impl/UavPositionChangeService.java b/ard-work/src/main/java/com/ard/work/service/impl/UavPositionChangeService.java
new file mode 100644
index 0000000..468f696
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/service/impl/UavPositionChangeService.java
@@ -0,0 +1,89 @@
+package com.ard.work.service.impl;
+
+import com.ard.work.domain.cache.UavTelemetryDataCache;
+import com.ard.work.domain.dto.telemetry.TelemetryData;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * 无人机位置变化判断服务
+ *
+ * @author 刘苏义
+ */
+@Slf4j
+@Service
+public class UavPositionChangeService {
+
+ // 位置变化阈值(米)
+ private static final double POSITION_CHANGE_THRESHOLD = 5.0;
+
+ // 无效坐标的默认值
+ private static final double INVALID_COORD = 0.0;
+
+ /**
+ * 判断是否需要触发区域检测
+ * @param telemetryData 当前遥测数据
+ * @return true-需要触发区域检测,false-不需要
+ */
+ public boolean shouldTriggerRegionCheck(TelemetryData telemetryData) {
+ String boxSn = telemetryData.getBoxSn();
+
+ // 获取上次遥测数据
+ TelemetryData lastData = UavTelemetryDataCache.get(boxSn);
+
+ // 首次上报,需要检测
+ if (lastData == null) {
+ log.debug("无人机 {} 首次上报,触发区域检测", boxSn);
+ return true;
+ }
+
+ // 判断位置是否变化
+ return isPositionChanged(lastData, telemetryData);
+ }
+
+ /**
+ * 判断位置是否发生变化
+ */
+ private boolean isPositionChanged(TelemetryData lastData, TelemetryData newData) {
+ double lastLng = lastData.getLng();
+ double lastLat = lastData.getLat();
+ double newLng = newData.getLng();
+ double newLat = newData.getLat();
+
+ // 坐标无效(为0),认为需要检测
+ if (lastLng == INVALID_COORD || lastLat == INVALID_COORD ||
+ newLng == INVALID_COORD || newLat == INVALID_COORD) {
+ log.debug("坐标无效,触发区域检测");
+ return true;
+ }
+
+ // 计算移动距离
+ double distance = calculateDistance(lastLat, lastLng, newLat, newLng);
+
+ boolean changed = distance > POSITION_CHANGE_THRESHOLD;
+
+ if (changed) {
+ log.debug("无人机 {} 移动了 {:.2f} 米,触发区域检测", newData.getBoxSn(), distance);
+ }
+
+ return changed;
+ }
+
+ /**
+ * 计算两点间距离(米)- Haversine公式
+ */
+ private double calculateDistance(double lat1, double lng1, double lat2, double lng2) {
+ double earthRadius = 6371000; // 地球半径(米)
+
+ double dLat = Math.toRadians(lat2 - lat1);
+ double dLng = Math.toRadians(lng2 - lng1);
+
+ double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+ Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
+ Math.sin(dLng / 2) * Math.sin(dLng / 2);
+
+ double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+
+ return earthRadius * c;
+ }
+}
\ No newline at end of file
diff --git a/ard-work/src/main/java/com/ard/work/service/impl/UavRegionDetectService.java b/ard-work/src/main/java/com/ard/work/service/impl/UavRegionDetectService.java
new file mode 100644
index 0000000..84dd64c
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/service/impl/UavRegionDetectService.java
@@ -0,0 +1,43 @@
+package com.ard.work.service.impl;
+
+import com.ard.data.region.domain.ArdDeptRegion;
+import com.ard.data.region.service.IArdDeptRegionService;
+import com.ard.work.domain.cache.UavRegionCache;
+import com.ard.work.domain.cache.UavTelemetryDataCache;
+import com.ard.work.domain.dto.telemetry.TelemetryData;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+/**
+ * 无人机区域检测服务(异步)
+ *
+ * @author 刘苏义
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class UavRegionDetectService {
+
+ private final IArdDeptRegionService ardDeptRegionService;
+
+ /**
+ * 异步检测无人机所在区域
+ */
+ @Async
+ public void detectRegionAsync(TelemetryData telemetryData) {
+ String boxSn = telemetryData.getBoxSn();
+ try {
+ // 1. 查询当前坐标所属区域
+ ArdDeptRegion region = null;
+ if (telemetryData.getLat() != 0.0 && telemetryData.getLng() != 0.0) {
+ region = ardDeptRegionService.selectRegionByPoint(telemetryData.getLat(), telemetryData.getLng());
+ }
+ // 3. 更新区域缓存
+ UavRegionCache.put(boxSn, region);
+ } catch (Exception e) {
+ log.error("无人机 {} 区域检测失败: {}", boxSn, e.getMessage(), e);
+ }
+ }
+}
\ No newline at end of file
--
Gitblit v1.9.3