增加禁飞区

This commit is contained in:
sdy 2026-06-09 18:14:35 +08:00
parent 139e2eff46
commit 61ca367330
10 changed files with 782 additions and 49 deletions

View File

@ -8,6 +8,9 @@ package com.multictrl.common.constant;
*/
public interface BusinessConstant {
String WEB_EVENT_TOPIC = "thing/product/%s/web_event";
String NOFLY_ZONE_METHOD = "nofly_zone";
//********************************* minio *********************************//
String ROUTE_IMG_BUCKET = "route-images";//航线图片桶
String ROUTE_KMZ_BUCKET = "route-kmz";//航线桶
@ -83,6 +86,10 @@ public interface BusinessConstant {
String UAV_LIGHT_INDEX = "uav_light_index_";
String UAV_MODE_CODE = "uav_mode_code_";
//********************************* other cache key *********************************//
String DOCK_NOFLY_ZONE = "dock_nofly_zone_";
String DOCK_NOFLY_ZONE_TRIGGER_SIGN = "dock_nofly_zone_trigger_sign_";
//********************************* other *********************************//
String HTTP_PROTOCOL = "http://";
String HTTPS_PROTOCOL = "https://";

View File

@ -0,0 +1,97 @@
package com.multictrl.modules.business.controller;
import com.multictrl.common.annotation.ApiOrder;
import com.multictrl.common.annotation.LogOperation;
import com.multictrl.common.constant.Constant;
import com.multictrl.common.page.PageData;
import com.multictrl.common.utils.Result;
import com.multictrl.common.validator.AssertUtils;
import com.multictrl.common.validator.ValidatorUtils;
import com.multictrl.common.validator.group.AddGroup;
import com.multictrl.common.validator.group.UpdateGroup;
import com.multictrl.modules.business.dto.NoflyZoneDTO;
import com.multictrl.modules.business.service.NoflyZoneService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import java.util.List;
import java.util.Map;
/**
* 禁飞区
*
* @author Sdy
* @since 1.0.0 2026-06-09
*/
@RestController
@RequestMapping("business/noflyzone")
@Tag(name = "禁飞区")
@ApiOrder(22)
@RequiredArgsConstructor
public class NoflyZoneController {
private final NoflyZoneService noflyZoneService;
@GetMapping("page")
@Operation(summary = "分页")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码从1开始"),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数"),
@Parameter(name = "name", description = "名称")
})
@RequiresPermissions("bus:noflyzone:page")
public Result<PageData<NoflyZoneDTO>> page(@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
PageData<NoflyZoneDTO> page = noflyZoneService.pageList(params);
return new Result<PageData<NoflyZoneDTO>>().ok(page);
}
@PostMapping
@Operation(summary = "保存")
@LogOperation("保存")
@RequiresPermissions("bus:noflyzone:save")
public Result<Object> save(@RequestBody NoflyZoneDTO dto) {
//效验数据
ValidatorUtils.validateEntity(dto, AddGroup.class);
noflyZoneService.saveNoFlying(dto);
return new Result<>();
}
@PutMapping
@Operation(summary = "修改")
@LogOperation("修改")
@RequiresPermissions("bus:noflyzone:update")
public Result<Object> update(@RequestBody NoflyZoneDTO dto) {
//效验数据
ValidatorUtils.validateEntity(dto, UpdateGroup.class);
noflyZoneService.update(dto);
return new Result<>();
}
@DeleteMapping
@Operation(summary = "删除")
@LogOperation("删除")
@RequiresPermissions("bus:noflyzone:delete")
public Result<Object> delete(@RequestBody Long[] ids) {
//效验数据
AssertUtils.isArrayEmpty(ids, "id");
noflyZoneService.delete(ids);
return new Result<>();
}
@GetMapping("/getNoFlying")
@Operation(summary = "获取机库禁飞区", description = "返回机库所属组织以及上级组织设置的禁飞区")
public Result<List<NoflyZoneDTO>> getNoFlying(@RequestParam String dockSn) {
List<NoflyZoneDTO> list = noflyZoneService.getNoFlyZoneByDockSn(dockSn);
return new Result<List<NoflyZoneDTO>>().ok(list);
}
}

View File

@ -0,0 +1,30 @@
package com.multictrl.modules.business.dao;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.multictrl.common.dao.BaseDao;
import com.multictrl.modules.business.dto.NoflyZoneDTO;
import com.multictrl.modules.business.entity.NoflyZoneEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 禁飞区
*
* @author Sdy
* @since 1.0.0 2026-06-09
*/
@Mapper
public interface NoflyZoneDao extends BaseDao<NoflyZoneEntity> {
//分页列表
IPage<NoflyZoneDTO> pageList(IPage<NoflyZoneEntity> page, @Param("ew") QueryWrapper<NoflyZoneEntity> ew);
//新增禁飞区
void saveNoFlyZone(NoflyZoneEntity noflyZoneEntity);
//查询禁飞区
List<NoflyZoneDTO> getNoFlyZoneList(@Param("ew") QueryWrapper<NoflyZoneEntity> ew);
}

View File

@ -0,0 +1,82 @@
package com.multictrl.modules.business.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.multictrl.common.validator.group.AddGroup;
import com.multictrl.common.validator.group.UpdateGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import jakarta.validation.groups.Default;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 禁飞区
*
* @author Sdy
* @since 1.0.0 2026-06-09
*/
@Data
@Schema(name = "禁飞区")
public class NoflyZoneDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Null(message = "标识必须为空", groups = {AddGroup.class})
@NotNull(message = "标识不能为空", groups = {UpdateGroup.class})
@Schema(description = "标识")
private Long id;
@Schema(description = "名称")
@NotBlank(message = "名称不能为空", groups = {AddGroup.class, UpdateGroup.class})
private String name;
@NotBlank(message = "是否开启不能为空", groups = {AddGroup.class, UpdateGroup.class})
@Schema(description = "是否开启 1开启 0未开启")
private String status;
@Schema(description = "范围")
@NotEmpty(message = "范围不能为空", groups = {AddGroup.class})
@Size(min = 2, max = 2, message = "范围数据错误", groups = {AddGroup.class})
private List<List<@Valid point>> extent;
@Schema(description = "时间")
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Date createDate;
@Schema(hidden = true)
private String deptName;
@Schema(hidden = true)
private String userName;
@Schema(hidden = true)
@JsonIgnore
private String extentWkt;
@AllArgsConstructor
@NoArgsConstructor
@Data
public static class point {
@NotNull(message = "经度不能为空", groups = {AddGroup.class})
@Schema(description = "经度")
private Double lng;
@NotNull(message = "纬度不能为空", groups = {AddGroup.class})
@Schema(description = "纬度")
private Double lat;
@NotNull(message = "海拔高度不能为空", groups = {AddGroup.class})
@Schema(description = "海拔高度")
private Double height;
}
}

View File

@ -0,0 +1,39 @@
package com.multictrl.modules.business.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.multictrl.common.entity.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 禁飞区
*
* @author Sdy
* @since 1.0.0 2026-06-09
*/
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("bus_nofly_zone")
public class NoflyZoneEntity extends BaseEntity {
/**
* 名称
*/
private String name;
/**
* 是否开启 1开启 0未开启
*/
private String status;
/**
* 范围
*/
private String extent;
/**
* 部门标识
*/
@TableField(fill = FieldFill.INSERT)
private Long deptId;
}

View File

@ -7,10 +7,10 @@ import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.constant.DockMode;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.common.utils.JsonUtils;
import com.multictrl.modules.business.dto.NoflyZoneDTO;
import com.multictrl.modules.business.influxdb.FlightLog;
import com.multictrl.modules.business.influxdb.UavReport;
import com.multictrl.modules.business.service.DockService;
import com.multictrl.modules.business.service.FlightTaskService;
import com.multictrl.modules.business.service.InfluxService;
import com.multictrl.modules.business.service.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@ -18,6 +18,9 @@ import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* osd设备属性定频上报0.5HZ
@ -33,6 +36,9 @@ public class OsdHandler implements MessageHandler {
private final InfluxService influxService;
private final FlightTaskService flightTaskService;
private final DockService dockService;
private final DJIBaseService djiBaseService;
private final MqttPushService mqttPushService;
private final NoflyZoneService noflyZoneService;
@Override
public void handleMessage(String topic, String payload, String gateway) {
@ -145,10 +151,117 @@ public class OsdHandler implements MessageHandler {
.horizontal_speed(data.getDouble("horizontal_speed"))
.build();
influxService.addRecord(uavReport);
//禁飞区校验
Double height = data.getDouble("height");
NoflyZoneDTO.point point = new NoflyZoneDTO.point(uavReport.getLongitude(), uavReport.getLatitude(), height);
judgingNoFlyZone(dockSn, point, mode);
}
}
} else {
log.debug("osd --> payload解析失败解析后为null");
}
}
//禁飞区限制
// 允许触发返航的飞行模式集合
//"3":"手动飞行","4":"自动起飞","5":"航线飞行","16":"虚拟摇杆状态","17":"指令飞行"
private static final Set<Integer> ALLOWED_MODE_CODES = Set.of(3, 4, 5, 16, 17);
// 按设备隔离的计数器线程安全
private final ConcurrentHashMap<String, AtomicInteger> counterMap = new ConcurrentHashMap<>();
// 单线程执行器若需提高并发可调整线程数或使用同步
private final ExecutorService executor = new ThreadPoolExecutor(1, 1, 0L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
private void judgingNoFlyZone(String dockSn, NoflyZoneDTO.point point, Integer modeCode) {
// 1. 模式过滤快速失败
if (!ALLOWED_MODE_CODES.contains(modeCode)) {
return;
}
// 2. 按设备自增计数器判断是否需要校验每3次上报校验一次
AtomicInteger deviceCounter = counterMap.computeIfAbsent(dockSn, k -> new AtomicInteger(0));
int count = deviceCounter.incrementAndGet();
if (count % 3 != 1) {
return;
}
// 3. 获取禁飞区列表
Object obj = CacheUtils.get(BusinessConstant.DOCK_NOFLY_ZONE + dockSn);
if (!(obj instanceof List)) {
return;
}
List<NoflyZoneDTO> noFlyZoneList = (List<NoflyZoneDTO>) obj;
// 4. 遍历禁飞区检查是否已触发返航
String triggerKey = BusinessConstant.DOCK_NOFLY_ZONE_TRIGGER_SIGN + dockSn;
for (NoflyZoneDTO dto : noFlyZoneList) {
// 先判断是否已在触发中快速路径
if (CacheUtils.get(triggerKey) != null) {
return;
}
// 执行点是否在禁飞区内
if (noflyZoneService.isInNoFlyZone(dto.getExtent(), point)) {
// 原子设置触发标记防止并发重复触发
if (trySetTrigger(triggerKey)) {
// 异步执行返航任务
CompletableFuture.runAsync(() -> executeReturnHome(dockSn, dto), executor);
}
// 只要命中一个禁飞区就停止遍历
return;
}
}
}
/**
* 原子设置触发标记使用缓存框架的 putIfAbsent 或本地锁
* 返回 true 表示当前线程成功设置false 表示已被其他线程设置
*/
private boolean trySetTrigger(String key) {
synchronized (this) {
if (CacheUtils.get(key) == null) {
CacheUtils.set(key, true, 1000 * 60);
return true;
}
return false;
}
}
/**
* 执行返航并推送状态抽离重复逻辑
*/
private void executeReturnHome(String dockSn, NoflyZoneDTO dto) {
String zoneName = dto.getName();
// 1. 发送进入禁飞区通知type=0
logAndPush(dockSn, zoneName, 0, "飞机进入禁飞区,强制返航");
try {
// 2. 执行返航指令
djiBaseService.executeAndReturnResult(dockSn, "return_home");
// 3. 发送成功通知type=1
logAndPush(dockSn, zoneName, 1, "飞机进入禁飞区,强制返航成功");
} catch (Exception e) {
// 记录异常堆栈便于排查
log.error("返航失败dockSn={}, zone={}", dockSn, zoneName, e);
// 4. 发送失败通知type=2
logAndPush(dockSn, zoneName, 2, "飞机进入禁飞区,强制返航失败,请手动返航");
}
}
/**
* 统一记录飞行日志并推送 MQTT 消息
*/
private void logAndPush(String dockSn, String zoneName, int type, String msg) {
// 记录 InfluxDB 日志
FlightLog flightLog = new FlightLog();
flightLog.setDockSn(dockSn);
flightLog.setLevel(2);
flightLog.setMessage(msg);
influxService.addRecord(flightLog);
// 推送 MQTT 事件
JSONObject data = new JSONObject();
data.set("name", zoneName);
data.set("type", type);
data.set("msg", msg);
JSONObject payload = djiBaseService.getPayload(BusinessConstant.NOFLY_ZONE_METHOD, data);
mqttPushService.pushMessageByClient1(BusinessConstant.WEB_EVENT_TOPIC.formatted(dockSn), payload.toString());
}
}

View File

@ -0,0 +1,33 @@
package com.multictrl.modules.business.service;
import com.multictrl.common.page.PageData;
import com.multictrl.common.service.CrudService;
import com.multictrl.modules.business.dto.NoflyZoneDTO;
import com.multictrl.modules.business.entity.NoflyZoneEntity;
import java.util.List;
import java.util.Map;
/**
* 禁飞区
*
* @author Sdy
* @since 1.0.0 2026-06-09
*/
public interface NoflyZoneService extends CrudService<NoflyZoneEntity, NoflyZoneDTO> {
//分页列表
PageData<NoflyZoneDTO> pageList(Map<String, Object> params);
//新增禁飞区
void saveNoFlying(NoflyZoneDTO noflyZone);
//修改禁飞区
void updateNoFlying(NoflyZoneDTO noflyZone);
//获取机库禁飞区
List<NoflyZoneDTO> getNoFlyZoneByDockSn(String dockSn);
//判断是否在禁飞区内
Boolean isInNoFlyZone(List<List<NoflyZoneDTO.point>> noFlyZone, NoflyZoneDTO.point point);
}

View File

@ -0,0 +1,253 @@
package com.multictrl.modules.business.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.multictrl.common.constant.BusinessConstant;
import com.multictrl.common.exception.RenException;
import com.multictrl.common.page.PageData;
import com.multictrl.common.service.impl.CrudServiceImpl;
import com.multictrl.common.utils.CacheUtils;
import com.multictrl.modules.business.dao.NoflyZoneDao;
import com.multictrl.modules.business.dto.NoflyZoneDTO;
import com.multictrl.modules.business.entity.DockEntity;
import com.multictrl.modules.business.entity.NoflyZoneEntity;
import cn.hutool.core.util.StrUtil;
import com.multictrl.modules.business.service.DockService;
import com.multictrl.modules.business.service.NoflyZoneService;
import com.multictrl.modules.sys.entity.SysDeptEntity;
import com.multictrl.modules.sys.service.SysDeptService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 禁飞区
*
* @author Sdy
* @since 1.0.0 2026-06-09
*/
@Service
@RequiredArgsConstructor
public class NoflyZoneServiceImpl extends CrudServiceImpl<NoflyZoneDao, NoflyZoneEntity, NoflyZoneDTO> implements NoflyZoneService {
private final DockService dockService;
private final SysDeptService deptService;
@Override
public QueryWrapper<NoflyZoneEntity> getWrapper(Map<String, Object> params) {
String name = (String) params.get("f.name");
QueryWrapper<NoflyZoneEntity> wrapper = new QueryWrapper<>();
wrapper.like(StrUtil.isNotBlank(name), "f.name", name);
wrapper.orderByDesc("f.create_date");
return wrapper;
}
@Override
public PageData<NoflyZoneDTO> pageList(Map<String, Object> params) {
IPage<NoflyZoneDTO> page = baseDao.pageList(getPage(params, null, false), getWrapper(params));
for (NoflyZoneDTO record : page.getRecords()) {
String extentWkt = record.getExtentWkt();
List<List<NoflyZoneDTO.point>> lists = parseMultipolygonZ(extentWkt);
record.setExtent(lists);
}
return getPageData(page, currentDtoClass());
}
@Override
public void saveNoFlying(NoflyZoneDTO noflyZone) {
NoflyZoneEntity entity = new NoflyZoneEntity();
entity.setName(noflyZone.getName());
entity.setStatus(noflyZone.getStatus());
StringBuilder extent = new StringBuilder("'MULTIPOLYGON Z (");
List<List<NoflyZoneDTO.point>> lists = noflyZone.getExtent();
for (List<NoflyZoneDTO.point> list : lists) {
if (list.size() < 3) {
throw new RenException("无效的面");
}
NoflyZoneDTO.point first = list.get(0);
StringBuilder sb = new StringBuilder("((");
for (NoflyZoneDTO.point point : list) {
sb.append(point.getLng()).append(" ").append(point.getLat()).append(" ").append(point.getHeight()).append(",");
}
sb.append(first.getLng()).append(" ").append(first.getLat()).append(" ").append(first.getHeight()).append("))");
extent.append(sb).append(",");
}
extent.deleteCharAt(extent.length() - 1);
extent.append(")'");
entity.setExtent(extent.toString());
baseDao.saveNoFlyZone(entity);
}
@Override
public void updateNoFlying(NoflyZoneDTO noflyZone) {
update(null, new UpdateWrapper<NoflyZoneEntity>()
.eq("id", noflyZone.getId())
.set(StringUtils.isNotBlank(noflyZone.getStatus()), "status", noflyZone.getStatus())
.set(StringUtils.isNotBlank(noflyZone.getName()), "name", noflyZone.getName()));
}
@Override
public List<NoflyZoneDTO> getNoFlyZoneByDockSn(String dockSn) {
DockEntity dockEntity = dockService.getDao().selectOne(new QueryWrapper<DockEntity>().eq("dock_sn", dockSn));
if (dockEntity == null) {
return List.of();
}
SysDeptEntity deptEntity = deptService.selectById(dockEntity.getDeptId());
List<Long> deptIds = new ArrayList<>();
deptIds.add(dockEntity.getDeptId());
if (StringUtils.isNotBlank(deptEntity.getPids())) {
Arrays.stream(deptEntity.getPids().split(","))
.filter(StringUtils::isNotBlank)
.map(Long::parseLong)
.forEach(deptIds::add);
}
List<NoflyZoneDTO> list = baseDao.getNoFlyZoneList(new QueryWrapper<NoflyZoneEntity>()
.eq("f.status", "1").and(v ->
v.in("f.dept_id", deptIds).or().isNull("f.dept_id")));
list.forEach(dto -> dto.setExtent(parseMultipolygonZ(dto.getExtentWkt())));
CacheUtils.set(BusinessConstant.DOCK_NOFLY_ZONE + dockSn, list);
return list;
}
@Override
public Boolean isInNoFlyZone(List<List<NoflyZoneDTO.point>> noFlyZone, NoflyZoneDTO.point point) {
if (noFlyZone.size() != 2 || noFlyZone.get(0).size() < 3 || noFlyZone.get(1).size() < 3) {
throw new IllegalArgumentException("立方体必须由两个多边形面构成每个面至少3个点");
}
List<NoflyZoneDTO.point> bottomFace = noFlyZone.get(0);
List<NoflyZoneDTO.point> topFace = noFlyZone.get(1);
// 检查高度是否在立方体范围内
double minHeight = Math.min(bottomFace.get(0).getHeight(), topFace.get(0).getHeight());
double maxHeight = Math.max(bottomFace.get(0).getHeight(), topFace.get(0).getHeight());
if (point.getHeight() < minHeight || point.getHeight() > maxHeight) {
return false;
}
// 检查点是否在底面多边形内
boolean inBottomFace = isPointInPolygon(bottomFace, point);
// 检查点是否在顶面多边形内
boolean inTopFace = isPointInPolygon(topFace, point);
// 如果点在底面或顶面多边形内则它在立方体内
return inBottomFace || inTopFace;
}
/**
* 解析MULTIPOLYGON Z字符串
*
* @param multipolygonZ MULTIPOLYGON Z格式的字符串
* @return 解析后的范围数据
*/
private List<List<NoflyZoneDTO.point>> parseMultipolygonZ(String multipolygonZ) {
List<List<NoflyZoneDTO.point>> result = new ArrayList<>();
// 使用正则表达式匹配多边形数据
Pattern pattern = Pattern.compile("\\(\\(([^)]+)\\)\\)");
Matcher matcher = pattern.matcher(multipolygonZ);
while (matcher.find()) {
String polygonData = matcher.group(1);
List<NoflyZoneDTO.point> polygon = parsePolygon(polygonData);
result.add(polygon);
}
return result;
}
/**
* 解析单个多边形数据
*
* @param polygonData 多边形数据字符串
* @return 多边形点列表
*/
private List<NoflyZoneDTO.point> parsePolygon(String polygonData) {
List<NoflyZoneDTO.point> points = new ArrayList<>();
String[] coordinates = polygonData.split(",");
for (String coord : coordinates) {
NoflyZoneDTO.point point = parseCoordinate(coord.trim());
if (point != null) {
points.add(point);
}
}
// 去除最后一个点如果它与第一个点相同WKT格式多边形首尾闭合
if (points.size() > 1) {
NoflyZoneDTO.point first = points.get(0);
NoflyZoneDTO.point last = points.get(points.size() - 1);
if (first.getLng().equals(last.getLng())
&& first.getLat().equals(last.getLat())
&& first.getHeight().equals(last.getHeight())) {
points.remove(points.size() - 1);
}
}
return points;
}
/**
* 解析单个坐标点
*
* @param coordinate 坐标字符串
* @return 点对象解析失败返回null
*/
private NoflyZoneDTO.point parseCoordinate(String coordinate) {
// 使用正则表达式匹配三个浮点数
Pattern pattern = Pattern.compile("([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)");
Matcher matcher = pattern.matcher(coordinate);
if (matcher.find()) {
return new NoflyZoneDTO.point(
Double.parseDouble(matcher.group(1)),
Double.parseDouble(matcher.group(2)),
Double.parseDouble(matcher.group(3))
);
}
return null;
}
/**
* 判断点是否在多边形内射线法 Ray Casting
* <p>
* 将3D多边形投影到2D平面经纬度忽略高度
* 使用射线法判断点是否落在多边形内部
* </p>
*
* @param polygon 多边形顶点列表
* @param point 要检查的点
* @return 如果点在多边形内返回true否则返回false
*/
private boolean isPointInPolygon(List<NoflyZoneDTO.point> polygon, NoflyZoneDTO.point point) {
int n = polygon.size();
if (n < 3) return false;
double px = point.getLng();
double py = point.getLat();
boolean inside = false;
for (int i = 0, j = n - 1; i < n; j = i++) {
double xi = polygon.get(i).getLng(), yi = polygon.get(i).getLat();
double xj = polygon.get(j).getLng(), yj = polygon.get(j).getLat();
if (((yi > py) != (yj > py)) &&
(px < (xj - xi) * (py - yi) / (yj - yi) + xi)) {
inside = !inside;
}
}
return inside;
}
}

View File

@ -0,0 +1,48 @@
<?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.multictrl.modules.business.dao.NoflyZoneDao">
<resultMap type="com.multictrl.modules.business.entity.NoflyZoneEntity" id="busNoflyZoneMap">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="status" column="status"/>
<result property="extent" column="extent"/>
<result property="deptId" column="dept_id"/>
<result property="creator" column="creator"/>
<result property="createDate" column="create_date"/>
</resultMap>
<select id="pageList" resultType="com.multictrl.modules.business.dto.NoflyZoneDTO">
SELECT
f.id,
f."name",
ST_AsText(f.extent) AS extent_wkt,
f.create_date,
u.real_name user_name,
d.name dept_name,
f.status
FROM bus_nofly_zone f
left join sys_user u on f.creator = u.id
left join sys_dept d on f.dept_id = d.id
${ew.customSqlSegment}
</select>
<insert id="saveNoFlyZone">
INSERT INTO bus_nofly_zone (id, name, extent, dept_id, creator, create_date, status)
VALUES (#{id}, #{name}, ST_GeomFromText(${extent}, 4326), #{deptId}, #{creator}, #{createDate}, #{status})
</insert>
<select id="getNoFlyZoneList" resultType="com.multictrl.modules.business.dto.NoflyZoneDTO">
SELECT f.id,
f."name",
ST_AsText(f.extent) AS extent_wkt,
f.create_date,
u.real_name user_name,
d.name dept_name,
f.status
FROM bus_nofly_zone f
left join sys_user u on f.creator = u.id
left join sys_dept d on f.dept_id = d.id
${ew.customSqlSegment}
</select>
</mapper>

View File

@ -2264,3 +2264,34 @@ VALUES (2063906540177379330, 2063906301022359553, '妙算', 'MIAO_SUAN', '', 1,
'2026-06-08 16:50:39.364', 1067246875800000001, '2026-06-08 16:52:08.349',
'source-material/miao_suan.png');
CREATE
EXTENSION IF NOT EXISTS postgis;
DROP TABLE IF EXISTS "public"."bus_nofly_zone";
CREATE TABLE "public"."bus_nofly_zone"
(
"id" int8 NOT NULL,
"name" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
"status" char(1) COLLATE "pg_catalog"."default",
"extent" "public"."geography" NOT NULL,
"dept_id" int8,
"creator" int8,
"create_date" timestamp(6)
)
;
COMMENT
ON COLUMN "public"."bus_nofly_zone"."extent" IS '范围';
COMMENT
ON COLUMN "public"."bus_nofly_zone"."dept_id" IS '部门标识';
COMMENT
ON COLUMN "public"."bus_nofly_zone"."name" IS '名称';
COMMENT
ON COLUMN "public"."bus_nofly_zone"."status" IS '是否开启 1开启 0未开启';
COMMENT
ON TABLE "public"."bus_nofly_zone" IS '禁飞区';
-- ----------------------------
-- Primary Key structure for table bus_nofly_zone
-- ----------------------------
ALTER TABLE "public"."bus_nofly_zone"
ADD CONSTRAINT "uav_no_flying_pkey" PRIMARY KEY ("id");