增加航空器路由接口的模拟和对接
This commit is contained in:
parent
1efb1e6b7b
commit
462bf3dc77
@ -1,6 +1,6 @@
|
||||
# QAUP-Management 平台管理系统
|
||||
|
||||
基于若依框架的综合管理平台,集成了机场冲突避免系统,提供完整的车辆管理、空间分析、实时监控等功能。
|
||||
机场无人车冲突管理平台,集成了机场冲突避免系统,提供完整的车辆管理、空间分析、实时监控等功能。
|
||||
|
||||
## 项目架构
|
||||
|
||||
@ -138,5 +138,5 @@ java -jar target/qaup-admin.jar
|
||||
3. 使用`QuapDataAdapter`获取车辆和司机数据,避免直接访问DAO
|
||||
|
||||
## 版本信息
|
||||
- 当前版本: 3.8.9
|
||||
- 当前版本: 1.0.1
|
||||
- 更新日志: 详见 `change_log.md`
|
||||
@ -1 +1 @@
|
||||
0.3.8
|
||||
0.3.9
|
||||
78
changelog.md
78
changelog.md
@ -5,6 +5,84 @@
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。
|
||||
版本规范基于 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
|
||||
|
||||
## [0.3.9] - 2025-07-15
|
||||
|
||||
### 🚀 **新功能:CA3456航空器生命周期模拟集成**
|
||||
|
||||
- **航空器路由和状态数据采集**:
|
||||
- 新增 `collectAircraftRouteAndStatus()` 方法,每5秒采集航空器状态和路由信息
|
||||
- 支持根据航空器状态(IN/OUT/ARRIVED)动态获取对应路由数据
|
||||
- 实现 `AircraftRouteDTO` 和 `AircraftStatusDTO` 数据传输对象
|
||||
|
||||
- **航空器路由模型扩展**:
|
||||
- 为 `Aircraft` 类新增 `arrivalRoute`、`departureRoute` 和 `currentRoute` 字段
|
||||
- 添加 `activateArrivalRoute()` 和 `activateDepartureRoute()` 便捷方法
|
||||
- 支持路由时间戳和状态跟踪
|
||||
|
||||
- **Mock API服务完善**:
|
||||
- 实现CA3456状态循环模拟:进港(30秒) → 停留(60秒) → 出港(30秒) → 循环
|
||||
- 新增4个API端点:登录、航空器状态、进港路由、出港路由
|
||||
- 改进认证机制,支持多种参数传递方式(query、form、JSON)
|
||||
|
||||
### 🔌 **WebSocket实时推送增强**
|
||||
|
||||
- **新增航空器路由更新事件**:
|
||||
- 创建 `AircraftRouteUpdateEvent` 事件类型
|
||||
- 实现 `AircraftRouteUpdateEventListener` 事件监听器
|
||||
- 支持航空器路由变更的实时WebSocket推送
|
||||
|
||||
- **事件推送优化**:
|
||||
- 路由更新事件包含完整的航班号、路由类型、状态和几何数据
|
||||
- 统一事件格式,便于前端处理
|
||||
|
||||
### 🔧 **数据采集服务重构**
|
||||
|
||||
- **新增路由数据采集任务**:
|
||||
- 配置项:`data.collector.route.interval: 5000`(5秒间隔)
|
||||
- 支持进港和出港路由的智能切换
|
||||
- 完整的DTO到模型对象转换
|
||||
|
||||
- **数据转换优化**:
|
||||
- 实现 `convertToAircraftRoute()` 方法,支持GeoJSON到JTS几何对象转换
|
||||
- 坐标格式转换:`List<List<Double>>` → `List<Point>`
|
||||
- 路由段数据结构完善
|
||||
|
||||
### 🛠️ **技术债务清理**
|
||||
|
||||
- **MovingObjectType枚举修复**:
|
||||
- 移除已废弃的 `AIRPORT_VEHICLE` 枚举常量
|
||||
- 更新数据库规则配置,使用 `SPECIAL_VEHICLE` 替代
|
||||
- 修复 `VehicleTypePermissionServiceImpl` 中的枚举不匹配错误
|
||||
|
||||
- **Mock服务认证优化**:
|
||||
- 改进 `check_auth()` 函数,支持Bearer token验证
|
||||
- 添加调试日志,便于问题排查
|
||||
- 修复token格式验证逻辑
|
||||
|
||||
### 📊 **系统集成测试**
|
||||
|
||||
- **端到端测试验证**:
|
||||
- CA3456状态循环正常:IN → ARRIVED → OUT → IN(122秒周期)
|
||||
- 路由API正常返回:进港路由(F1,L4,138)、出港路由(138,L4,F1)
|
||||
- WebSocket事件推送正常:航空器路由更新事件成功广播
|
||||
|
||||
- **数据采集验证**:
|
||||
- 每5秒成功采集航空器状态和路由数据
|
||||
- 实时数据缓存更新正常
|
||||
- 事件发布和监听机制工作正常
|
||||
|
||||
### 🔧 **配置文件更新**
|
||||
|
||||
- **application.yml配置扩展**:
|
||||
- 新增 `data.collector.route.interval` 配置项
|
||||
- 支持路由数据采集间隔自定义
|
||||
|
||||
### 📝 **文档更新**
|
||||
|
||||
- **SQL脚本新增**:
|
||||
- 创建 `fix_vehicle_type_enum.sql` 修复枚举不匹配问题
|
||||
- 提供完整的数据库修复方案
|
||||
|
||||
## [0.3.8] - 2025-07-14
|
||||
|
||||
### 🐛 **关键Bug修复:路径冲突检测服务NullPointerException**
|
||||
|
||||
232
doc/design/vehicle-trajectory-api.md
Normal file
232
doc/design/vehicle-trajectory-api.md
Normal file
@ -0,0 +1,232 @@
|
||||
# 车辆轨迹查询和回放API文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了车辆轨迹查询和回放功能的API接口,支持无人车轨迹回放和车辆行驶信息轨迹回放需求。
|
||||
|
||||
## 接口列表
|
||||
|
||||
### 1. 车辆轨迹查询接口
|
||||
|
||||
**接口地址:** `GET /system/vehicle_location/trajectory/{vehicleId}`
|
||||
|
||||
**功能描述:** 查询指定车辆在指定时间段内的运行轨迹,支持数据简化和采样优化
|
||||
|
||||
**请求参数:**
|
||||
- `vehicleId` (路径参数): 车辆ID
|
||||
- `startTime` (查询参数): 开始时间,格式:yyyy-MM-dd HH:mm:ss
|
||||
- `endTime` (查询参数): 结束时间,格式:yyyy-MM-dd HH:mm:ss
|
||||
- `simplified` (查询参数,可选): 是否简化数据,默认false
|
||||
- `maxPoints` (查询参数,可选): 最大轨迹点数量,默认1000
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"vehicleId": 1,
|
||||
"licensePlate": "测A12345",
|
||||
"vehicleType": "无人车",
|
||||
"brand": "品牌A",
|
||||
"owningUnit": "测试单位",
|
||||
"startTime": "2025-01-17 08:00:00",
|
||||
"endTime": "2025-01-17 18:00:00",
|
||||
"statistics": {
|
||||
"pointCount": 360,
|
||||
"totalDistance": 15.68,
|
||||
"averageSpeed": 25.5,
|
||||
"maxSpeed": 45.2,
|
||||
"durationSeconds": 3600,
|
||||
"durationFormatted": "1小时0分钟"
|
||||
},
|
||||
"points": [
|
||||
{
|
||||
"longitude": 116.397128,
|
||||
"latitude": 39.916527,
|
||||
"altitude": 45.5,
|
||||
"speed": 25.8,
|
||||
"heading": 135.5,
|
||||
"timestamp": "2025-01-17 08:00:00",
|
||||
"dataQuality": "HIGH"
|
||||
}
|
||||
],
|
||||
"simplified": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 轨迹回放数据接口
|
||||
|
||||
**接口地址:** `GET /system/vehicle_location/trajectory/{vehicleId}/playback`
|
||||
|
||||
**功能描述:** 获取用于轨迹回放的数据,按指定时间间隔采样,包含回放所需的序列和时间信息
|
||||
|
||||
**请求参数:**
|
||||
- `vehicleId` (路径参数): 车辆ID
|
||||
- `startTime` (查询参数): 开始时间,格式:yyyy-MM-dd HH:mm:ss
|
||||
- `endTime` (查询参数): 结束时间,格式:yyyy-MM-dd HH:mm:ss
|
||||
- `intervalSeconds` (查询参数,可选): 时间间隔(秒),默认10
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"vehicleId": 1,
|
||||
"licensePlate": "测A12345",
|
||||
"sequenceNumber": 1,
|
||||
"longitude": 116.397128,
|
||||
"latitude": 39.916527,
|
||||
"speed": 25.8,
|
||||
"timestamp": "2025-01-17 08:00:00",
|
||||
"intervalMs": 0,
|
||||
"cumulativeDistance": 0.0,
|
||||
"progressPercentage": 0.0,
|
||||
"isKeyFrame": true,
|
||||
"keyFrameType": "START",
|
||||
"suggestedDelay": 1000
|
||||
},
|
||||
{
|
||||
"vehicleId": 1,
|
||||
"licensePlate": "测A12345",
|
||||
"sequenceNumber": 2,
|
||||
"longitude": 116.398128,
|
||||
"latitude": 39.917527,
|
||||
"speed": 30.0,
|
||||
"timestamp": "2025-01-17 08:00:10",
|
||||
"intervalMs": 10000,
|
||||
"cumulativeDistance": 125.6,
|
||||
"progressPercentage": 25.5,
|
||||
"isKeyFrame": false,
|
||||
"suggestedDelay": 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 多车辆轨迹查询接口
|
||||
|
||||
**接口地址:** `POST /system/vehicle_location/trajectory/batch`
|
||||
|
||||
**功能描述:** 批量查询多个车辆的运行轨迹,支持复杂查询条件
|
||||
|
||||
**请求体示例:**
|
||||
```json
|
||||
{
|
||||
"vehicleIds": [1, 2, 3],
|
||||
"startTime": "2025-01-17 08:00:00",
|
||||
"endTime": "2025-01-17 18:00:00",
|
||||
"simplified": false,
|
||||
"maxPoints": 1000,
|
||||
"includeStatistics": true,
|
||||
"includeGeoJson": false
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 区域内轨迹查询接口
|
||||
|
||||
**接口地址:** `GET /system/vehicle_location/trajectory/{vehicleId}/area`
|
||||
|
||||
**功能描述:** 查询车辆在指定区域内的运行轨迹
|
||||
|
||||
**请求参数:**
|
||||
- `vehicleId` (路径参数): 车辆ID
|
||||
- `startTime` (查询参数): 开始时间
|
||||
- `endTime` (查询参数): 结束时间
|
||||
- `areaWkt` (查询参数): 区域WKT格式字符串
|
||||
|
||||
### 5. 轨迹统计信息查询接口
|
||||
|
||||
**接口地址:** `GET /system/vehicle_location/trajectory/{vehicleId}/statistics`
|
||||
|
||||
**功能描述:** 获取车辆轨迹的统计信息,如总距离、平均速度、行驶时长等
|
||||
|
||||
**请求参数:**
|
||||
- `vehicleId` (路径参数): 车辆ID
|
||||
- `startTime` (查询参数): 开始时间
|
||||
- `endTime` (查询参数): 结束时间
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 轨迹查询 vs 轨迹回放
|
||||
|
||||
1. **轨迹查询接口** 适用于:
|
||||
- 静态轨迹展示
|
||||
- 轨迹分析
|
||||
- 数据导出
|
||||
- 历史轨迹查看
|
||||
|
||||
2. **轨迹回放接口** 适用于:
|
||||
- 动画回放
|
||||
- 实时模拟
|
||||
- 时间轴播放
|
||||
- 交互式回放控制
|
||||
|
||||
## 技术特性
|
||||
|
||||
- **智能采样**:根据时间间隔和点数限制自动优化轨迹数据
|
||||
- **统计计算**:利用PostGIS计算精确的距离和轨迹统计
|
||||
- **回放优化**:提供专门的回放数据格式,支持前端动画
|
||||
- **性能优化**:通过索引和查询优化支持大数据量轨迹查询
|
||||
- **空间查询**:支持区域内轨迹过滤功能
|
||||
|
||||
## 错误码
|
||||
|
||||
- `200`: 操作成功
|
||||
- `400`: 参数错误
|
||||
- `404`: 数据不存在
|
||||
- `500`: 服务器内部错误
|
||||
|
||||
|
||||
## 测试命令
|
||||
|
||||
1. 轨迹查询接口
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/system/vehicle_location/trajectory/5?startTime=2025-01-01%2000:00:00&endTime=2025-10-17%2023:59:59&simplified=false&maxPoints=100" \
|
||||
-H "Content-Type: application/json" \
|
||||
-w "\nHTTP Status: %{http_code}\n"
|
||||
```
|
||||
|
||||
2. 轨迹回放接口
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/system/vehicle_location/trajectory/5/playback?startTime=2025-01-01%2000:00:00&endTime=2025-10-17%2023:59:59&intervalSeconds=10" \
|
||||
-H "Content-Type: application/json" \
|
||||
-w "\nHTTP Status: %{http_code}\n"
|
||||
```
|
||||
|
||||
3. 轨迹统计信息接口
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/system/vehicle_location/trajectory/5/statistics?startTime=2025-01-01%2000:00:00&endTime=2025-10-17%2023:59:59" \
|
||||
-H "Content-Type: application/json" \
|
||||
-w "\nHTTP Status: %{http_code}\n"
|
||||
```
|
||||
|
||||
4. 批量轨迹查询接口
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/system/vehicle_location/trajectory/batch" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"vehicleIds": [5, 6],
|
||||
"startTime": "2025-01-01 00:00:00",
|
||||
"endTime": "2025-10-17 23:59:59",
|
||||
"simplified": false,
|
||||
"maxPoints": 100,
|
||||
"includeStatistics": true
|
||||
}' \
|
||||
-w "\nHTTP Status: %{http_code}\n"
|
||||
```
|
||||
|
||||
5. 区域内轨迹查询接口
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/system/vehicle_location/trajectory/5/area?startTime=2025-01-01%2000:00:00&endTime=2025-10-17%2023:59:59&areaWkt=POLYGON((116.3%2039.9,116.4%2039.9,116.4%2040.0,116.3%2040.0,116.3%2039.9))" \
|
||||
-H "Content-Type: application/json" \
|
||||
-w "\nHTTP Status: %{http_code}\n"
|
||||
```
|
||||
@ -7,6 +7,252 @@ QAUP是一个集成的机场车辆管理系统,包含:
|
||||
- **实时位置监控**:基于PostGIS的空间数据管理
|
||||
- **碰撞避免系统**:实时安全规则检测和预警
|
||||
- **轨迹管理**:车辆历史路径记录和分析
|
||||
- **航空器生命周期模拟**:CA3456航空器状态和路由模拟系统
|
||||
|
||||
## API接口测试
|
||||
|
||||
### 航空器生命周期模拟API
|
||||
|
||||
系统提供了完整的航空器生命周期模拟API,支持CA3456航空器的状态循环和路由管理。
|
||||
|
||||
#### 1. 登录认证
|
||||
```bash
|
||||
# 基本登录(推荐)
|
||||
curl -X POST "http://localhost:8090/login?username=dianxin&password=dianxin@123"
|
||||
|
||||
# 使用表单数据登录
|
||||
curl -X POST "http://localhost:8090/login" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "username=dianxin&password=dianxin@123"
|
||||
|
||||
# 使用JSON数据登录
|
||||
curl -X POST "http://localhost:8090/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"dianxin","password":"dianxin@123"}'
|
||||
```
|
||||
|
||||
**预期响应:**
|
||||
```json
|
||||
{
|
||||
"data": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY",
|
||||
"msg": "登入成功",
|
||||
"status": 200
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 航空器状态查询
|
||||
```bash
|
||||
# 查询CA3456当前状态
|
||||
curl -X GET "http://localhost:8090/aircraftStatusController/getAircraftStatus" \
|
||||
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY"
|
||||
```
|
||||
|
||||
**预期响应:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"contactCross": "F1",
|
||||
"flightNo": "CA3456",
|
||||
"inRunway": "35",
|
||||
"outRunway": "34",
|
||||
"seat": "138",
|
||||
"timestamp": 1752567616199,
|
||||
"type": "IN" // 状态: IN(进港), ARRIVED(到达), OUT(出港)
|
||||
},
|
||||
"msg": "航空器状态查询成功",
|
||||
"status": 200
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 进港路由查询
|
||||
```bash
|
||||
# 查询进港滑行路线
|
||||
curl -X GET "http://localhost:8090/runwayPathPlanningController/findArrTaxiwayByRunwayAndContactCrossAndSeat?inRunway=35&outRunway=34&contactCross=F1&seat=138" \
|
||||
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY"
|
||||
```
|
||||
|
||||
**预期响应:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "IN",
|
||||
"status": "COMPLETE",
|
||||
"codes": "F1,L4,138",
|
||||
"geometry": null,
|
||||
"geoPath": {
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[120.086263, 36.370484],
|
||||
[120.085000, 36.370000],
|
||||
[120.084000, 36.369500],
|
||||
[120.083000, 36.369000],
|
||||
[120.082000, 36.368500],
|
||||
[120.081000, 36.368000],
|
||||
[120.080996, 36.369105]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "L4"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"msg": "进港滑行路线查询成功",
|
||||
"status": 200
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 出港路由查询
|
||||
```bash
|
||||
# 查询出港滑行路线
|
||||
curl -X GET "http://localhost:8090/runwayPathPlanningController/findDepTaxiwayByRunwayAndContactCrossAndSeat?inRunway=35&outRunway=34&startSeat=138" \
|
||||
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY"
|
||||
```
|
||||
|
||||
**预期响应:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"type": "OUT",
|
||||
"status": "COMPLETE",
|
||||
"codes": "138,L4,F1",
|
||||
"geometry": null,
|
||||
"geoPath": {
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[120.080996, 36.369105],
|
||||
[120.081000, 36.368000],
|
||||
[120.082000, 36.368500],
|
||||
[120.083000, 36.369000],
|
||||
[120.084000, 36.369500],
|
||||
[120.085000, 36.370000],
|
||||
[120.086263, 36.370484]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "L4"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"msg": "出港滑行路线查询成功",
|
||||
"status": 200
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. 状态循环测试
|
||||
```bash
|
||||
# 连续监控CA3456状态变化
|
||||
for i in {1..6}; do
|
||||
echo "=== 第 $i 次调用 ($(date +%H:%M:%S)) ==="
|
||||
curl -s -X GET "http://localhost:8090/aircraftStatusController/getAircraftStatus" \
|
||||
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY" \
|
||||
| python3 -c "import sys,json; data=json.load(sys.stdin); print(f'状态: {data[\"data\"][\"type\"]}, 航班号: {data[\"data\"][\"flightNo\"]}, 时间戳: {data[\"data\"][\"timestamp\"]}')"
|
||||
sleep 15
|
||||
done
|
||||
```
|
||||
|
||||
**预期输出:**
|
||||
```
|
||||
=== 第 1 次调用 (16:22:45) ===
|
||||
状态: IN, 航班号: CA3456, 时间戳: 1752567765233
|
||||
=== 第 2 次调用 (16:23:00) ===
|
||||
状态: IN, 航班号: CA3456, 时间戳: 1752567780372
|
||||
=== 第 3 次调用 (16:23:15) ===
|
||||
状态: ARRIVED, 航班号: CA3456, 时间戳: 1752567795496
|
||||
=== 第 4 次调用 (16:23:30) ===
|
||||
状态: ARRIVED, 航班号: CA3456, 时间戳: 1752567810629
|
||||
=== 第 5 次调用 (16:23:45) ===
|
||||
状态: IN, 航班号: CA3456, 时间戳: 1752567825769
|
||||
=== 第 6 次调用 (16:24:00) ===
|
||||
状态: IN, 航班号: CA3456, 时间戳: 1752567840897
|
||||
```
|
||||
|
||||
#### 6. 路由API简化测试
|
||||
```bash
|
||||
# 简化的路由测试
|
||||
echo "=== 测试进港路由 ==="
|
||||
curl -s -X GET "http://localhost:8090/runwayPathPlanningController/findArrTaxiwayByRunwayAndContactCrossAndSeat?inRunway=35&outRunway=34&contactCross=F1&seat=138" \
|
||||
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY" \
|
||||
| python3 -c "import sys,json; data=json.load(sys.stdin); print(f'路由类型: {data[\"data\"][\"type\"]}, 编码: {data[\"data\"][\"codes\"]}, 状态: {data[\"data\"][\"status\"]}')"
|
||||
|
||||
echo "=== 测试出港路由 ==="
|
||||
curl -s -X GET "http://localhost:8090/runwayPathPlanningController/findDepTaxiwayByRunwayAndContactCrossAndSeat?inRunway=35&outRunway=34&startSeat=138" \
|
||||
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY" \
|
||||
| python3 -c "import sys,json; data=json.load(sys.stdin); print(f'路由类型: {data[\"data\"][\"type\"]}, 编码: {data[\"data\"][\"codes\"]}, 状态: {data[\"data\"][\"status\"]}')"
|
||||
```
|
||||
|
||||
### CA3456生命周期说明
|
||||
|
||||
CA3456航空器按照以下周期进行状态模拟:
|
||||
|
||||
1. **进港阶段(30秒)**:`type = "IN"`
|
||||
- 航空器正在进场,执行进港滑行
|
||||
- 使用进港路由:F1,L4,138
|
||||
|
||||
2. **停留阶段(60秒)**:`type = "ARRIVED"`
|
||||
- 航空器已到达机位,进行客货作业
|
||||
- 停留在138机位
|
||||
|
||||
3. **出港阶段(30秒)**:`type = "OUT"`
|
||||
- 航空器开始出港,执行出港滑行
|
||||
- 使用出港路由:138,L4,F1
|
||||
|
||||
4. **循环周期**:总计122秒(包含2秒状态切换时间)
|
||||
|
||||
### WebSocket连接测试
|
||||
|
||||
系统支持WebSocket实时推送,可以通过以下方式连接:
|
||||
|
||||
```javascript
|
||||
// WebSocket连接示例
|
||||
const ws = new WebSocket('ws://localhost:8080/ws');
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'aircraftRouteUpdate') {
|
||||
console.log('收到航空器路由更新:', data.data);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 故障排除
|
||||
|
||||
#### 常见错误
|
||||
|
||||
1. **认证失败(401)**:
|
||||
- 检查token是否正确
|
||||
- 确认Authorization header格式:`Bearer <token>`
|
||||
|
||||
2. **连接超时**:
|
||||
- 检查Mock服务是否运行在端口8090
|
||||
- 确认Java服务是否运行在端口8080
|
||||
|
||||
3. **状态不变化**:
|
||||
- 等待完整的状态周期(122秒)
|
||||
- 检查Mock服务日志
|
||||
|
||||
#### 服务状态检查
|
||||
|
||||
```bash
|
||||
# 检查Mock服务状态
|
||||
curl -I http://localhost:8090/login
|
||||
|
||||
# 检查Java服务状态
|
||||
curl -I http://localhost:8080/actuator/health
|
||||
```
|
||||
|
||||
## 核心数据结构
|
||||
|
||||
30
doc/requirement/aircraft_status_api.md
Normal file
30
doc/requirement/aircraft_status_api.md
Normal file
@ -0,0 +1,30 @@
|
||||
## 航空器状态接口(模拟)
|
||||
|
||||
### 1.1 航空器进出港状态
|
||||
|
||||
1.1.1 接口地址:http://IP:端口/aircraftStatusController/getAircraftStatus
|
||||
|
||||
1.1.2 请求方式:get
|
||||
|
||||
1.1.3 请求参数:
|
||||
|
||||
1.1.4 返回格式:json
|
||||
|
||||
type:进出港类型 IN:进港 OUT:出港
|
||||
flightNo:航班号 CA3456
|
||||
inRunway :进港跑道编号 35
|
||||
outRunway:出港跑道编号 34
|
||||
contactCross:联络道口 F1
|
||||
seat:目的机位 138
|
||||
timestamp:时间戳 1715769600000
|
||||
|
||||
示例:
|
||||
{
|
||||
"type": "IN",
|
||||
"flightNo": "CA3456",
|
||||
"inRunway": "35",
|
||||
"outRunway": "34",
|
||||
"contactCross": "F1",
|
||||
"seat": "138",
|
||||
"timestamp": 1715769600000
|
||||
}
|
||||
10
doc/requirement/area.md
Normal file
10
doc/requirement/area.md
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
## 区域设置(测试)
|
||||
|
||||
### 电子围栏
|
||||
|
||||
- 无人车 A(B567)
|
||||
POLYGON ((120.08483597765024 36.36489863472901, 120.08549104408633 36.36594996564427, 120.08416649728491 36.36673180599232, 120.08378219088705 36.36561378252756, 120.08483597765024 36.36489863472901))
|
||||
|
||||
- 无人车 B (B579)
|
||||
POLYGON ((120.08646134220226 36.36997466676866, 120.086830266589 36.37063980458865, 120.08515882034526 36.37156622537344, 120.08543839423878 36.37073891647525, 120.0851419887492 36.370416189872486, 120.0858980160674 36.36986114517269, 120.08646134220226 36.36997466676866))
|
||||
@ -2,6 +2,54 @@
|
||||
|
||||
## 需求列表(按时间跟踪)
|
||||
|
||||
### 2025-06-03
|
||||
|
||||
- 需求:无人车轨迹回放
|
||||
- 分析:
|
||||
- 需要对无人车的运行轨迹进行查询和显示
|
||||
- 功能模块:
|
||||
- 数据存储模块:存储无人车的位置变更数据(具备)
|
||||
- 数据处理模块:根据无人车的位置历史数据,提供轨迹查询API(新增)
|
||||
- 前端展示模块:对无人车的运行轨迹进行查询和显示(新增)
|
||||
|
||||
### 2025-06-02
|
||||
|
||||
- 需求:无人车路由导入
|
||||
- 分析:
|
||||
- 需要对无人车的路由(文件形式)进行导入
|
||||
- 需要对无人车的路由列表进行查询和显示
|
||||
- 功能模块:
|
||||
- 后台管理模块:导入无人车的路由列表(新增)并提供查询接口
|
||||
- 数据存储模块:存储无人车的路由列表(新增)
|
||||
- 技术细节:
|
||||
- 路由数据结构(编号、名称、路径地理信息、生效时间段、优先级等)
|
||||
- 导入功能:支持文件上传和解析(CSV、JSON等格式)
|
||||
- 查询功能:提供分页查询和条件过滤
|
||||
- 显示功能:在前端页面展示路由列表和详情
|
||||
|
||||
### 2025-06-01
|
||||
|
||||
- 需求:采集和显示无人车的运行状态
|
||||
- 分析:
|
||||
- 需要获取无人车的运行状态数据(当前路径、运行时间、运行距离、电池电量等)
|
||||
- 需要对无人车的运行状态进行显示
|
||||
- 功能模块:
|
||||
- 数据采集模块:获取无人车的运行状态数据(新增)
|
||||
- 数据存储模块:存储无人车的运行状态数据(新增)
|
||||
- 数据处理模块:根据无人车的运行状态数据,发送websocket无人车状态消息到前端(新增)
|
||||
|
||||
### 2025-05-01
|
||||
|
||||
- 需求:基于路径的碰撞预警,当航空器或机场车辆的路径与无人车路径之间可能发生碰撞时,进行预警或报警
|
||||
- 分析:
|
||||
- 需要获取车辆的实时位置数据
|
||||
- 需要获取航空器和车辆的路径数据
|
||||
- 需要对车辆进行碰撞避免预警
|
||||
- 功能模块:
|
||||
- 数据采集模块:获取车辆的实时位置数据(具备)和路径数据(新增)
|
||||
- 数据存储模块:存储车辆的实时位置数据和路径数据(增加路径数据)
|
||||
- 数据处理模块:根据车辆的实时位置数据,对车辆进行碰撞避免预警(新增)
|
||||
|
||||
### 2025-05-01
|
||||
|
||||
- 需求:
|
||||
|
||||
895
doc/requirement/route_api.md
Normal file
895
doc/requirement/route_api.md
Normal file
@ -0,0 +1,895 @@
|
||||
## 航空器滑行路由接口
|
||||
|
||||
### 1.1.登录认证
|
||||
1、登录接口:http://IP:端口/login
|
||||
2、请求方式:post
|
||||
3、参数:username、password
|
||||
4、示例:127.0.0.1:8080/login?username=XXXX&password=XXXX
|
||||
5、返回值 data 为返回的鉴权token,后续接口需要再header中携带,data所有的数据是一个token,不要截断
|
||||
示例:{
|
||||
"status": 200,
|
||||
"msg": "登入成功",
|
||||
"data": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY"
|
||||
}
|
||||
|
||||
### 1.2.Token续时
|
||||
1、登录接口:http://IP:端口/userInfoController/refreshToken
|
||||
2、请求方式:get
|
||||
3、参数:header 携带登录返回的token
|
||||
4、示例:127.0.0.1:8080/userInfoController/refreshToken
|
||||
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzI3ODMwOTAsInVzZXJuYW1lIjoiYWRtaW4ifQ.y9feEL_9NT8UzED9NNkb0Ln6C-PBoufiSHWobWe5vWY
|
||||
|
||||
5、返回值 data 为返回的鉴权token,后续接口需要再header中携带,data所有的数据是一个token,不要截断
|
||||
示例:{
|
||||
"status": 200,
|
||||
"msg": "OK",
|
||||
"data": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzQ0OTA0NTcsInVzZXJuYW1lIjoiZGlhbnhpbiJ9.1Ss3Gd6ijjmUmVW2DFwQF7lsdUwlB0m1TP73zf-B5mk"
|
||||
}
|
||||
|
||||
### 1.3.航空器进港滑行路线
|
||||
1、接口地址: http://IP:端口/runwayPathPlanningController/findArrTaxiwayByRunwayAndContactCrossAndSeat
|
||||
2、请求方式: get
|
||||
3、返回格式:以对象形式数据返回
|
||||
4、请求接口示例
|
||||
参数:
|
||||
inRunway :进港跑道编号 35
|
||||
outRunway:出港跑道编号 34
|
||||
contactCross:联络道口 F1
|
||||
seat:目的机位 138
|
||||
|
||||
5、数据结构
|
||||
type:进出港类型
|
||||
status:接口状态
|
||||
codes:滑行线编码
|
||||
geoPath:返回路线
|
||||
{
|
||||
type:JeoJson集合
|
||||
geometry:返回的路线JeoJson集合,前端直接绘制即可,拿出features直接绘制JeoJson即可
|
||||
}
|
||||
|
||||
示例:
|
||||
{
|
||||
"type": "IN",
|
||||
"status": "COMPLETE",
|
||||
"codes": "F1,L4,138",
|
||||
"geometry": null,
|
||||
"geoPath": {
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050742275893088E7,
|
||||
4026164.644604296
|
||||
],
|
||||
[
|
||||
4.050742342874898E7,
|
||||
4026162.545793306
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "L4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050743615407222E7,
|
||||
4026122.672208275
|
||||
],
|
||||
[
|
||||
4.050743684026714E7,
|
||||
4026120.146600441
|
||||
],
|
||||
[
|
||||
4.050743730372977E7,
|
||||
4026117.570797326
|
||||
],
|
||||
[
|
||||
4.050743754093282E7,
|
||||
4026114.964402468
|
||||
],
|
||||
[
|
||||
4.050743757419489E7,
|
||||
4026113.602043673
|
||||
],
|
||||
[
|
||||
4.050743755007106E7,
|
||||
4026112.347252104
|
||||
],
|
||||
[
|
||||
4.050743733107493E7,
|
||||
4026109.739264329
|
||||
],
|
||||
[
|
||||
4.050743688561112E7,
|
||||
4026107.160287504
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "L4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050717462298063E7,
|
||||
4026091.904402129
|
||||
],
|
||||
[
|
||||
4.050716820216861E7,
|
||||
4026089.855066455
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050722536188381E7,
|
||||
4026108.097315812
|
||||
],
|
||||
[
|
||||
4.050720821283463E7,
|
||||
4026102.624334418
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050727144214725E7,
|
||||
4026112.527790001
|
||||
],
|
||||
[
|
||||
4.050726278505515E7,
|
||||
4026114.415332655
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050731882638656E7,
|
||||
4026102.196402456
|
||||
],
|
||||
[
|
||||
4.050727312768086E7,
|
||||
4026112.160285922
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050738651815705E7,
|
||||
4026087.437277401
|
||||
],
|
||||
[
|
||||
4.050734647450486E7,
|
||||
4026096.168165339
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050714461981621E7,
|
||||
4026082.328947974
|
||||
],
|
||||
[
|
||||
4.05071119278174E7,
|
||||
4026071.895744022
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050734647450486E7,
|
||||
4026096.168165339
|
||||
],
|
||||
[
|
||||
4.050733913391775E7,
|
||||
4026097.768664928
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050689454491971E7,
|
||||
4026002.519737061
|
||||
],
|
||||
[
|
||||
4.050693265139649E7,
|
||||
4026014.681113256
|
||||
],
|
||||
[
|
||||
4.050697075787329E7,
|
||||
4026026.842489458
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050741162298967E7,
|
||||
4026083.825606086
|
||||
],
|
||||
[
|
||||
4.050741416963529E7,
|
||||
4026084.285112275
|
||||
],
|
||||
[
|
||||
4.050741669524226E7,
|
||||
4026084.971307588
|
||||
],
|
||||
[
|
||||
4.050741915143272E7,
|
||||
4026085.875012957
|
||||
],
|
||||
[
|
||||
4.050742151951354E7,
|
||||
4026086.989350639
|
||||
],
|
||||
[
|
||||
4.050742378146222E7,
|
||||
4026088.305839852
|
||||
],
|
||||
[
|
||||
4.050742592006397E7,
|
||||
4026089.814461317
|
||||
],
|
||||
[
|
||||
4.050742791904272E7,
|
||||
4026091.503733515
|
||||
],
|
||||
[
|
||||
4.050742976318505E7,
|
||||
4026093.360800063
|
||||
],
|
||||
[
|
||||
4.050743143845592E7,
|
||||
4026095.371527565
|
||||
],
|
||||
[
|
||||
4.050743293210549E7,
|
||||
4026097.52061317
|
||||
],
|
||||
[
|
||||
4.050743423276621E7,
|
||||
4026099.791701039
|
||||
],
|
||||
[
|
||||
4.050743533053925E7,
|
||||
4026102.167506821
|
||||
],
|
||||
[
|
||||
4.05074362170699E7,
|
||||
4026104.629949201
|
||||
],
|
||||
[
|
||||
4.050743683431807E7,
|
||||
4026106.966150228
|
||||
],
|
||||
[
|
||||
4.050743688561112E7,
|
||||
4026107.160287504
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050697552118288E7,
|
||||
4026028.362661481
|
||||
],
|
||||
[
|
||||
4.050697075787329E7,
|
||||
4026026.842489458
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050704221346159E7,
|
||||
4026049.646966941
|
||||
],
|
||||
[
|
||||
4.050703137036284E7,
|
||||
4026046.18647901
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050708746004742E7,
|
||||
4026064.087051627
|
||||
],
|
||||
[
|
||||
4.050704840096232E7,
|
||||
4026051.621473066
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.05071119278174E7,
|
||||
4026071.895744022
|
||||
],
|
||||
[
|
||||
4.050710556055213E7,
|
||||
4026069.863682419
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050741939071107E7,
|
||||
4026175.198599438
|
||||
],
|
||||
[
|
||||
4.05074216811156E7,
|
||||
4026168.021835575
|
||||
],
|
||||
[
|
||||
4.050742275893088E7,
|
||||
4026164.644604296
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "L4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050753774654577E7,
|
||||
4026246.448261945
|
||||
],
|
||||
[
|
||||
4.050749515081406E7,
|
||||
4026236.848849251
|
||||
],
|
||||
[
|
||||
4.050744870329395E7,
|
||||
4026226.381394062
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "138"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050753774654577E7,
|
||||
4026246.448261945
|
||||
],
|
||||
[
|
||||
4.0507613391983E7,
|
||||
4026263.495786141
|
||||
],
|
||||
[
|
||||
4.05076192451935E7,
|
||||
4026264.814870958
|
||||
],
|
||||
[
|
||||
4.050762119626365E7,
|
||||
4026265.254565894
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "138"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050742342874898E7,
|
||||
4026162.545793306
|
||||
],
|
||||
[
|
||||
4.050743615407222E7,
|
||||
4026122.672208275
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "L4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050716820216861E7,
|
||||
4026089.855066455
|
||||
],
|
||||
[
|
||||
4.050714461981621E7,
|
||||
4026082.328947974
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050720821283463E7,
|
||||
4026102.624334418
|
||||
],
|
||||
[
|
||||
4.050717462298063E7,
|
||||
4026091.904402129
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050726278505515E7,
|
||||
4026114.415332655
|
||||
],
|
||||
[
|
||||
4.050725934642086E7,
|
||||
4026115.009285077
|
||||
],
|
||||
[
|
||||
4.050725586910526E7,
|
||||
4026115.301280484
|
||||
],
|
||||
[
|
||||
4.050725237957282E7,
|
||||
4026115.289096617
|
||||
],
|
||||
[
|
||||
4.050724890438099E7,
|
||||
4026114.9728262
|
||||
],
|
||||
[
|
||||
4.050724546997807E7,
|
||||
4026114.354876244
|
||||
],
|
||||
[
|
||||
4.050724210250195E7,
|
||||
4026113.43994972
|
||||
],
|
||||
[
|
||||
4.050722536188381E7,
|
||||
4026108.097315812
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050727312768086E7,
|
||||
4026112.160285922
|
||||
],
|
||||
[
|
||||
4.050727144214725E7,
|
||||
4026112.527790001
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050733913391775E7,
|
||||
4026097.768664928
|
||||
],
|
||||
[
|
||||
4.050731882638656E7,
|
||||
4026102.196402456
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.05074011833275E7,
|
||||
4026084.239767811
|
||||
],
|
||||
[
|
||||
4.050738651815705E7,
|
||||
4026087.437277401
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.05074011833275E7,
|
||||
4026084.239767811
|
||||
],
|
||||
[
|
||||
4.050740376230329E7,
|
||||
4026083.794303922
|
||||
],
|
||||
[
|
||||
4.050740637029002E7,
|
||||
4026083.5753078
|
||||
],
|
||||
[
|
||||
4.050740898743934E7,
|
||||
4026083.584446135
|
||||
],
|
||||
[
|
||||
4.050741162298967E7,
|
||||
4026083.825606086
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050744870329395E7,
|
||||
4026226.381394062
|
||||
],
|
||||
[
|
||||
4.050744533581797E7,
|
||||
4026225.466467002
|
||||
],
|
||||
[
|
||||
4.050744206089737E7,
|
||||
4026224.261526533
|
||||
],
|
||||
[
|
||||
4.050743890345625E7,
|
||||
4026222.775742978
|
||||
],
|
||||
[
|
||||
4.050743588752466E7,
|
||||
4026221.020424048
|
||||
],
|
||||
[
|
||||
4.050743303605565E7,
|
||||
4026219.00892878
|
||||
],
|
||||
[
|
||||
4.050743037075064E7,
|
||||
4026216.756565868
|
||||
],
|
||||
[
|
||||
4.050742791189419E7,
|
||||
4026214.280477153
|
||||
],
|
||||
[
|
||||
4.050742567819968E7,
|
||||
4026211.599507165
|
||||
],
|
||||
[
|
||||
4.050742368666689E7,
|
||||
4026208.734059705
|
||||
],
|
||||
[
|
||||
4.050742195245258E7,
|
||||
4026205.705942559
|
||||
],
|
||||
[
|
||||
4.050742048875517E7,
|
||||
4026202.538201526
|
||||
],
|
||||
[
|
||||
4.050741930671428E7,
|
||||
4026199.254945029
|
||||
],
|
||||
[
|
||||
4.050741841532595E7,
|
||||
4026195.88116063
|
||||
],
|
||||
[
|
||||
4.050741782137419E7,
|
||||
4026192.442524868
|
||||
],
|
||||
[
|
||||
4.050741752937933E7,
|
||||
4026188.965207836
|
||||
],
|
||||
[
|
||||
4.050741754156362E7,
|
||||
4026185.475674017
|
||||
],
|
||||
[
|
||||
4.050741785783435E7,
|
||||
4026182.000480871
|
||||
],
|
||||
[
|
||||
4.050741847578449E7,
|
||||
4026178.566076715
|
||||
],
|
||||
[
|
||||
4.050741939071107E7,
|
||||
4026175.198599438
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050684675534101E7,
|
||||
4025987.268076611
|
||||
],
|
||||
[
|
||||
4.050685643844293E7,
|
||||
4025990.358360866
|
||||
],
|
||||
[
|
||||
4.050689454491971E7,
|
||||
4026002.519737061
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050703137036284E7,
|
||||
4026046.18647901
|
||||
],
|
||||
[
|
||||
4.05070295640735E7,
|
||||
4026045.610016264
|
||||
],
|
||||
[
|
||||
4.050701981996215E7,
|
||||
4026042.500261312
|
||||
],
|
||||
[
|
||||
4.050697623399237E7,
|
||||
4026028.590148907
|
||||
],
|
||||
[
|
||||
4.050697552118288E7,
|
||||
4026028.362661481
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050704840096232E7,
|
||||
4026051.621473066
|
||||
],
|
||||
[
|
||||
4.050704221346159E7,
|
||||
4026049.646966941
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
4.050710556055213E7,
|
||||
4026069.863682419
|
||||
],
|
||||
[
|
||||
4.050708746004742E7,
|
||||
4026064.087051627
|
||||
]
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"code": "F1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
### 1.4.航空器出港滑行路线
|
||||
1、接口地址: http://IP:端口/runwayPathPlanningController
|
||||
/findDepTaxiwayByRunwayAndContactCrossAndSeat
|
||||
2、请求方式: get
|
||||
3、返回格式:以对象形式数据返回
|
||||
4、请求接口示例
|
||||
|
||||
inRunway :进港跑道编号
|
||||
outRunway:出港跑道编号
|
||||
startSeat:起始机位
|
||||
5、数据结构
|
||||
type:进出港类型
|
||||
status:接口状态
|
||||
codes:滑行线编码
|
||||
geoPath:返回路线
|
||||
{
|
||||
type:JeoJson集合
|
||||
geometry:返回的路线JeoJson集合,前端直接绘制即可
|
||||
}
|
||||
数据返回示例与1.3一致
|
||||
@ -65,7 +65,6 @@ SELECT * FROM vehicle_complete_info LIMIT 5;
|
||||
- `sys_vehicle_info` - 车辆基础信息
|
||||
- `sys_driver_info` - 驾驶员信息
|
||||
- `sys_vehicle_type` - 车辆类型
|
||||
- 其他若依框架基础表...
|
||||
|
||||
### 新增表(来自CollisionAvoidanceSystem)
|
||||
- `vehicle_locations` - 车辆实时位置(支持PostGIS空间查询)
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
将CollisionAvoidanceSystem项目的空间数据表合并到QAUP-Management项目数据库中,实现统一的数据库架构。
|
||||
|
||||
# 项目概述
|
||||
QAUP-Management是基于若依框架的车辆管理系统,CollisionAvoidanceSystem是机场碰撞避免系统。两个项目都使用PostgreSQL数据库,表结构完全互补,无冲突。
|
||||
QAUP-Management是车辆管理系统,CollisionAvoidanceSystem是机场碰撞避免系统。两个项目都使用PostgreSQL数据库,表结构完全互补,无冲突。
|
||||
|
||||
---
|
||||
*以下部分由 AI 在协议执行过程中维护*
|
||||
|
||||
@ -23,7 +23,7 @@ psql -h localhost -U postgres
|
||||
# 连接到qaup数据库
|
||||
psql -h localhost -U postgres -d qaup
|
||||
|
||||
# 导入若依框架的基础表结构和数据
|
||||
# 导入基础表结构和数据
|
||||
\i /path/to/QAUP-Management/sql/postgresql.sql
|
||||
```
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
**项目名称**: QAUP 机场车辆管理和碰撞避免系统
|
||||
**优化日期**: 2025-01-15
|
||||
**项目版本**: 3.8.9
|
||||
**项目版本**: 1.0.1
|
||||
**分析人员**: Claude Code Assistant
|
||||
|
||||
## 📋 执行摘要
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
|
||||
## 项目概述
|
||||
QAUP机场车辆管理系统,包含:
|
||||
- QAUP-Management:基础车辆信息管理(若依框架)
|
||||
- QAUP-Management:基础车辆信息管理
|
||||
- CollisionAvoidanceSystem:实时位置监控和碰撞避免
|
||||
|
||||
---
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
**QAUP-Management技术栈:**
|
||||
- Spring Boot 2.5.15 + Java 8
|
||||
- MyBatis + PostgreSQL
|
||||
- 若依框架(用户权限、菜单管理等)
|
||||
- 用户权限、菜单管理等)
|
||||
- Maven多模块结构(qaup-admin、qaup-system、qaup-framework等)
|
||||
|
||||
**CollisionAvoidanceSystem技术栈:**
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
## 🔧 数据适配器架构
|
||||
|
||||
### QuapDataAdapter功能
|
||||
- **数据桥接**: 连接若依Service层与CollisionAvoidanceSystem
|
||||
- **数据桥接**: 连接Service层与CollisionAvoidanceSystem
|
||||
- **类型转换**: SysVehicleInfo ↔ VehicleLocation
|
||||
- **统一接口**: 避免重复编写DAO组件
|
||||
- **测试覆盖**: 15个单元测试全部通过
|
||||
|
||||
4
pom.xml
4
pom.xml
@ -6,10 +6,10 @@
|
||||
|
||||
<groupId>com.qaup</groupId>
|
||||
<artifactId>qaup-management</artifactId>
|
||||
<version>3.8.9</version>
|
||||
<version>1.0.1</version>
|
||||
|
||||
<properties>
|
||||
<qaup.version>3.8.9</qaup.version>
|
||||
<qaup.version>1.0.1</qaup.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>17</java.version>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>qaup-management</artifactId>
|
||||
<groupId>com.qaup</groupId>
|
||||
<version>3.8.9</version>
|
||||
<version>1.0.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@ -1,19 +1,28 @@
|
||||
package com.qaup.web.controller.system;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.qaup.common.annotation.Log;
|
||||
import com.qaup.common.core.controller.BaseController;
|
||||
import com.qaup.common.core.domain.AjaxResult;
|
||||
import com.qaup.common.enums.BusinessType;
|
||||
import com.qaup.system.domain.SysVehicleLocation;
|
||||
import com.qaup.system.domain.vo.VehicleTrajectoryVO;
|
||||
import com.qaup.system.domain.vo.VehicleLocationPlaybackVO;
|
||||
import com.qaup.system.domain.vo.TrajectoryStatistics;
|
||||
import com.qaup.system.domain.dto.TrajectoryQueryRequest;
|
||||
import com.qaup.system.service.ISysVehicleLocationService;
|
||||
import com.qaup.common.utils.poi.ExcelUtil;
|
||||
import com.qaup.common.core.page.TableDataInfo;
|
||||
@ -107,4 +116,80 @@ public class SysVehicleLocationController extends BaseController
|
||||
SysVehicleLocation sysVehicleLocation = sysVehicleLocationService.selectLatestSysVehicleLocationByLicensePlate(licensePlate);
|
||||
return success(sysVehicleLocation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 车辆轨迹查询接口
|
||||
*/
|
||||
@Operation(summary = "车辆轨迹查询", description = "查询指定车辆在指定时间段内的运行轨迹,支持数据简化和采样优化")
|
||||
@GetMapping("/trajectory/{vehicleId}")
|
||||
public AjaxResult getVehicleTrajectory(
|
||||
@PathVariable("vehicleId") Long vehicleId,
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime,
|
||||
@RequestParam(defaultValue = "false") Boolean simplified,
|
||||
@RequestParam(defaultValue = "1000") Integer maxPoints)
|
||||
{
|
||||
VehicleTrajectoryVO trajectory = sysVehicleLocationService
|
||||
.getVehicleTrajectory(vehicleId, startTime, endTime, simplified, maxPoints);
|
||||
return success(trajectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多车辆轨迹查询接口
|
||||
*/
|
||||
@Operation(summary = "多车辆轨迹查询", description = "批量查询多个车辆的运行轨迹,支持复杂查询条件")
|
||||
@PostMapping("/trajectory/batch")
|
||||
public AjaxResult getBatchVehicleTrajectory(@Valid @RequestBody TrajectoryQueryRequest request)
|
||||
{
|
||||
List<VehicleTrajectoryVO> trajectories = sysVehicleLocationService
|
||||
.getBatchVehicleTrajectory(request);
|
||||
return success(trajectories);
|
||||
}
|
||||
|
||||
/**
|
||||
* 轨迹回放数据接口
|
||||
*/
|
||||
@Operation(summary = "轨迹回放数据查询", description = "获取用于轨迹回放的数据,按指定时间间隔采样,包含回放所需的序列和时间信息")
|
||||
@GetMapping("/trajectory/{vehicleId}/playback")
|
||||
public AjaxResult getTrajectoryPlaybackData(
|
||||
@PathVariable("vehicleId") Long vehicleId,
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime,
|
||||
@RequestParam(defaultValue = "10") Integer intervalSeconds)
|
||||
{
|
||||
List<VehicleLocationPlaybackVO> playbackData = sysVehicleLocationService
|
||||
.getTrajectoryPlaybackData(vehicleId, startTime, endTime, intervalSeconds);
|
||||
return success(playbackData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 区域内轨迹查询接口
|
||||
*/
|
||||
@Operation(summary = "区域内轨迹查询", description = "查询车辆在指定区域内的运行轨迹")
|
||||
@GetMapping("/trajectory/{vehicleId}/area")
|
||||
public AjaxResult getVehicleTrajectoryInArea(
|
||||
@PathVariable("vehicleId") Long vehicleId,
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime,
|
||||
@RequestParam String areaWkt)
|
||||
{
|
||||
VehicleTrajectoryVO trajectory = sysVehicleLocationService
|
||||
.getVehicleTrajectoryInArea(vehicleId, startTime, endTime, areaWkt);
|
||||
return success(trajectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 轨迹统计信息查询接口
|
||||
*/
|
||||
@Operation(summary = "轨迹统计信息查询", description = "获取车辆轨迹的统计信息,如总距离、平均速度、行驶时长等")
|
||||
@GetMapping("/trajectory/{vehicleId}/statistics")
|
||||
public AjaxResult getTrajectoryStatistics(
|
||||
@PathVariable("vehicleId") Long vehicleId,
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime)
|
||||
{
|
||||
TrajectoryStatistics statistics = sysVehicleLocationService
|
||||
.getTrajectoryStatistics(vehicleId, startTime, endTime);
|
||||
return success(statistics);
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ qaup:
|
||||
# 名称
|
||||
name: Qaup
|
||||
# 版本
|
||||
version: 3.8.9
|
||||
version: 1.0.1
|
||||
# 版权年份
|
||||
copyrightYear: 2025
|
||||
# 文件路径 示例( Windows配置D:/qaup/uploadPath,Linux配置 /home/qaup/uploadPath)
|
||||
@ -113,7 +113,7 @@ spring:
|
||||
# JPA配置(collision模块空间数据处理)
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: none # 使用若依的数据库管理方式
|
||||
ddl-auto: none # 使用数据库管理方式
|
||||
show-sql: false
|
||||
properties:
|
||||
hibernate:
|
||||
@ -178,19 +178,26 @@ xss:
|
||||
data:
|
||||
collector:
|
||||
# 数据采集间隔,单位:毫秒(高频采集保证数据新鲜度)
|
||||
interval: 1000
|
||||
interval: 250
|
||||
# 路由数据采集间隔,单位:毫秒(较低频率采集路由信息)
|
||||
route:
|
||||
interval: 5000
|
||||
# 检测和推送间隔配置
|
||||
detection:
|
||||
# 检测间隔,单位:毫秒(控制围栏检测、冲突检测、违规检测和WebSocket推送频率)
|
||||
interval: 5000
|
||||
interval: 1000
|
||||
# 机场数据源配置
|
||||
airport-api:
|
||||
base-url: http://localhost:8090
|
||||
endpoints:
|
||||
login: /login
|
||||
refresh: /userInfoController/refreshToken
|
||||
aircraft: /openApi/getCurrentFlightPositions
|
||||
vehicle: /openApi/getCurrentVehiclePositions
|
||||
refresh: /refresh
|
||||
arrival-route: /runwayPathPlanningController/findArrTaxiwayByRunwayAndContactCrossAndSeat
|
||||
departure-route: /runwayPathPlanningController/findDepTaxiwayByRunwayAndContactCrossAndSeat
|
||||
aircraft-status: /aircraftStatusController/getAircraftStatus
|
||||
|
||||
auth:
|
||||
username: dianxin
|
||||
password: dianxin@123
|
||||
@ -201,7 +208,7 @@ data:
|
||||
vehicle-location: /api/VehicleLocationInfo
|
||||
vehicle-state: /api/VehicleStateInfo
|
||||
vehicle-command: /api/VehicleCommandInfo
|
||||
timeout: 5000
|
||||
timeout: 1000
|
||||
retry-attempts: 3
|
||||
# 无人车数据持久化配置
|
||||
unmanned-vehicle:
|
||||
@ -211,7 +218,7 @@ data:
|
||||
location-retention-days: 90
|
||||
command-retention-days: 365
|
||||
command:
|
||||
timeout: 5000
|
||||
timeout: 1000
|
||||
retry-attempts: 3
|
||||
validation:
|
||||
enabled: true
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>qaup-management</artifactId>
|
||||
<groupId>com.qaup</groupId>
|
||||
<version>3.8.9</version>
|
||||
<version>1.0.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -23,7 +23,6 @@ import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 区域配置导入服务 - 将YAML配置文件中的区域数据导入到PostGIS数据库
|
||||
@ -238,7 +237,6 @@ public class AreaConfigImportService {
|
||||
// 几何边界(假设是坐标点数组)
|
||||
Object boundaryObj = areaMap.get("boundary");
|
||||
if (boundaryObj instanceof List<?>) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<?> list = (List<?>) boundaryObj;
|
||||
if (!list.isEmpty() && list.get(0) instanceof List) {
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package com.qaup.collision.common.adapter;
|
||||
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.system.domain.SysVehicleInfo;
|
||||
import com.qaup.system.domain.SysVehicleType;
|
||||
import com.qaup.system.service.ISysVehicleInfoService;
|
||||
@ -21,7 +20,7 @@ import java.util.stream.Collectors;
|
||||
/**
|
||||
* QAUP数据适配器
|
||||
*
|
||||
* 连接CollisionAvoidanceSystem和若依系统的Service层
|
||||
* 连接CollisionAvoidanceSystem和QAUP系统的Service层
|
||||
* 提供统一的数据访问接口,避免重复编写DAO组件
|
||||
*
|
||||
* 主要功能:
|
||||
@ -191,68 +190,7 @@ public class QuapDataAdapter {
|
||||
|
||||
return vehicleLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将车辆类型编码转换为CollisionAvoidanceSystem的MovingObjectType
|
||||
*
|
||||
* @param typeCode 车辆类型编码
|
||||
* @return MovingObjectType
|
||||
*/
|
||||
public MovingObjectType convertToMovingObjectType(String typeCode) {
|
||||
if (typeCode == null || typeCode.trim().isEmpty()) {
|
||||
return MovingObjectType.UNKNOWN;
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用路径编码模式进行分类
|
||||
String topLevelCode = typeCode.split("\\.")[0];
|
||||
|
||||
switch (topLevelCode) {
|
||||
case "UV":
|
||||
return MovingObjectType.UNMANNED_VEHICLE;
|
||||
case "SP":
|
||||
return MovingObjectType.SPECIAL_VEHICLE;
|
||||
case "NM":
|
||||
return MovingObjectType.NORMAL_VEHICLE;
|
||||
default:
|
||||
logger.warn("未知的车辆类型编码: typeCode={}", typeCode);
|
||||
return MovingObjectType.UNKNOWN;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("转换车辆类型失败: typeCode={}", typeCode, e);
|
||||
return MovingObjectType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将车辆类型ID转换为CollisionAvoidanceSystem的MovingObjectType(向后兼容方法)
|
||||
*
|
||||
* @param typeId 车辆类型ID(已废弃)
|
||||
* @return MovingObjectType
|
||||
* @deprecated 使用 convertToMovingObjectType(String typeCode) 代替
|
||||
*/
|
||||
@Deprecated
|
||||
public MovingObjectType convertToMovingObjectType(Long typeId) {
|
||||
if (typeId == null) {
|
||||
return MovingObjectType.UNKNOWN;
|
||||
}
|
||||
|
||||
try {
|
||||
// 通过ID查找类型编码,然后使用新方法
|
||||
SysVehicleType vehicleType = vehicleTypeService.selectSysVehicleTypeById(typeId);
|
||||
if (vehicleType != null && vehicleType.getTypeCode() != null) {
|
||||
return convertToMovingObjectType(vehicleType.getTypeCode());
|
||||
}
|
||||
|
||||
logger.warn("未找到车辆类型: typeId={}", typeId);
|
||||
return MovingObjectType.UNKNOWN;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("转换车辆类型失败: typeId={}", typeId, e);
|
||||
return MovingObjectType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ================== 查询条件构建辅助方法 ==================
|
||||
|
||||
@ -292,16 +230,6 @@ public class QuapDataAdapter {
|
||||
|
||||
// ================== 日志和监控方法 ==================
|
||||
|
||||
/**
|
||||
* 记录数据访问操作日志
|
||||
*
|
||||
* @param operation 操作类型
|
||||
* @param details 操作详情
|
||||
*/
|
||||
private void logDataAccess(String operation, String details) {
|
||||
logger.info("数据访问操作 - 操作: {}, 详情: {}", operation, details);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证适配器初始化状态
|
||||
*
|
||||
|
||||
@ -2,12 +2,10 @@ package com.qaup.collision.common.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor; // 重新引入无参构造函数
|
||||
// import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
// import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
|
||||
/**
|
||||
* 航空器模型
|
||||
@ -18,13 +16,114 @@ import org.locationtech.jts.geom.Point;
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@NoArgsConstructor // 重新引入无参构造函数
|
||||
// @AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@SuperBuilder
|
||||
@JsonIgnoreProperties(ignoreUnknown = true) // 忽略JSON中未知的字段
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Aircraft extends MovingObject {
|
||||
|
||||
// 根据用户反馈,移除 flightNo 和 time 字段。这些信息应从 sys_vehicle_info 表中查询。
|
||||
// 外部API的原始字段将通过 DataCollectorDao 中的内部 DTO 进行处理和映射。
|
||||
|
||||
/**
|
||||
* 航班号
|
||||
*/
|
||||
private String flightNumber;
|
||||
|
||||
/**
|
||||
* 进港跑道编号
|
||||
*/
|
||||
private String inRunway;
|
||||
|
||||
/**
|
||||
* 出港跑道编号
|
||||
*/
|
||||
private String outRunway;
|
||||
|
||||
/**
|
||||
* 联络道口
|
||||
*/
|
||||
private String contactCross;
|
||||
|
||||
/**
|
||||
* 目的机位
|
||||
*/
|
||||
private String seat;
|
||||
|
||||
/**
|
||||
* 航班状态
|
||||
*/
|
||||
private FlightStatus flightStatus;
|
||||
|
||||
/**
|
||||
* 当前进港路由信息
|
||||
*/
|
||||
private AircraftRoute arrivalRoute;
|
||||
|
||||
/**
|
||||
* 当前出港路由信息
|
||||
*/
|
||||
private AircraftRoute departureRoute;
|
||||
|
||||
/**
|
||||
* 当前激活的路由 (指向arrivalRoute或departureRoute)
|
||||
*/
|
||||
private AircraftRoute currentRoute;
|
||||
|
||||
/**
|
||||
* 路由状态时间戳
|
||||
*/
|
||||
private Long routeTimestamp;
|
||||
|
||||
/**
|
||||
* 航班状态枚举
|
||||
*/
|
||||
public enum FlightStatus {
|
||||
SCHEDULED, // 计划
|
||||
TAXIING, // 滑行
|
||||
TAKEOFF, // 起飞
|
||||
AIRBORNE, // 空中
|
||||
LANDING, // 降落
|
||||
ARRIVED, // 进港
|
||||
DEPARTURE, // 出港
|
||||
CANCELLED // 取消
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有激活的路由
|
||||
*/
|
||||
public boolean hasActiveRoute() {
|
||||
return currentRoute != null && currentRoute.isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前路由类型
|
||||
*/
|
||||
public String getCurrentRouteType() {
|
||||
return currentRoute != null ? currentRoute.getType() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进港路由为当前激活路由
|
||||
*/
|
||||
public void activateArrivalRoute() {
|
||||
if (arrivalRoute != null) {
|
||||
this.currentRoute = arrivalRoute;
|
||||
this.routeTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置出港路由为当前激活路由
|
||||
*/
|
||||
public void activateDepartureRoute() {
|
||||
if (departureRoute != null) {
|
||||
this.currentRoute = departureRoute;
|
||||
this.routeTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前激活路由
|
||||
*/
|
||||
public void clearCurrentRoute() {
|
||||
this.currentRoute = null;
|
||||
this.routeTimestamp = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,165 @@
|
||||
package com.qaup.collision.common.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import org.locationtech.jts.geom.LineString;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 航空器路由模型类
|
||||
*
|
||||
* 用于存储机场实时计算的航空器滑行路由信息
|
||||
* 该类不持久化到数据库,仅用于实时处理
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AircraftRoute {
|
||||
|
||||
/**
|
||||
* 路由类型 (IN: 进港, OUT: 出港)
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 路由状态 (COMPLETE: 完成, CALCULATING: 计算中, MODIFIED: 已修改)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 路由编码序列 (如: "F1,L4,138")
|
||||
*/
|
||||
private String codes;
|
||||
|
||||
/**
|
||||
* 路由几何图形 (LineString)
|
||||
*/
|
||||
private LineString geometry;
|
||||
|
||||
/**
|
||||
* 路由路径集合 (GeoJSON格式的路径段)
|
||||
*/
|
||||
private List<RouteSegment> routeSegments;
|
||||
|
||||
/**
|
||||
* 路由总长度 (米)
|
||||
*/
|
||||
private Double totalLength;
|
||||
|
||||
/**
|
||||
* 预计滑行时间 (秒)
|
||||
*/
|
||||
private Integer estimatedTime;
|
||||
|
||||
/**
|
||||
* 路由创建时间戳
|
||||
*/
|
||||
private Long timestamp;
|
||||
|
||||
/**
|
||||
* 路由修改次数
|
||||
*/
|
||||
private Integer modificationCount;
|
||||
|
||||
/**
|
||||
* 路由段模型
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public static class RouteSegment {
|
||||
|
||||
/**
|
||||
* 路径段编码 (如: "L4", "F1")
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 路径段类型 (TAXIWAY: 滑行道, CONTACT: 联络道, RUNWAY: 跑道)
|
||||
*/
|
||||
private String segmentType;
|
||||
|
||||
/**
|
||||
* 路径段坐标点列表
|
||||
*/
|
||||
private List<Point> coordinates;
|
||||
|
||||
/**
|
||||
* 路径段长度 (米)
|
||||
*/
|
||||
private Double length;
|
||||
|
||||
/**
|
||||
* 建议滑行速度 (km/h)
|
||||
*/
|
||||
private Double recommendedSpeed;
|
||||
|
||||
/**
|
||||
* 路径段顺序
|
||||
*/
|
||||
private Integer sequence;
|
||||
|
||||
/**
|
||||
* 是否为关键路径段
|
||||
*/
|
||||
private Boolean critical;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路由是否有效
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return type != null &&
|
||||
status != null &&
|
||||
codes != null &&
|
||||
!codes.trim().isEmpty() &&
|
||||
routeSegments != null &&
|
||||
!routeSegments.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取路由段数量
|
||||
*/
|
||||
public int getSegmentCount() {
|
||||
return routeSegments != null ? routeSegments.size() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为进港路由
|
||||
*/
|
||||
public boolean isArrivalRoute() {
|
||||
return "IN".equals(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为出港路由
|
||||
*/
|
||||
public boolean isDepartureRoute() {
|
||||
return "OUT".equals(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路由是否完成
|
||||
*/
|
||||
public boolean isComplete() {
|
||||
return "COMPLETE".equals(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查路由是否被修改过
|
||||
*/
|
||||
public boolean isModified() {
|
||||
return "MODIFIED".equals(status) ||
|
||||
(modificationCount != null && modificationCount > 0);
|
||||
}
|
||||
}
|
||||
@ -2,16 +2,12 @@ package com.qaup.collision.common.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor; // 重新引入无参构造函数
|
||||
// import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
// import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
|
||||
/**
|
||||
* 机场车辆模型
|
||||
* 表示无人车以外的机场内车辆(如服务车、清洁车、加油车等)
|
||||
* 机场车辆模型(包括特勤车和普通车)
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @version 1.0
|
||||
@ -19,13 +15,34 @@ import org.locationtech.jts.geom.Point;
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@NoArgsConstructor // 重新引入无参构造函数
|
||||
// @AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@SuperBuilder
|
||||
@JsonIgnoreProperties(ignoreUnknown = true) // 忽略JSON中未知的字段
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class AirportVehicle extends MovingObject {
|
||||
|
||||
// 根据用户反馈,移除 vehicleNo 和 time 字段。这些信息应从 sys_vehicle_info 表中查询。
|
||||
// 外部API的原始字段将通过 DataCollectorDao 中的内部 DTO 进行处理和映射。
|
||||
|
||||
/**
|
||||
* 车牌号(主要识别信息)
|
||||
*/
|
||||
private String plateNumber;
|
||||
|
||||
/**
|
||||
* 获取车牌号,如果为空则返回对象名称
|
||||
*/
|
||||
public String getPlateNumber() {
|
||||
return plateNumber != null ? plateNumber : getObjectName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为特勤车
|
||||
*/
|
||||
public boolean isSpecialVehicle() {
|
||||
return getObjectType() == MovingObjectType.SPECIAL_VEHICLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为普通车
|
||||
*/
|
||||
public boolean isNormalVehicle() {
|
||||
return getObjectType() == MovingObjectType.NORMAL_VEHICLE;
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ public class MovingObject {
|
||||
/**
|
||||
* 对象类型
|
||||
*/
|
||||
private ObjectType objectType;
|
||||
private MovingObjectType objectType;
|
||||
|
||||
/**
|
||||
* 对象名称(车牌号或航班号)
|
||||
@ -48,30 +48,41 @@ public class MovingObject {
|
||||
private Double altitude;
|
||||
|
||||
/**
|
||||
* 对象类型枚举
|
||||
* 对象类型枚举 - 四种基本分类
|
||||
*/
|
||||
public enum ObjectType {
|
||||
UNMANNED_VEHICLE, // 无人车
|
||||
AIRCRAFT, // 航空器
|
||||
SPECIAL_VEHICLE, // 特种车辆(如机场车辆)
|
||||
NORMAL_VEHICLE; // 普通车辆
|
||||
public enum MovingObjectType {
|
||||
UNMANNED_VEHICLE("UV"), // 无人车
|
||||
AIRCRAFT("AC"), // 航空器
|
||||
SPECIAL_VEHICLE("SP"), // 特勤车
|
||||
NORMAL_VEHICLE("NM"); // 普通车
|
||||
|
||||
private final String typeCode;
|
||||
|
||||
MovingObjectType(String typeCode) {
|
||||
this.typeCode = typeCode;
|
||||
}
|
||||
|
||||
public String getTypeCode() {
|
||||
return typeCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为MovingObjectType
|
||||
* 根据类型编码获取对象类型
|
||||
*/
|
||||
public MovingObjectType toMovingObjectType() {
|
||||
switch (this) {
|
||||
case UNMANNED_VEHICLE:
|
||||
return MovingObjectType.UNMANNED_VEHICLE;
|
||||
case AIRCRAFT:
|
||||
return MovingObjectType.AIRCRAFT;
|
||||
case SPECIAL_VEHICLE:
|
||||
return MovingObjectType.SPECIAL_VEHICLE;
|
||||
case NORMAL_VEHICLE:
|
||||
return MovingObjectType.NORMAL_VEHICLE;
|
||||
default:
|
||||
return MovingObjectType.UNMANNED_VEHICLE;
|
||||
public static MovingObjectType fromTypeCode(String typeCode) {
|
||||
for (MovingObjectType type : values()) {
|
||||
if (type.typeCode.equals(typeCode)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown type code: " + typeCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要数据持久化
|
||||
*/
|
||||
public boolean shouldPersist() {
|
||||
return this == UNMANNED_VEHICLE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
package com.qaup.collision.common.model;
|
||||
|
||||
import com.qaup.collision.common.model.enums.VehicleTypeCode;
|
||||
import com.qaup.collision.common.model.enums.VehicleTypeConverter;
|
||||
|
||||
public enum MovingObjectType {
|
||||
// 航空器(飞机)- 航空器位置数据接入
|
||||
AIRCRAFT,
|
||||
// 特勤车辆(具有特殊权限)- 特勤车辆如警车、消防车、救护车等
|
||||
SPECIAL_VEHICLE,
|
||||
// 普通车(普通车辆)- 新增的普通车辆类型
|
||||
NORMAL_VEHICLE,
|
||||
// 无人车(可控)- 无人车控制接口
|
||||
UNMANNED_VEHICLE,
|
||||
// 未知类型
|
||||
UNKNOWN;
|
||||
|
||||
/**
|
||||
* 从VehicleTypeCode转换为MovingObjectType
|
||||
* 提供新旧枚举之间的兼容性
|
||||
*/
|
||||
public static MovingObjectType fromVehicleTypeCode(VehicleTypeCode vehicleTypeCode) {
|
||||
return VehicleTypeConverter.toMovingObjectType(vehicleTypeCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从路径编码字符串转换为MovingObjectType
|
||||
*/
|
||||
public static MovingObjectType fromTypeCode(String typeCode) {
|
||||
return VehicleTypeConverter.fromTypeCode(typeCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为车辆类型(排除航空器)
|
||||
*/
|
||||
public boolean isVehicle() {
|
||||
return this != AIRCRAFT && this != UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为特殊权限车辆
|
||||
*/
|
||||
public boolean hasSpecialPrivileges() {
|
||||
return this == SPECIAL_VEHICLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为自动驾驶车辆
|
||||
*/
|
||||
public boolean isAutonomous() {
|
||||
return this == UNMANNED_VEHICLE;
|
||||
}
|
||||
}
|
||||
@ -2,11 +2,10 @@ package com.qaup.collision.common.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor; // 重新引入无参构造函数
|
||||
// import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
// import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
|
||||
/**
|
||||
* 无人车模型
|
||||
@ -17,13 +16,83 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@NoArgsConstructor // 重新引入无参构造函数
|
||||
// @AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@SuperBuilder
|
||||
@JsonIgnoreProperties(ignoreUnknown = true) // 忽略JSON中未知的字段
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class UnmannedVehicle extends MovingObject {
|
||||
|
||||
// 根据用户反馈,移除 vehicleId, licensePlate, vehicleModel, usagePurpose 和 timestamp 字段。
|
||||
// 外部API的原始字段将通过 DataCollectorDao 中的内部 DTO 进行处理和映射。
|
||||
|
||||
/**
|
||||
* 电池电量百分比 (0-100)
|
||||
*/
|
||||
private Integer batteryLevel;
|
||||
|
||||
/**
|
||||
* 运行状态
|
||||
*/
|
||||
private VehicleStatus vehicleStatus;
|
||||
|
||||
/**
|
||||
* 当前任务ID
|
||||
*/
|
||||
private String missionId;
|
||||
|
||||
/**
|
||||
* 任务状态
|
||||
*/
|
||||
private MissionStatus missionStatus;
|
||||
|
||||
/**
|
||||
* 目标位置
|
||||
*/
|
||||
private Point targetPosition;
|
||||
|
||||
/**
|
||||
* 无人车运行状态枚举
|
||||
*/
|
||||
public enum VehicleStatus {
|
||||
IDLE, // 空闲
|
||||
WORKING, // 工作中
|
||||
CHARGING, // 充电中
|
||||
MAINTENANCE, // 维护中
|
||||
ERROR, // 故障
|
||||
OFFLINE // 离线
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务状态枚举
|
||||
*/
|
||||
public enum MissionStatus {
|
||||
NONE, // 无任务
|
||||
ASSIGNED, // 已分配
|
||||
IN_PROGRESS, // 执行中
|
||||
COMPLETED, // 已完成
|
||||
PAUSED, // 暂停
|
||||
CANCELLED // 已取消
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以接收新指令
|
||||
*/
|
||||
public boolean canReceiveCommand() {
|
||||
return vehicleStatus != VehicleStatus.MAINTENANCE &&
|
||||
vehicleStatus != VehicleStatus.ERROR &&
|
||||
vehicleStatus != VehicleStatus.OFFLINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否电量不足
|
||||
*/
|
||||
public boolean isLowBattery() {
|
||||
return batteryLevel != null && batteryLevel < 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算到目标位置的距离(米)
|
||||
*/
|
||||
public double getDistanceToTarget() {
|
||||
if (targetPosition == null || getCurrentPosition() == null) {
|
||||
return Double.MAX_VALUE;
|
||||
}
|
||||
return getCurrentPosition().distance(targetPosition) * 111320; // 度转米的近似值
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package com.qaup.collision.common.model.enums;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
|
||||
/**
|
||||
* 车辆类型转换工具类
|
||||
* 负责新旧车辆类型枚举之间的转换
|
||||
* 负责车辆类型枚举和编码之间的转换
|
||||
*
|
||||
* @author Claude Code
|
||||
* @version 1.0
|
||||
@ -12,11 +12,11 @@ import com.qaup.collision.common.model.MovingObjectType;
|
||||
public class VehicleTypeConverter {
|
||||
|
||||
/**
|
||||
* 将VehicleTypeCode转换为MovingObjectType
|
||||
* 将VehicleTypeCode转换为ObjectType
|
||||
*/
|
||||
public static MovingObjectType toMovingObjectType(VehicleTypeCode vehicleTypeCode) {
|
||||
public static MovingObjectType toObjectType(VehicleTypeCode vehicleTypeCode) {
|
||||
if (vehicleTypeCode == null) {
|
||||
return MovingObjectType.UNKNOWN;
|
||||
return MovingObjectType.NORMAL_VEHICLE;
|
||||
}
|
||||
|
||||
switch (vehicleTypeCode.getTopLevelType()) {
|
||||
@ -27,45 +27,50 @@ public class VehicleTypeConverter {
|
||||
case NM:
|
||||
return MovingObjectType.NORMAL_VEHICLE;
|
||||
default:
|
||||
return MovingObjectType.UNKNOWN;
|
||||
return MovingObjectType.NORMAL_VEHICLE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从MovingObjectType转换为VehicleTypeCode(一级分类)
|
||||
* 从ObjectType转换为VehicleTypeCode(一级分类)
|
||||
*/
|
||||
public static VehicleTypeCode fromMovingObjectType(MovingObjectType movingObjectType) {
|
||||
if (movingObjectType == null) {
|
||||
public static VehicleTypeCode fromObjectType(MovingObjectType objectType) {
|
||||
if (objectType == null) {
|
||||
return VehicleTypeCode.NM;
|
||||
}
|
||||
|
||||
switch (movingObjectType) {
|
||||
switch (objectType) {
|
||||
case UNMANNED_VEHICLE:
|
||||
return VehicleTypeCode.UV;
|
||||
case SPECIAL_VEHICLE:
|
||||
return VehicleTypeCode.SP;
|
||||
case NORMAL_VEHICLE:
|
||||
case AIRPORT_VEHICLE: // 兼容旧的机场车辆类型
|
||||
return VehicleTypeCode.NM;
|
||||
case AIRCRAFT:
|
||||
return VehicleTypeCode.NM; // 航空器默认映射为普通车(虽然不会用到)
|
||||
default:
|
||||
return VehicleTypeCode.NM; // 默认返回普通车
|
||||
return VehicleTypeCode.NM;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从路径编码字符串转换为MovingObjectType
|
||||
* 从路径编码字符串转换为ObjectType
|
||||
*/
|
||||
public static MovingObjectType fromTypeCode(String typeCode) {
|
||||
if (typeCode == null || typeCode.trim().isEmpty()) {
|
||||
return MovingObjectType.UNKNOWN;
|
||||
return MovingObjectType.NORMAL_VEHICLE;
|
||||
}
|
||||
|
||||
VehicleTypeCode vehicleTypeCode = VehicleTypeCode.fromCodeSafe(typeCode);
|
||||
if (vehicleTypeCode != null) {
|
||||
return toMovingObjectType(vehicleTypeCode);
|
||||
// 直接根据编码前缀判断
|
||||
if (typeCode.startsWith("UV")) {
|
||||
return MovingObjectType.UNMANNED_VEHICLE;
|
||||
} else if (typeCode.startsWith("SP")) {
|
||||
return MovingObjectType.SPECIAL_VEHICLE;
|
||||
} else if (typeCode.startsWith("AC")) {
|
||||
return MovingObjectType.AIRCRAFT;
|
||||
} else {
|
||||
return MovingObjectType.NORMAL_VEHICLE;
|
||||
}
|
||||
|
||||
return MovingObjectType.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,9 +78,9 @@ public class VehicleTypeConverter {
|
||||
* 这个方法需要和QuapDataAdapter配合使用
|
||||
*/
|
||||
public static MovingObjectType fromLegacyTypeId(Long typeId, String typeName) {
|
||||
// 基于类型名称进行简单分类(保持原有逻辑)
|
||||
// 基于类型名称进行简单分类
|
||||
if (typeName == null || typeName.trim().isEmpty()) {
|
||||
return MovingObjectType.UNKNOWN;
|
||||
return MovingObjectType.NORMAL_VEHICLE;
|
||||
}
|
||||
|
||||
if (typeName.contains("无人车")) {
|
||||
@ -84,8 +89,10 @@ public class VehicleTypeConverter {
|
||||
typeName.contains("警车") || typeName.contains("救护") ||
|
||||
typeName.contains("引导")) {
|
||||
return MovingObjectType.SPECIAL_VEHICLE;
|
||||
} else if (typeName.contains("航空") || typeName.contains("飞机")) {
|
||||
return MovingObjectType.AIRCRAFT;
|
||||
} else {
|
||||
// 所有其他类型都归类为普通车辆(原来是机场车辆)
|
||||
// 所有其他类型都归类为普通车辆
|
||||
return MovingObjectType.NORMAL_VEHICLE;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.common.model.spatial;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@ -2,6 +2,7 @@ package com.qaup.collision.common.service;
|
||||
|
||||
import com.qaup.collision.common.model.spatial.AirportArea;
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
import com.qaup.common.constant.GeoConstants;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.locationtech.jts.geom.*;
|
||||
@ -332,7 +333,7 @@ public class SpatialQueryService {
|
||||
// ===== 私有辅助方法 =====
|
||||
|
||||
private double calculateDistance(Point point1, Point point2) {
|
||||
return point1.distance(point2) * 111320; // 近似转换为米(在赤道附近)
|
||||
return point1.distance(point2) * GeoConstants.DEGREE_TO_METER; // 近似转换为米(在赤道附近)
|
||||
}
|
||||
|
||||
private boolean isHeadOnConflict(VehicleLocation vehicle1, VehicleLocation vehicle2, double distance) {
|
||||
|
||||
@ -2,7 +2,8 @@ package com.qaup.collision.common.service;
|
||||
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
import com.qaup.collision.common.model.repository.VehicleLocationRepository;
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.adapter.QuapDataAdapter;
|
||||
import com.qaup.collision.rule.event.RuleViolationEvent;
|
||||
import com.qaup.collision.rule.model.entity.SpatialRule;
|
||||
@ -10,6 +11,7 @@ import com.qaup.collision.rule.model.enums.RuleExecutionResult;
|
||||
import com.qaup.collision.rule.service.LocationRuleQueryService;
|
||||
import com.qaup.collision.rule.service.RealTimeViolationDetector;
|
||||
import com.qaup.collision.rule.service.RuleExecutionEngine;
|
||||
import com.qaup.common.utils.GeoUtils;
|
||||
import com.qaup.system.domain.SysVehicleInfo;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -125,18 +127,7 @@ public class VehicleLocationService {
|
||||
* 计算两点之间的距离(米)
|
||||
*/
|
||||
private static double calculateDistance(double lon1, double lat1, double lon2, double lat2) {
|
||||
if (lon1 == lon2 && lat1 == lat2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final double R = 6371000; // 地球半径(米)
|
||||
double dLat = Math.toRadians(lat2 - lat1);
|
||||
double dLon = Math.toRadians(lon2 - lon1);
|
||||
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
|
||||
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
return R * c;
|
||||
return GeoUtils.calculateDistance(lat1, lon1, lat2, lon2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -319,7 +310,7 @@ public class VehicleLocationService {
|
||||
public List<VehicleLocation> getActiveVehiclesByType(MovingObjectType vehicleType, int minutesBack) {
|
||||
LocalDateTime since = LocalDateTime.now().minusMinutes(minutesBack);
|
||||
try {
|
||||
// 将MovingObjectType转换为数据库中的类型名称模式
|
||||
// 将MovingObject.ObjectType转换为数据库中的类型名称模式
|
||||
String typeName = convertMovingObjectTypeToTypeName(vehicleType);
|
||||
return vehicleLocationRepository.findActiveByVehicleType(typeName, since);
|
||||
} catch (Exception e) {
|
||||
@ -329,14 +320,16 @@ public class VehicleLocationService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将MovingObjectType转换为数据库中的类型名称模式
|
||||
* 将MovingObject.ObjectType转换为数据库中的类型名称模式
|
||||
*/
|
||||
private String convertMovingObjectTypeToTypeName(MovingObjectType vehicleType) {
|
||||
switch (vehicleType) {
|
||||
case UNMANNED_VEHICLE:
|
||||
return "%无人车%";
|
||||
case AIRPORT_VEHICLE:
|
||||
return "%车"; // 匹配包含"车"但不包含"无人车"的类型
|
||||
case SPECIAL_VEHICLE:
|
||||
return "%特勤车%";
|
||||
case NORMAL_VEHICLE:
|
||||
return "%普通车"; // 匹配包含"车"但不包含"无人车"的类型
|
||||
case AIRCRAFT:
|
||||
return "%飞机%";
|
||||
default:
|
||||
@ -764,7 +757,7 @@ public class VehicleLocationService {
|
||||
}
|
||||
|
||||
SysVehicleInfo vehicleInfo = vehicleInfoOpt.get();
|
||||
MovingObjectType vehicleType = quapDataAdapter.convertToMovingObjectType(vehicleInfo.getTypeCode());
|
||||
MovingObjectType vehicleType = MovingObjectType.fromTypeCode(vehicleInfo.getTypeCode());
|
||||
|
||||
// 查询该位置适用的规则
|
||||
List<SpatialRule> applicableRules = locationRuleQueryService.findApplicableRules(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.common.service;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.model.enums.VehicleSubType;
|
||||
import com.qaup.collision.common.model.spatial.AirportArea;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -34,9 +34,9 @@ public class VehiclePermissionService {
|
||||
return checkUnmannedVehicleAccess(vehicleId, area);
|
||||
}
|
||||
|
||||
// 机场车辆需要进一步检查子类型
|
||||
if (vehicleType == MovingObjectType.AIRPORT_VEHICLE) {
|
||||
return checkAirportVehicleAccess(vehicleId, area);
|
||||
// 普通车辆需要进一步检查子类型
|
||||
if (vehicleType == MovingObjectType.NORMAL_VEHICLE) {
|
||||
return checkNormalVehicleAccess(vehicleId, area);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -48,9 +48,9 @@ public class VehiclePermissionService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查机场车辆的区域访问权限
|
||||
* 检查普通车辆的区域访问权限
|
||||
*/
|
||||
private boolean checkAirportVehicleAccess(Long vehicleId, AirportArea area) {
|
||||
private boolean checkNormalVehicleAccess(Long vehicleId, AirportArea area) {
|
||||
//VehicleSubType subType = VehicleSubType.inferFromVehicleId(vehicleId);
|
||||
VehicleSubType subType = VehicleSubType.AMBULANCE;
|
||||
|
||||
@ -63,7 +63,7 @@ public class VehiclePermissionService {
|
||||
return checkSpecialVehicleAccess(subType, area);
|
||||
}
|
||||
|
||||
// 一般机场车辆按区域类型和默认规则检查
|
||||
// 一般普通车辆按区域类型和默认规则检查
|
||||
return checkRegularVehicleAccess(subType, area);
|
||||
}
|
||||
|
||||
@ -176,8 +176,8 @@ public class VehiclePermissionService {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果对方是机场车辆,检查是否为特勤车辆
|
||||
if (otherVehicleType == MovingObjectType.AIRPORT_VEHICLE) {
|
||||
// 如果对方是普通车辆,检查是否为特勤车辆
|
||||
if (otherVehicleType == MovingObjectType.NORMAL_VEHICLE) {
|
||||
VehicleSubType otherSubType = VehicleSubType.AMBULANCE;
|
||||
if (otherSubType != null && otherSubType.hasSpecialRights()) {
|
||||
log.info("车辆 {} 需要为特勤车辆 {} 避让", thisVehicleId, otherSubType.getDisplayName());
|
||||
@ -203,8 +203,8 @@ public class VehiclePermissionService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 机场车辆中的特勤车辆不受红绿灯限制
|
||||
if (vehicleType == MovingObjectType.AIRPORT_VEHICLE) {
|
||||
// 普通车辆中的特勤车辆不受红绿灯限制
|
||||
if (vehicleType == MovingObjectType.NORMAL_VEHICLE) {
|
||||
VehicleSubType subType = VehicleSubType.AMBULANCE;
|
||||
if (subType != null && subType.hasSpecialRights()) {
|
||||
log.debug("特勤车辆 {} 无需遵守红绿灯", subType.getDisplayName());
|
||||
@ -228,12 +228,12 @@ public class VehiclePermissionService {
|
||||
return "特殊权限(特勤车辆)";
|
||||
}
|
||||
|
||||
if (vehicleType == MovingObjectType.AIRPORT_VEHICLE) {
|
||||
if (vehicleType == MovingObjectType.NORMAL_VEHICLE) {
|
||||
VehicleSubType subType = VehicleSubType.AMBULANCE;
|
||||
if (subType != null && subType.hasSpecialRights()) {
|
||||
return "特殊权限(" + subType.getDisplayName() + ")";
|
||||
}
|
||||
return "一般权限(机场车辆)";
|
||||
return "一般权限(普通车辆)";
|
||||
}
|
||||
|
||||
if (vehicleType == MovingObjectType.UNMANNED_VEHICLE) {
|
||||
|
||||
@ -4,9 +4,11 @@ package com.qaup.collision.datacollector.dao;
|
||||
import com.qaup.collision.common.model.Aircraft;
|
||||
import com.qaup.collision.common.model.AirportVehicle;
|
||||
import com.qaup.collision.common.model.UnmannedVehicle;
|
||||
import com.qaup.collision.common.model.MovingObject;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.model.dto.Response;
|
||||
import com.qaup.collision.datacollector.service.AuthService;
|
||||
import com.qaup.collision.datacollector.dto.AircraftRouteDTO;
|
||||
import com.qaup.collision.datacollector.dto.AircraftStatusDTO;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
@ -191,7 +193,7 @@ public class DataCollectorDao {
|
||||
unmannedVehicle.setCurrentSpeed(rawData.getSpeed() != null ? rawData.getSpeed() * 3.6 : 0.0); // m/s to km/h
|
||||
unmannedVehicle.setCurrentHeading(rawData.getDirection() != null ? Math.toDegrees(rawData.getDirection()) : 0.0); // 弧度 to 角度
|
||||
unmannedVehicle.setAltitude(0.0); // 假设默认高度为0,如果API有提供可以设置
|
||||
unmannedVehicle.setObjectType(MovingObject.ObjectType.UNMANNED_VEHICLE); // 显式设置对象类型
|
||||
unmannedVehicle.setObjectType(MovingObjectType.UNMANNED_VEHICLE); // 显式设置对象类型
|
||||
return unmannedVehicle;
|
||||
}).filter(java.util.Objects::nonNull).collect(Collectors.toList());
|
||||
|
||||
@ -259,4 +261,154 @@ public class DataCollectorDao {
|
||||
@JsonProperty("direction")
|
||||
private Double direction;
|
||||
}
|
||||
|
||||
// 新增航空器路由和状态数据采集方法
|
||||
|
||||
/**
|
||||
* 获取航空器进港路由
|
||||
*
|
||||
* @param inRunway 进港跑道编号
|
||||
* @param outRunway 出港跑道编号
|
||||
* @param contactCross 联络道口
|
||||
* @param seat 目的机位
|
||||
* @return 进港路由数据
|
||||
*/
|
||||
public AircraftRouteDTO getArrivalRoute(String inRunway, String outRunway, String contactCross, String seat) {
|
||||
try {
|
||||
String token = authService.getToken();
|
||||
if (token == null) {
|
||||
log.error("无法获取有效的认证token");
|
||||
return null;
|
||||
}
|
||||
|
||||
String url = UriComponentsBuilder.fromHttpUrl(vehicleBaseUrl)
|
||||
.path("/runwayPathPlanningController/findArrTaxiwayByRunwayAndContactCrossAndSeat")
|
||||
.queryParam("inRunway", inRunway)
|
||||
.queryParam("outRunway", outRunway)
|
||||
.queryParam("contactCross", contactCross)
|
||||
.queryParam("seat", seat)
|
||||
.toUriString();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", token);
|
||||
headers.set("Content-Type", "application/json");
|
||||
|
||||
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<Response<AircraftRouteDTO>> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
requestEntity,
|
||||
new ParameterizedTypeReference<Response<AircraftRouteDTO>>() {}
|
||||
);
|
||||
|
||||
Response<AircraftRouteDTO> responseBody = response.getBody();
|
||||
if (responseBody != null && responseBody.getStatus() == 200) {
|
||||
log.info("成功获取进港路由数据: inRunway={}, outRunway={}, contactCross={}, seat={}",
|
||||
inRunway, outRunway, contactCross, seat);
|
||||
return responseBody.getData();
|
||||
} else {
|
||||
log.warn("获取进港路由数据失败: {}", responseBody != null ? responseBody.getMsg() : "未知错误");
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取进港路由数据时发生异常: inRunway={}, outRunway={}, contactCross={}, seat={}",
|
||||
inRunway, outRunway, contactCross, seat, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取航空器出港路由
|
||||
*
|
||||
* @param inRunway 进港跑道编号
|
||||
* @param outRunway 出港跑道编号
|
||||
* @param startSeat 起始机位
|
||||
* @return 出港路由数据
|
||||
*/
|
||||
public AircraftRouteDTO getDepartureRoute(String inRunway, String outRunway, String startSeat) {
|
||||
try {
|
||||
String token = authService.getToken();
|
||||
if (token == null) {
|
||||
log.error("无法获取有效的认证token");
|
||||
return null;
|
||||
}
|
||||
|
||||
String url = UriComponentsBuilder.fromHttpUrl(vehicleBaseUrl)
|
||||
.path("/runwayPathPlanningController/findDepTaxiwayByRunwayAndContactCrossAndSeat")
|
||||
.queryParam("inRunway", inRunway)
|
||||
.queryParam("outRunway", outRunway)
|
||||
.queryParam("startSeat", startSeat)
|
||||
.toUriString();
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", token);
|
||||
headers.set("Content-Type", "application/json");
|
||||
|
||||
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<Response<AircraftRouteDTO>> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
requestEntity,
|
||||
new ParameterizedTypeReference<Response<AircraftRouteDTO>>() {}
|
||||
);
|
||||
|
||||
Response<AircraftRouteDTO> responseBody = response.getBody();
|
||||
if (responseBody != null && responseBody.getStatus() == 200) {
|
||||
log.info("成功获取出港路由数据: inRunway={}, outRunway={}, startSeat={}",
|
||||
inRunway, outRunway, startSeat);
|
||||
return responseBody.getData();
|
||||
} else {
|
||||
log.warn("获取出港路由数据失败: {}", responseBody != null ? responseBody.getMsg() : "未知错误");
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取出港路由数据时发生异常: inRunway={}, outRunway={}, startSeat={}",
|
||||
inRunway, outRunway, startSeat, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取航空器状态
|
||||
*
|
||||
* @return 航空器状态数据
|
||||
*/
|
||||
public AircraftStatusDTO getAircraftStatus() {
|
||||
try {
|
||||
String token = authService.getToken();
|
||||
if (token == null) {
|
||||
log.error("无法获取有效的认证token");
|
||||
return null;
|
||||
}
|
||||
|
||||
String url = vehicleBaseUrl + "/aircraftStatusController/getAircraftStatus";
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", token);
|
||||
headers.set("Content-Type", "application/json");
|
||||
|
||||
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<Response<AircraftStatusDTO>> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
requestEntity,
|
||||
new ParameterizedTypeReference<Response<AircraftStatusDTO>>() {}
|
||||
);
|
||||
|
||||
Response<AircraftStatusDTO> responseBody = response.getBody();
|
||||
if (responseBody != null && responseBody.getStatus() == 200) {
|
||||
log.info("成功获取航空器状态数据: {}", responseBody.getData().getFlightNo());
|
||||
return responseBody.getData();
|
||||
} else {
|
||||
log.warn("获取航空器状态数据失败: {}", responseBody != null ? responseBody.getMsg() : "未知错误");
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取航空器状态数据时发生异常", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,128 @@
|
||||
package com.qaup.collision.datacollector.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 航空器路由API响应DTO
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class AircraftRouteDTO {
|
||||
|
||||
/**
|
||||
* 路由类型 (IN: 进港, OUT: 出港)
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 路由状态 (COMPLETE: 完成, CALCULATING: 计算中, MODIFIED: 已修改)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 路由编码序列 (如: "F1,L4,138")
|
||||
*/
|
||||
private String codes;
|
||||
|
||||
/**
|
||||
* 路由几何图形 (通常为null,使用geoPath)
|
||||
*/
|
||||
private Object geometry;
|
||||
|
||||
/**
|
||||
* 路由路径 (GeoJSON格式)
|
||||
*/
|
||||
private GeoPath geoPath;
|
||||
|
||||
/**
|
||||
* GeoJSON路径数据
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class GeoPath {
|
||||
|
||||
/**
|
||||
* GeoJSON类型,通常为"FeatureCollection"
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 路径特征集合
|
||||
*/
|
||||
private List<Feature> features;
|
||||
}
|
||||
|
||||
/**
|
||||
* GeoJSON特征
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class Feature {
|
||||
|
||||
/**
|
||||
* 特征类型,通常为"Feature"
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 几何图形信息
|
||||
*/
|
||||
private Geometry geometry;
|
||||
|
||||
/**
|
||||
* 属性信息
|
||||
*/
|
||||
private Properties properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 几何图形信息
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class Geometry {
|
||||
|
||||
/**
|
||||
* 几何类型,通常为"LineString"
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 坐标数组 [[经度, 纬度], [经度, 纬度], ...]
|
||||
*/
|
||||
private List<List<Double>> coordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 属性信息
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class Properties {
|
||||
|
||||
/**
|
||||
* 路径段编码
|
||||
*/
|
||||
private String code;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
package com.qaup.collision.datacollector.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 航空器状态API响应DTO
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class AircraftStatusDTO {
|
||||
|
||||
/**
|
||||
* 进出港类型 (IN: 进港, OUT: 出港, ARRIVED: 到达)
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 航班号
|
||||
*/
|
||||
private String flightNo;
|
||||
|
||||
/**
|
||||
* 进港跑道编号
|
||||
*/
|
||||
private String inRunway;
|
||||
|
||||
/**
|
||||
* 出港跑道编号
|
||||
*/
|
||||
private String outRunway;
|
||||
|
||||
/**
|
||||
* 联络道口
|
||||
*/
|
||||
private String contactCross;
|
||||
|
||||
/**
|
||||
* 目的机位
|
||||
*/
|
||||
private String seat;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
private Long timestamp;
|
||||
}
|
||||
@ -1,11 +1,19 @@
|
||||
package com.qaup.collision.datacollector.service;
|
||||
|
||||
import com.qaup.collision.common.model.*;
|
||||
import com.qaup.collision.common.model.MovingObject;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.model.Aircraft;
|
||||
import com.qaup.collision.common.model.AirportVehicle;
|
||||
import com.qaup.collision.common.model.UnmannedVehicle;
|
||||
import com.qaup.collision.common.model.AircraftRoute;
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
import com.qaup.collision.common.service.VehicleLocationService;
|
||||
import com.qaup.collision.dataprocessing.service.SpeedCalculationService;
|
||||
import com.qaup.collision.datacollector.dao.DataCollectorDao;
|
||||
import com.qaup.collision.datacollector.dto.AircraftRouteDTO;
|
||||
import com.qaup.collision.datacollector.dto.AircraftStatusDTO;
|
||||
import com.qaup.collision.websocket.event.PositionUpdateEvent;
|
||||
import com.qaup.collision.websocket.event.AircraftRouteUpdateEvent;
|
||||
import com.qaup.collision.websocket.message.PositionUpdatePayload;
|
||||
import com.qaup.collision.pathconflict.service.PathConflictDetectionService;
|
||||
import com.qaup.collision.common.adapter.QuapDataAdapter;
|
||||
@ -97,11 +105,187 @@ public class DataCollectorService {
|
||||
private final Map<String, MovingObject> activeMovingObjectsCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 定时采集航空器数据
|
||||
* 采集航空器路由和状态数据
|
||||
*
|
||||
* 重构说明:
|
||||
* - 航空器数据仅用于实时处理,不存储到数据库
|
||||
* - 数据采集后直接用于碰撞检测等实时计算
|
||||
* 数据来源:机场系统API
|
||||
* - 获取航空器状态 (/aircraftStatusController/getAircraftStatus)
|
||||
* - 获取进港路由 (/runwayPathPlanningController/findArrTaxiwayByRunwayAndContactCrossAndSeat)
|
||||
* - 获取出港路由 (/runwayPathPlanningController/findDepTaxiwayByRunwayAndContactCrossAndSeat)
|
||||
*
|
||||
* 说明:此方法用于更新航空器的路由信息,支持CA3456生命周期模拟
|
||||
*/
|
||||
@Scheduled(fixedRateString = "${data.collector.route.interval:5000}")
|
||||
public void collectAircraftRouteAndStatus() {
|
||||
if (collectorDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取航空器状态
|
||||
AircraftStatusDTO aircraftStatus = dataCollectorDao.getAircraftStatus();
|
||||
if (aircraftStatus == null) {
|
||||
log.debug("未获取到航空器状态数据");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("获取到航空器状态: 航班号={}, 类型={}, 跑道={}-{}, 机位={}",
|
||||
aircraftStatus.getFlightNo(),
|
||||
aircraftStatus.getType(),
|
||||
aircraftStatus.getInRunway(),
|
||||
aircraftStatus.getOutRunway(),
|
||||
aircraftStatus.getSeat());
|
||||
|
||||
// 根据航空器状态类型获取相应的路由数据
|
||||
AircraftRouteDTO routeData = null;
|
||||
if ("IN".equals(aircraftStatus.getType())) {
|
||||
// 进港状态,获取进港路由
|
||||
routeData = dataCollectorDao.getArrivalRoute(
|
||||
aircraftStatus.getInRunway(),
|
||||
aircraftStatus.getOutRunway(),
|
||||
aircraftStatus.getContactCross(),
|
||||
aircraftStatus.getSeat()
|
||||
);
|
||||
} else if ("OUT".equals(aircraftStatus.getType())) {
|
||||
// 出港状态,获取出港路由
|
||||
routeData = dataCollectorDao.getDepartureRoute(
|
||||
aircraftStatus.getInRunway(),
|
||||
aircraftStatus.getOutRunway(),
|
||||
aircraftStatus.getSeat()
|
||||
);
|
||||
}
|
||||
|
||||
if (routeData != null) {
|
||||
log.info("获取到{}路由数据: 编码={}, 状态={}",
|
||||
routeData.getType(),
|
||||
routeData.getCodes(),
|
||||
routeData.getStatus());
|
||||
|
||||
// 转换DTO为航空器路由对象
|
||||
AircraftRoute aircraftRoute = convertToAircraftRoute(routeData);
|
||||
|
||||
if (aircraftRoute != null) {
|
||||
// 更新缓存中的航空器路由信息
|
||||
updateAircraftRouteInCache(aircraftStatus.getFlightNo(), aircraftRoute, aircraftStatus);
|
||||
|
||||
// 发布WebSocket事件通知前端路由更新
|
||||
publishAircraftRouteUpdateEvent(aircraftStatus.getFlightNo(), aircraftRoute, aircraftStatus);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("采集航空器路由和状态数据异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将AircraftRouteDTO转换为AircraftRoute对象
|
||||
*/
|
||||
private AircraftRoute convertToAircraftRoute(AircraftRouteDTO routeDTO) {
|
||||
if (routeDTO == null || routeDTO.getGeoPath() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建路由段列表
|
||||
List<AircraftRoute.RouteSegment> routeSegments = new ArrayList<>();
|
||||
|
||||
if (routeDTO.getGeoPath().getFeatures() != null) {
|
||||
for (AircraftRouteDTO.Feature feature : routeDTO.getGeoPath().getFeatures()) {
|
||||
if (feature.getGeometry() != null && feature.getGeometry().getCoordinates() != null) {
|
||||
// 转换坐标格式:从List<List<Double>>到List<Point>
|
||||
List<org.locationtech.jts.geom.Point> points = feature.getGeometry().getCoordinates().stream()
|
||||
.map(coord -> geometryFactory.createPoint(new org.locationtech.jts.geom.Coordinate(coord.get(0), coord.get(1))))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
AircraftRoute.RouteSegment segment = AircraftRoute.RouteSegment.builder()
|
||||
.code(feature.getProperties() != null ? feature.getProperties().getCode() : "")
|
||||
.coordinates(points)
|
||||
.build();
|
||||
routeSegments.add(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有坐标数据,创建LineString几何对象
|
||||
org.locationtech.jts.geom.LineString geometry = null;
|
||||
if (!routeSegments.isEmpty() && routeSegments.get(0).getCoordinates() != null) {
|
||||
List<org.locationtech.jts.geom.Point> points = routeSegments.get(0).getCoordinates();
|
||||
if (points.size() >= 2) {
|
||||
org.locationtech.jts.geom.Coordinate[] coords = points.stream()
|
||||
.map(point -> point.getCoordinate())
|
||||
.toArray(org.locationtech.jts.geom.Coordinate[]::new);
|
||||
geometry = geometryFactory.createLineString(coords);
|
||||
}
|
||||
}
|
||||
|
||||
return AircraftRoute.builder()
|
||||
.type(routeDTO.getType())
|
||||
.status(routeDTO.getStatus())
|
||||
.codes(routeDTO.getCodes())
|
||||
.geometry(geometry)
|
||||
.routeSegments(routeSegments)
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("转换航空器路由数据失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新缓存中的航空器路由信息
|
||||
*/
|
||||
private void updateAircraftRouteInCache(String flightNo, AircraftRoute route, AircraftStatusDTO status) {
|
||||
MovingObject cachedAircraft = activeMovingObjectsCache.get(flightNo);
|
||||
if (cachedAircraft != null && cachedAircraft instanceof Aircraft) {
|
||||
Aircraft aircraft = (Aircraft) cachedAircraft;
|
||||
|
||||
// 根据航空器状态更新路由
|
||||
if ("IN".equals(status.getType())) {
|
||||
aircraft.setArrivalRoute(route);
|
||||
aircraft.activateArrivalRoute();
|
||||
} else if ("OUT".equals(status.getType())) {
|
||||
aircraft.setDepartureRoute(route);
|
||||
aircraft.activateDepartureRoute();
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
activeMovingObjectsCache.put(flightNo, aircraft);
|
||||
|
||||
log.debug("更新航空器 {} 路由信息到缓存: 类型={}, 编码={}",
|
||||
flightNo, route.getType(), route.getCodes());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布航空器路由更新事件
|
||||
*/
|
||||
private void publishAircraftRouteUpdateEvent(String flightNo, AircraftRoute route, AircraftStatusDTO status) {
|
||||
try {
|
||||
// 创建路由更新事件
|
||||
AircraftRouteUpdateEvent routeUpdateEvent = AircraftRouteUpdateEvent.builder()
|
||||
.flightNo(flightNo)
|
||||
.routeType(route.getType())
|
||||
.routeStatus(route.getStatus())
|
||||
.routeCodes(route.getCodes())
|
||||
.aircraftStatus(status.getType())
|
||||
.routeGeometry(route.getGeometry() != null ? route.getGeometry().toText() : null)
|
||||
.timestamp(System.currentTimeMillis())
|
||||
.build();
|
||||
|
||||
// 发布WebSocket事件
|
||||
eventPublisher.publishEvent(routeUpdateEvent);
|
||||
|
||||
log.info("发布航空器路由更新事件: 航班号={}, 路由类型={}, 状态={}",
|
||||
flightNo, route.getType(), status.getType());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("发布航空器路由更新事件失败: flightNo={}", flightNo, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时采集航空器数据
|
||||
* - 不进行数据持久化
|
||||
*/
|
||||
@Scheduled(fixedRateString = "${data.collector.interval}")
|
||||
@ -145,7 +329,7 @@ public class DataCollectorService {
|
||||
|
||||
MovingObject movingObject = MovingObject.builder()
|
||||
.objectId(aircraft.getObjectId()) // Changed from getFlightNo()
|
||||
.objectType(MovingObject.ObjectType.AIRCRAFT)
|
||||
.objectType(MovingObjectType.AIRCRAFT)
|
||||
.objectName(aircraft.getObjectName()) // Changed from getFlightNo()
|
||||
.currentPosition(currentPosition)
|
||||
.currentSpeed(calculatedSpeed)
|
||||
@ -244,7 +428,7 @@ public class DataCollectorService {
|
||||
|
||||
MovingObject movingObject = MovingObject.builder()
|
||||
.objectId(vehicle.getObjectId())
|
||||
.objectType(MovingObject.ObjectType.SPECIAL_VEHICLE)
|
||||
.objectType(MovingObjectType.SPECIAL_VEHICLE)
|
||||
.objectName(vehicle.getObjectName())
|
||||
.currentPosition(currentPosition)
|
||||
.currentSpeed(calculatedSpeed)
|
||||
@ -345,7 +529,7 @@ public class DataCollectorService {
|
||||
|
||||
MovingObject movingObject = MovingObject.builder()
|
||||
.objectId(unmannedVehicle.getObjectId()) // Changed from getLicensePlate()
|
||||
.objectType(MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.objectName(unmannedVehicle.getObjectName()) // Changed from getLicensePlate()
|
||||
.currentPosition(currentPosition)
|
||||
.currentSpeed(calculatedSpeed)
|
||||
@ -442,7 +626,7 @@ public class DataCollectorService {
|
||||
for (VehicleLocation vehicleLocation : detectionVehicleLocations) {
|
||||
try {
|
||||
// 只处理无人车
|
||||
if (vehicleLocation.getVehicleType() == com.qaup.collision.common.model.MovingObjectType.UNMANNED_VEHICLE) {
|
||||
if (vehicleLocation.getVehicleType() == MovingObjectType.UNMANNED_VEHICLE) {
|
||||
// 构建车辆状态参数
|
||||
Map<String, Object> vehicleState = new HashMap<>();
|
||||
vehicleState.put("speed", vehicleLocation.getSpeed());
|
||||
@ -518,7 +702,7 @@ public class DataCollectorService {
|
||||
.heading(vehicle.getCurrentHeading())
|
||||
.timestamp(java.time.LocalDateTime.ofEpochSecond(System.currentTimeMillis() / 1000, 0, java.time.ZoneOffset.UTC))
|
||||
.licensePlate(sysVehicleInfo.getLicensePlate()) // 设置运行时属性 licensePlate
|
||||
.vehicleType(com.qaup.collision.common.model.MovingObjectType.UNMANNED_VEHICLE) // 从无人车接口获取的数据,直接设置为无人车类型
|
||||
.vehicleType(MovingObjectType.UNMANNED_VEHICLE) // 从无人车接口获取的数据,直接设置为无人车类型
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
log.error("转换无人车位置数据时发生未知错误: objectId={}", vehicle.getObjectId(), e);
|
||||
@ -538,9 +722,9 @@ public class DataCollectorService {
|
||||
try {
|
||||
Long vehicleIdForDetection;
|
||||
String licensePlateForDetection;
|
||||
com.qaup.collision.common.model.MovingObjectType objectTypeForDetection = com.qaup.collision.common.model.MovingObjectType.valueOf(movingObject.getObjectType().name());
|
||||
MovingObjectType objectTypeForDetection = MovingObjectType.valueOf(movingObject.getObjectType().name());
|
||||
|
||||
if (MovingObject.ObjectType.UNMANNED_VEHICLE.equals(movingObject.getObjectType())) {
|
||||
if (MovingObjectType.UNMANNED_VEHICLE.equals(movingObject.getObjectType())) {
|
||||
// 对于无人车,其objectId就是车牌号,可以从sys_vehicle_info中查到vehicleId
|
||||
Optional<SysVehicleInfo> vehicleInfoOptional = quapDataAdapter.findVehicleByLicensePlate(movingObject.getObjectId());
|
||||
if (vehicleInfoOptional.isEmpty()) {
|
||||
@ -640,7 +824,7 @@ public class DataCollectorService {
|
||||
|
||||
// 过滤出无人车对象
|
||||
List<MovingObject> unmannedVehicles = activeObjects.stream()
|
||||
.filter(obj -> obj.getObjectType() == MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.filter(obj -> obj.getObjectType() == MovingObjectType.UNMANNED_VEHICLE)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (unmannedVehicles.isEmpty()) {
|
||||
@ -662,7 +846,7 @@ public class DataCollectorService {
|
||||
.currentSpeed(unmannedVehicle.getCurrentSpeed())
|
||||
.currentHeading(unmannedVehicle.getCurrentHeading())
|
||||
.altitude(unmannedVehicle.getAltitude())
|
||||
.objectType(MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.build();
|
||||
|
||||
// 转换无人车数据为VehicleLocation
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.qaup.collision.datacollector.service;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
import com.qaup.collision.datacollector.model.converter.VehicleCommandConverter;
|
||||
import com.qaup.collision.datacollector.model.dto.VehicleCommand;
|
||||
@ -328,16 +327,18 @@ public class UnmannedVehicleControlService {
|
||||
private String transId;
|
||||
private long timestamp;
|
||||
private String vehicleId;
|
||||
private boolean isSingle;
|
||||
private boolean single;
|
||||
|
||||
// Getters and Setters
|
||||
public String getTransId() { return transId; }
|
||||
public void setTransId(String transId) { this.transId = transId; }
|
||||
|
||||
public long getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
|
||||
|
||||
public String getVehicleId() { return vehicleId; }
|
||||
public void setVehicleId(String vehicleId) { this.vehicleId = vehicleId; }
|
||||
public boolean isSingle() { return isSingle; }
|
||||
public void setSingle(boolean single) { isSingle = single; }
|
||||
|
||||
public boolean isSingle() { return single; }
|
||||
public void setSingle(boolean single) { this.single = single; }
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.datacollector.service;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
import com.qaup.collision.common.service.VehicleLocationService;
|
||||
import com.qaup.collision.datacollector.model.entity.VehicleCommandEntity;
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package com.qaup.collision.dataprocessing.service;
|
||||
|
||||
import com.qaup.common.utils.GeoUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
@Service
|
||||
@ -191,19 +191,7 @@ public class SpeedCalculationService {
|
||||
* @return 距离(米)
|
||||
*/
|
||||
private double calculateGeographicDistance(double lat1, double lon1, double lat2, double lon2) {
|
||||
final double R = 6371000; // 地球半径(米)
|
||||
|
||||
double lat1Rad = Math.toRadians(lat1);
|
||||
double lat2Rad = Math.toRadians(lat2);
|
||||
double deltaLatRad = Math.toRadians(lat2 - lat1);
|
||||
double deltaLonRad = Math.toRadians(lon2 - lon1);
|
||||
|
||||
double a = Math.sin(deltaLatRad / 2) * Math.sin(deltaLatRad / 2) +
|
||||
Math.cos(lat1Rad) * Math.cos(lat2Rad) *
|
||||
Math.sin(deltaLonRad / 2) * Math.sin(deltaLonRad / 2);
|
||||
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
|
||||
return R * c;
|
||||
return GeoUtils.calculateDistance(lat1, lon1, lat2, lon2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,6 +3,7 @@ package com.qaup.collision.geofence.service;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qaup.collision.common.model.MovingObject;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.model.spatial.AirportArea;
|
||||
import com.qaup.collision.common.service.AirportAreaService;
|
||||
import com.qaup.collision.geofence.model.dto.GeofenceAccessCheckResult;
|
||||
@ -57,7 +58,7 @@ public class UnmannedVehicleGeofenceService {
|
||||
*/
|
||||
public GeofenceAccessCheckResult checkAreaAccess(MovingObject vehicle, String areaCode) {
|
||||
// 前置检查:必须是无人车
|
||||
if (vehicle.getObjectType() != MovingObject.ObjectType.UNMANNED_VEHICLE) {
|
||||
if (vehicle.getObjectType() != MovingObjectType.UNMANNED_VEHICLE) {
|
||||
log.debug("跳过非无人车的围栏检测: {} (类型: {})", vehicle.getObjectId(), vehicle.getObjectType());
|
||||
return GeofenceAccessCheckResult.skipped(vehicle.getObjectId());
|
||||
}
|
||||
@ -103,7 +104,7 @@ public class UnmannedVehicleGeofenceService {
|
||||
List<GeofenceAccessCheckResult> results = new ArrayList<>();
|
||||
|
||||
// 类型检查:只处理无人车
|
||||
if (vehicle.getObjectType() != MovingObject.ObjectType.UNMANNED_VEHICLE) {
|
||||
if (vehicle.getObjectType() != MovingObjectType.UNMANNED_VEHICLE) {
|
||||
log.debug("跳过非无人车位置检测: {}", vehicle.getObjectId());
|
||||
results.add(GeofenceAccessCheckResult.skipped(vehicle.getObjectId()));
|
||||
return results;
|
||||
@ -165,7 +166,7 @@ public class UnmannedVehicleGeofenceService {
|
||||
}
|
||||
|
||||
// 检查车辆类型是否被允许
|
||||
if (!rule.isVehicleTypeAllowed(vehicle.getObjectType().toMovingObjectType())) {
|
||||
if (!rule.isVehicleTypeAllowed(vehicle.getObjectType())) {
|
||||
log.debug("规则 {} 不允许车辆类型 {}", rule.getRuleId(), vehicle.getObjectType());
|
||||
continue;
|
||||
}
|
||||
@ -342,8 +343,6 @@ public class UnmannedVehicleGeofenceService {
|
||||
* 检查时间限制
|
||||
*/
|
||||
private RuleCheckResult checkTimeRestrictions(UnmannedVehicleGeofenceRuleConfig.TimeRestrictionsConfig config) {
|
||||
LocalTime now = LocalTime.now();
|
||||
|
||||
// 检查活跃时间段
|
||||
if (config.getActiveHours() != null) {
|
||||
// TODO: 解析时间段格式 "06:00-22:00" 并检查
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
package com.qaup.collision.pathconflict.model.dto;
|
||||
|
||||
import com.qaup.collision.pathconflict.model.entity.ConflictAlertLog;
|
||||
import com.qaup.collision.common.model.MovingObject;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
@ -20,9 +21,9 @@ public class ConflictAlertEvent {
|
||||
private Optional<ConflictAlertLog.AlertLevel> alertLevel; // 告警级别
|
||||
private String message; // 告警消息
|
||||
private String object1Name; // 对象1名称
|
||||
private MovingObject.ObjectType object1Type; // 对象1类型
|
||||
private MovingObjectType object1Type; // 对象1类型
|
||||
private String object2Name; // 对象2名称
|
||||
private MovingObject.ObjectType object2Type; // 对象2类型
|
||||
private MovingObjectType object2Type; // 对象2类型
|
||||
private Point conflictPoint; // 冲突点
|
||||
private Double object1Distance; // 对象1距离冲突点距离 (米)
|
||||
private Double object2Distance; // 对象2距离冲突点距离 (米)
|
||||
|
||||
@ -3,6 +3,7 @@ package com.qaup.collision.pathconflict.service;
|
||||
import com.qaup.collision.pathconflict.model.entity.TransportRoute;
|
||||
import com.qaup.collision.pathconflict.repository.TransportRouteRepository;
|
||||
import com.qaup.collision.common.model.MovingObject;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.pathconflict.model.dto.ConflictAlertEvent;
|
||||
import com.qaup.collision.pathconflict.model.entity.ObjectRouteAssignment;
|
||||
import com.qaup.collision.pathconflict.repository.ObjectRouteAssignmentRepository;
|
||||
@ -84,7 +85,7 @@ public class PathConflictDetectionService {
|
||||
* 检测两个对象之间的路径冲突
|
||||
*/
|
||||
private Optional<ConflictAlertEvent> detectConflictBetweenObjects(MovingObject obj1, MovingObject obj2) {
|
||||
if (obj1.getObjectType() != MovingObject.ObjectType.UNMANNED_VEHICLE && obj2.getObjectType() != MovingObject.ObjectType.UNMANNED_VEHICLE) {
|
||||
if (obj1.getObjectType() != MovingObjectType.UNMANNED_VEHICLE && obj2.getObjectType() != MovingObjectType.UNMANNED_VEHICLE) {
|
||||
log.debug("对象 {} 和 {} 都不是无人车,跳过冲突检测", obj1.getObjectName(), obj2.getObjectName());
|
||||
return Optional.empty();
|
||||
}
|
||||
@ -247,12 +248,12 @@ public class PathConflictDetectionService {
|
||||
* 评估冲突的告警级别
|
||||
*/
|
||||
private Optional<ConflictAlertLog.AlertLevel> evaluateAlertLevel(
|
||||
double distance1, MovingObject.ObjectType obj1Type,
|
||||
double distance2, MovingObject.ObjectType obj2Type,
|
||||
double distance1, MovingObjectType obj1Type,
|
||||
double distance2, MovingObjectType obj2Type,
|
||||
double timeGap) {
|
||||
|
||||
boolean obj1IsUnmannedVehicle = MovingObject.ObjectType.UNMANNED_VEHICLE.equals(obj1Type);
|
||||
boolean obj2IsUnmannedVehicle = MovingObject.ObjectType.UNMANNED_VEHICLE.equals(obj2Type);
|
||||
boolean obj1IsUnmannedVehicle = MovingObjectType.UNMANNED_VEHICLE.equals(obj1Type);
|
||||
boolean obj2IsUnmannedVehicle = MovingObjectType.UNMANNED_VEHICLE.equals(obj2Type);
|
||||
|
||||
if (!obj1IsUnmannedVehicle && !obj2IsUnmannedVehicle) {
|
||||
// 双方都不是无人车,根据业务规则,不生成告警。
|
||||
@ -429,16 +430,16 @@ public class PathConflictDetectionService {
|
||||
private final double timeGap;
|
||||
private final Optional<ConflictAlertLog.AlertType> alertType; // Changed to Optional
|
||||
private final Optional<ConflictAlertLog.AlertLevel> alertLevel; // Changed to Optional
|
||||
private final MovingObject.ObjectType object1Type; // New
|
||||
private final MovingObject.ObjectType object2Type; // New
|
||||
private final MovingObjectType object1Type; // New
|
||||
private final MovingObjectType object2Type; // New
|
||||
|
||||
public ConflictCalculationResult(double distance1, double distance2,
|
||||
int timeToConflict1, int timeToConflict2,
|
||||
double timeGap,
|
||||
Optional<ConflictAlertLog.AlertType> alertType, // Changed to Optional
|
||||
Optional<ConflictAlertLog.AlertLevel> alertLevel, // Changed to Optional
|
||||
MovingObject.ObjectType object1Type, // New
|
||||
MovingObject.ObjectType object2Type) { // New
|
||||
MovingObjectType object1Type, // New
|
||||
MovingObjectType object2Type) { // New
|
||||
this.distance1 = distance1;
|
||||
this.distance2 = distance2;
|
||||
this.timeToConflict1 = timeToConflict1;
|
||||
@ -457,7 +458,7 @@ public class PathConflictDetectionService {
|
||||
public double getTimeGap() { return timeGap; }
|
||||
public Optional<ConflictAlertLog.AlertType> getAlertType() { return alertType; } // Changed
|
||||
public Optional<ConflictAlertLog.AlertLevel> getAlertLevel() { return alertLevel; } // Changed
|
||||
public MovingObject.ObjectType getObject1Type() { return object1Type; } // New
|
||||
public MovingObject.ObjectType getObject2Type() { return object2Type; } // New
|
||||
public MovingObjectType getObject1Type() { return object1Type; } // New
|
||||
public MovingObjectType getObject2Type() { return object2Type; } // New
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.rule.model.entity;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.rule.model.enums.RuleAlertLevel;
|
||||
import com.qaup.collision.rule.model.enums.RuleCategory;
|
||||
import com.qaup.collision.rule.model.enums.RuleStatus;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.rule.service;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
import com.qaup.collision.rule.model.entity.SpatialRule;
|
||||
import com.qaup.collision.rule.model.enums.RuleCategory;
|
||||
@ -167,7 +167,7 @@ public interface LocationRuleQueryService {
|
||||
* @param timestamp 时间戳
|
||||
* @return 如果有权限返回true
|
||||
*/
|
||||
boolean hasAccessPermission(org.locationtech.jts.geom.Point location, com.qaup.collision.common.model.MovingObjectType vehicleType, java.time.LocalDateTime timestamp);
|
||||
boolean hasAccessPermission(Point location, MovingObjectType vehicleType, LocalDateTime timestamp);
|
||||
|
||||
/**
|
||||
* 获取车辆类型被禁止访问的规则列表
|
||||
@ -176,7 +176,7 @@ public interface LocationRuleQueryService {
|
||||
* @param timestamp 时间戳
|
||||
* @return 被禁止的规则列表
|
||||
*/
|
||||
java.util.List<com.qaup.collision.rule.model.entity.SpatialRule> getRestrictedRules(org.locationtech.jts.geom.Point location, com.qaup.collision.common.model.MovingObjectType vehicleType, java.time.LocalDateTime timestamp);
|
||||
List<SpatialRule> getRestrictedRules(Point location, MovingObjectType vehicleType, LocalDateTime timestamp);
|
||||
|
||||
/**
|
||||
* 获取车辆类型允许访问的规则列表
|
||||
@ -185,7 +185,7 @@ public interface LocationRuleQueryService {
|
||||
* @param timestamp 时间戳
|
||||
* @return 允许的规则列表
|
||||
*/
|
||||
java.util.List<com.qaup.collision.rule.model.entity.SpatialRule> getAllowedRules(org.locationtech.jts.geom.Point location, com.qaup.collision.common.model.MovingObjectType vehicleType, java.time.LocalDateTime timestamp);
|
||||
List<SpatialRule> getAllowedRules(Point location, MovingObjectType vehicleType, LocalDateTime timestamp);
|
||||
|
||||
/**
|
||||
* 规则覆盖统计信息
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
package com.qaup.collision.rule.service;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
import com.qaup.collision.rule.event.RuleViolationEvent;
|
||||
import com.qaup.collision.rule.event.RuleViolationEvent;
|
||||
import com.qaup.collision.rule.model.entity.SpatialRule;
|
||||
import com.qaup.collision.rule.model.enums.RuleAlertLevel;
|
||||
import com.qaup.collision.rule.model.enums.ViolationType;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.rule.service;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.rule.event.RuleViolationEvent;
|
||||
import com.qaup.collision.rule.model.entity.SpatialRule;
|
||||
import com.qaup.collision.rule.model.enums.RuleExecutionResult;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.rule.service;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.rule.model.entity.SpatialRule;
|
||||
import com.qaup.collision.rule.model.enums.RuleCategory;
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.rule.service;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.rule.model.entity.SpatialRule;
|
||||
import com.qaup.collision.rule.model.enums.RuleCategory;
|
||||
import com.qaup.collision.rule.model.enums.RuleStatus;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.rule.service;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.rule.model.entity.SpatialRule;
|
||||
import com.qaup.collision.rule.model.enums.RuleCategory;
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
package com.qaup.collision.rule.service.impl;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
import com.qaup.collision.common.model.spatial.AirportArea;
|
||||
import com.qaup.collision.common.service.AirportAreaService;
|
||||
import com.qaup.collision.road.service.RoadNetworkService;
|
||||
import com.qaup.collision.road.model.RoadInfo;
|
||||
import com.qaup.collision.common.model.GeoPosition;
|
||||
import com.qaup.collision.rule.model.entity.SpatialRule;
|
||||
import com.qaup.collision.rule.model.enums.RuleCategory;
|
||||
import com.qaup.collision.rule.model.enums.RuleStatus;
|
||||
@ -16,6 +16,7 @@ import com.qaup.collision.rule.service.RulePriorityService;
|
||||
import com.qaup.collision.rule.service.SpatialRuleService;
|
||||
import com.qaup.collision.rule.service.TimeWindowMatchingService;
|
||||
import com.qaup.collision.rule.service.VehicleTypePermissionService;
|
||||
import com.qaup.common.constant.GeoConstants;
|
||||
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
@ -367,7 +368,7 @@ public class LocationRuleQueryServiceImpl implements LocationRuleQueryService {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return ruleGeometry.distance(location) * 111320; // 转换为米(近似)
|
||||
return ruleGeometry.distance(location) * GeoConstants.DEGREE_TO_METER; // 转换为米(近似)
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("计算到规则距离失败: ruleId={}", rule.getRuleId(), e);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.rule.service.impl;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
import com.qaup.collision.rule.event.RuleViolationEvent;
|
||||
import com.qaup.collision.rule.event.RuleViolationEventOccurred; // 导入RuleViolationEventOccurred
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.rule.service.impl;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.rule.event.RuleViolationEvent;
|
||||
import com.qaup.collision.rule.model.entity.SpatialRule;
|
||||
import com.qaup.collision.rule.model.enums.RuleCategory;
|
||||
@ -560,13 +560,6 @@ public class RuleExecutionEngineImpl implements RuleExecutionEngine {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成违规描述(为了兼容接口)
|
||||
*/
|
||||
private String generateViolationDescription(SpatialRule rule, Object result, Map<String, Object> vehicleState) {
|
||||
return generateSimpleDescription(rule, vehicleState);
|
||||
}
|
||||
|
||||
// ===============================================
|
||||
// 统计和查询方法(无人车专用)
|
||||
// ===============================================
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.qaup.collision.rule.service.impl;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.rule.model.entity.SpatialRule;
|
||||
import com.qaup.collision.rule.model.enums.RuleCategory;
|
||||
import com.qaup.collision.rule.service.RulePriorityService;
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package com.qaup.collision.rule.service.impl;
|
||||
|
||||
import com.qaup.collision.rule.event.RuleViolationEvent;
|
||||
import com.qaup.collision.rule.event.RuleEventPublisher;
|
||||
import com.qaup.collision.rule.event.RuleViolationEventOccurred;
|
||||
import com.qaup.collision.rule.model.entity.SpatialRule;
|
||||
import com.qaup.collision.rule.model.enums.RuleExecutionResult;
|
||||
import com.qaup.collision.rule.model.enums.ViolationType;
|
||||
@ -10,11 +8,9 @@ import com.qaup.collision.rule.repository.RuleViolationEventRepository;
|
||||
import com.qaup.collision.rule.service.RuleViolationProcessor;
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -40,15 +36,6 @@ public class RuleViolationProcessorImpl implements RuleViolationProcessor {
|
||||
@Autowired
|
||||
private RuleViolationEventRepository violationEventRepository;
|
||||
|
||||
@Autowired
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@Autowired
|
||||
private RuleEventPublisher ruleEventPublisher;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
// ============================================
|
||||
// 违规事件生成
|
||||
// ============================================
|
||||
@ -191,7 +178,6 @@ public class RuleViolationProcessorImpl implements RuleViolationProcessor {
|
||||
return false;
|
||||
}
|
||||
|
||||
RuleViolationEvent event = eventOpt.get();
|
||||
// 简化版本:只记录日志,不修改复杂状态
|
||||
logger.info("违规事件已确认: eventId={}, acknowledgedBy={}, notes={}", eventId, acknowledgedBy, notes);
|
||||
return true;
|
||||
@ -211,7 +197,6 @@ public class RuleViolationProcessorImpl implements RuleViolationProcessor {
|
||||
return false;
|
||||
}
|
||||
|
||||
RuleViolationEvent event = eventOpt.get();
|
||||
// 简化版本:只记录日志,不修改复杂状态
|
||||
logger.info("违规事件已解决: eventId={}, resolvedBy={}, resolution={}", eventId, resolvedBy, resolution);
|
||||
return true;
|
||||
@ -231,7 +216,6 @@ public class RuleViolationProcessorImpl implements RuleViolationProcessor {
|
||||
return false;
|
||||
}
|
||||
|
||||
RuleViolationEvent event = eventOpt.get();
|
||||
// 简化版本:只记录日志,不修改复杂状态
|
||||
logger.info("违规事件已忽略: eventId={}, ignoredBy={}, reason={}", eventId, ignoredBy, reason);
|
||||
return true;
|
||||
@ -415,17 +399,6 @@ public class RuleViolationProcessorImpl implements RuleViolationProcessor {
|
||||
&& event.getLocation() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证违规事件数据完整性(基于vehicle_id)
|
||||
*/
|
||||
private boolean isValidViolationEvent(RuleViolationEvent event) {
|
||||
return event != null
|
||||
&& event.getId() != null
|
||||
&& event.getRuleName() != null
|
||||
&& event.getVehicleId() != null // 基于vehicle_id验证
|
||||
&& event.getLocation() != null;
|
||||
}
|
||||
|
||||
private List<String> executeResponseActions(RuleViolationEvent event) {
|
||||
List<String> actionsExecuted = new ArrayList<>();
|
||||
|
||||
@ -448,19 +421,4 @@ public class RuleViolationProcessorImpl implements RuleViolationProcessor {
|
||||
return actionsExecuted;
|
||||
}
|
||||
|
||||
private void publishViolationEvent(RuleViolationEvent event) {
|
||||
try {
|
||||
// 使用统一的事件发布器发布违规事件(用于审计日志)
|
||||
ruleEventPublisher.publishViolationEvent(event, this,
|
||||
String.format("违规处理器处理事件: %s", event.getId()));
|
||||
|
||||
// 发布WebSocket事件
|
||||
RuleViolationEventOccurred webSocketEvent = new RuleViolationEventOccurred(this, event);
|
||||
eventPublisher.publishEvent(webSocketEvent);
|
||||
logger.info("违规事件已发布到WebSocket: eventId={}", event.getId());
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("发布违规事件失败: eventId={}", event.getId(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package com.qaup.collision.rule.service.impl;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.model.spatial.AirportArea;
|
||||
import com.qaup.collision.common.service.AirportAreaService;
|
||||
import com.qaup.collision.rule.event.RuleEventPublisher;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.rule.service.impl;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.rule.model.entity.SpatialRule;
|
||||
import com.qaup.collision.rule.model.enums.RuleCategory;
|
||||
import com.qaup.collision.rule.service.VehicleTypePermissionService;
|
||||
@ -30,7 +30,7 @@ public class VehicleTypePermissionServiceImpl implements VehicleTypePermissionSe
|
||||
private static final Map<MovingObjectType, Integer> PERMISSION_LEVELS = Map.of(
|
||||
MovingObjectType.AIRCRAFT, 1, // 最高权限:航空器
|
||||
MovingObjectType.SPECIAL_VEHICLE, 2, // 特殊权限:特勤车辆
|
||||
MovingObjectType.AIRPORT_VEHICLE, 3, // 中等权限:机场车辆
|
||||
MovingObjectType.NORMAL_VEHICLE, 3, // 中等权限:普通车辆
|
||||
MovingObjectType.UNMANNED_VEHICLE, 4 // 受控权限:无人车
|
||||
);
|
||||
|
||||
@ -38,7 +38,7 @@ public class VehicleTypePermissionServiceImpl implements VehicleTypePermissionSe
|
||||
private static final Map<MovingObjectType, Integer> PRIORITY_LEVELS = Map.of(
|
||||
MovingObjectType.AIRCRAFT, 1, // 最高优先级:航空器
|
||||
MovingObjectType.SPECIAL_VEHICLE, 2, // 特殊优先级:特勤车辆
|
||||
MovingObjectType.AIRPORT_VEHICLE, 3, // 中等优先级:机场车辆
|
||||
MovingObjectType.NORMAL_VEHICLE, 3, // 中等优先级:普通车辆
|
||||
MovingObjectType.UNMANNED_VEHICLE, 4 // 低优先级:无人车
|
||||
);
|
||||
|
||||
@ -52,7 +52,7 @@ public class VehicleTypePermissionServiceImpl implements VehicleTypePermissionSe
|
||||
private static final Map<MovingObjectType, Set<String>> ACCESSIBLE_AREA_TYPES = Map.of(
|
||||
MovingObjectType.AIRCRAFT, Set.of("RUNWAY", "TAXIWAY", "APRON", "GATE", "RESTRICTED", "MAINTENANCE", "CARGO", "SAFETY", "SERVICE"),
|
||||
MovingObjectType.SPECIAL_VEHICLE, Set.of("TAXIWAY", "APRON", "GATE", "RESTRICTED", "MAINTENANCE", "CARGO", "SAFETY", "SERVICE"),
|
||||
MovingObjectType.AIRPORT_VEHICLE, Set.of("APRON", "GATE", "MAINTENANCE", "CARGO", "SERVICE", "TAXIWAY"),
|
||||
MovingObjectType.NORMAL_VEHICLE, Set.of("APRON", "GATE", "MAINTENANCE", "CARGO", "SERVICE", "TAXIWAY"),
|
||||
MovingObjectType.UNMANNED_VEHICLE, Set.of("APRON", "MAINTENANCE", "CARGO", "SERVICE")
|
||||
);
|
||||
|
||||
@ -149,8 +149,8 @@ public class VehicleTypePermissionServiceImpl implements VehicleTypePermissionSe
|
||||
// 特勤车辆24小时可访问(紧急需要)
|
||||
return true;
|
||||
|
||||
case AIRPORT_VEHICLE:
|
||||
// 机场车辆一般在05:00-23:00可访问
|
||||
case NORMAL_VEHICLE:
|
||||
// 普通车辆一般在05:00-23:00可访问
|
||||
return !currentTime.isBefore(LocalTime.of(5, 0)) &&
|
||||
!currentTime.isAfter(LocalTime.of(23, 0));
|
||||
|
||||
@ -193,14 +193,14 @@ public class VehicleTypePermissionServiceImpl implements VehicleTypePermissionSe
|
||||
"特勤车辆具有特殊权限,可紧急通行大部分区域"
|
||||
);
|
||||
|
||||
case AIRPORT_VEHICLE:
|
||||
case NORMAL_VEHICLE:
|
||||
return new VehicleAccessRestriction(
|
||||
vehicleType,
|
||||
Set.of(RuleCategory.PRIORITY_CONTROL), // 限制优先级控制
|
||||
Set.of("RUNWAY", "RESTRICTED"), // 限制跑道和受限区域
|
||||
true, // 需要特殊批准访问某些区域
|
||||
80, // 最大速度80km/h
|
||||
"机场车辆有限制访问,不能进入跑道和受限区域"
|
||||
"普通车辆有限制访问,不能进入跑道和受限区域"
|
||||
);
|
||||
|
||||
case UNMANNED_VEHICLE:
|
||||
@ -299,25 +299,4 @@ public class VehicleTypePermissionServiceImpl implements VehicleTypePermissionSe
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 私有辅助方法
|
||||
// ============================================
|
||||
|
||||
private boolean isAccessProhibited(SpatialRule rule, MovingObjectType vehicleType) {
|
||||
try {
|
||||
// 检查规则参数中是否有明确的禁止配置
|
||||
if (rule.getParameters() != null) {
|
||||
// 这里可以解析JSON参数,检查是否有禁止访问的配置
|
||||
// 暂时使用简单逻辑:如果不在允许列表中则认为被禁止
|
||||
return !isVehicleTypeInAllowedList(rule, vehicleType);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("检查访问禁止状态失败: ruleId={}, vehicleType={}", rule.getRuleId(), vehicleType, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,9 +14,8 @@ import com.qaup.collision.websocket.message.RuleExecutionStatusPayload;
|
||||
import com.qaup.collision.websocket.message.RuleStateChangePayload;
|
||||
import com.qaup.collision.websocket.message.RuleViolationPayload;
|
||||
import com.qaup.collision.websocket.message.PathConflictAlertMessage;
|
||||
import com.qaup.collision.common.adapter.QuapDataAdapter; // 导入 QuapDataAdapter
|
||||
import com.qaup.system.domain.SysVehicleInfo; // 导入 SysVehicleInfo
|
||||
import org.locationtech.jts.io.WKTWriter;
|
||||
import com.qaup.collision.common.adapter.QuapDataAdapter;
|
||||
import com.qaup.system.domain.SysVehicleInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
@ -42,7 +41,7 @@ public class RuleEventWebSocketPublisher {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RuleEventWebSocketPublisher.class);
|
||||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
private final QuapDataAdapter quapDataAdapter; // 注入 QuapDataAdapter
|
||||
private final QuapDataAdapter quapDataAdapter;
|
||||
|
||||
// 构造函数中初始化ObjectMapper
|
||||
public RuleEventWebSocketPublisher(ApplicationEventPublisher eventPublisher, QuapDataAdapter quapDataAdapter) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.qaup.collision.websocket.controller;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
import com.qaup.collision.common.service.VehicleLocationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
package com.qaup.collision.websocket.controller;
|
||||
|
||||
public class MovingObjectType {
|
||||
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package com.qaup.collision.websocket.event;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 航空器路由更新事件
|
||||
*
|
||||
* 用于通知前端航空器路由信息的变更
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AircraftRouteUpdateEvent {
|
||||
|
||||
/**
|
||||
* 航班号
|
||||
*/
|
||||
private String flightNo;
|
||||
|
||||
/**
|
||||
* 路由类型 (IN: 进港, OUT: 出港)
|
||||
*/
|
||||
private String routeType;
|
||||
|
||||
/**
|
||||
* 路由状态 (COMPLETE: 完成, CALCULATING: 计算中, MODIFIED: 已修改)
|
||||
*/
|
||||
private String routeStatus;
|
||||
|
||||
/**
|
||||
* 路由编码 (如: "F1,L4,138")
|
||||
*/
|
||||
private String routeCodes;
|
||||
|
||||
/**
|
||||
* 航空器状态 (IN: 进港, OUT: 出港, ARRIVED: 到达)
|
||||
*/
|
||||
private String aircraftStatus;
|
||||
|
||||
/**
|
||||
* 路由几何数据 (WKT格式)
|
||||
*/
|
||||
private String routeGeometry;
|
||||
|
||||
/**
|
||||
* 事件时间戳
|
||||
*/
|
||||
private Long timestamp;
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
package com.qaup.collision.websocket.listener;
|
||||
|
||||
import com.qaup.collision.websocket.event.AircraftRouteUpdateEvent;
|
||||
import com.qaup.collision.websocket.handler.CollisionWebSocketHandler;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 航空器路由更新事件监听器
|
||||
*
|
||||
* 监听航空器路由更新事件,并通过WebSocket推送给前端客户端
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @version 1.0
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AircraftRouteUpdateEventListener {
|
||||
|
||||
@Autowired
|
||||
private CollisionWebSocketHandler webSocketHandler;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 处理航空器路由更新事件
|
||||
*
|
||||
* @param event 航空器路由更新事件
|
||||
*/
|
||||
@EventListener
|
||||
public void handleAircraftRouteUpdateEvent(AircraftRouteUpdateEvent event) {
|
||||
try {
|
||||
// 构建WebSocket消息
|
||||
String message = objectMapper.writeValueAsString(new WebSocketMessage(
|
||||
"aircraftRouteUpdate",
|
||||
event,
|
||||
System.currentTimeMillis()
|
||||
));
|
||||
|
||||
// 广播消息给所有连接的客户端
|
||||
webSocketHandler.broadcastMessage(message);
|
||||
|
||||
log.info("航空器路由更新事件已通过WebSocket推送: 航班号={}, 路由类型={}, 状态={}",
|
||||
event.getFlightNo(), event.getRouteType(), event.getAircraftStatus());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理航空器路由更新事件失败: flightNo={}", event.getFlightNo(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket消息包装类
|
||||
*/
|
||||
private static class WebSocketMessage {
|
||||
public final String type;
|
||||
public final Object data;
|
||||
public final long timestamp;
|
||||
|
||||
public WebSocketMessage(String type, Object data, long timestamp) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,8 @@ import com.qaup.system.service.ISysDriverInfoService;
|
||||
import com.qaup.system.service.ISysVehicleInfoService;
|
||||
import com.qaup.system.service.ISysVehicleTypeService;
|
||||
import com.qaup.collision.common.model.spatial.VehicleLocation;
|
||||
import com.qaup.collision.common.model.MovingObjectType;
|
||||
import com.qaup.collision.common.model.MovingObject;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -283,38 +284,6 @@ class QuapDataAdapterTest {
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConvertToMovingObjectType_TypeCode() {
|
||||
// 测试各种类型编码映射(根据QuapDataAdapter的实际实现)
|
||||
assertEquals(MovingObjectType.NORMAL_VEHICLE,
|
||||
quapDataAdapter.convertToMovingObjectType("NM")); // NM -> NORMAL_VEHICLE
|
||||
assertEquals(MovingObjectType.UNMANNED_VEHICLE,
|
||||
quapDataAdapter.convertToMovingObjectType("UV")); // UV -> UNMANNED_VEHICLE
|
||||
assertEquals(MovingObjectType.SPECIAL_VEHICLE,
|
||||
quapDataAdapter.convertToMovingObjectType("SP")); // SP -> SPECIAL_VEHICLE
|
||||
assertEquals(MovingObjectType.UNKNOWN,
|
||||
quapDataAdapter.convertToMovingObjectType("UNKNOWN")); // 未知类型
|
||||
assertEquals(MovingObjectType.UNKNOWN,
|
||||
quapDataAdapter.convertToMovingObjectType((String) null)); // null
|
||||
assertEquals(MovingObjectType.UNKNOWN,
|
||||
quapDataAdapter.convertToMovingObjectType("")); // 空字符串
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void testConvertToMovingObjectType_TypeId_Deprecated() {
|
||||
// 测试已废弃的类型ID映射方法
|
||||
assertEquals(MovingObjectType.NORMAL_VEHICLE,
|
||||
quapDataAdapter.convertToMovingObjectType(1L)); // 1L -> NM -> NORMAL_VEHICLE
|
||||
assertEquals(MovingObjectType.UNMANNED_VEHICLE,
|
||||
quapDataAdapter.convertToMovingObjectType(2L)); // 2L -> UV -> UNMANNED_VEHICLE
|
||||
assertEquals(MovingObjectType.SPECIAL_VEHICLE,
|
||||
quapDataAdapter.convertToMovingObjectType(3L)); // 3L -> SP -> SPECIAL_VEHICLE
|
||||
assertEquals(MovingObjectType.UNKNOWN,
|
||||
quapDataAdapter.convertToMovingObjectType(999L)); // 999L -> 未找到 -> UNKNOWN
|
||||
assertEquals(MovingObjectType.UNKNOWN,
|
||||
quapDataAdapter.convertToMovingObjectType((Long) null)); // null
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBuildVehicleQueryCondition() {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.qaup.collision.geofence.service;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObject;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.common.model.spatial.AirportArea;
|
||||
import com.qaup.collision.common.service.AirportAreaService;
|
||||
import com.qaup.collision.geofence.model.dto.GeofenceAccessCheckResult;
|
||||
@ -63,7 +64,7 @@ class UnmannedVehicleGeofenceServiceTest {
|
||||
|
||||
MovingObject unmannedVehicle = MovingObject.builder()
|
||||
.objectId("UV001")
|
||||
.objectType(MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.objectName("测试无人车")
|
||||
.currentPosition(position)
|
||||
.currentSpeed(10.0)
|
||||
@ -98,7 +99,7 @@ class UnmannedVehicleGeofenceServiceTest {
|
||||
|
||||
MovingObject aircraft = MovingObject.builder()
|
||||
.objectId("AC001")
|
||||
.objectType(MovingObject.ObjectType.AIRCRAFT)
|
||||
.objectType(MovingObjectType.AIRCRAFT)
|
||||
.objectName("测试航空器")
|
||||
.currentPosition(position)
|
||||
.build();
|
||||
@ -126,7 +127,7 @@ class UnmannedVehicleGeofenceServiceTest {
|
||||
|
||||
MovingObject unmannedVehicle = MovingObject.builder()
|
||||
.objectId("UV001")
|
||||
.objectType(MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.objectName("测试无人车")
|
||||
.currentPosition(position)
|
||||
.build();
|
||||
@ -153,7 +154,7 @@ class UnmannedVehicleGeofenceServiceTest {
|
||||
|
||||
MovingObject unmannedVehicle = MovingObject.builder()
|
||||
.objectId("UV001")
|
||||
.objectType(MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.objectName("测试无人车")
|
||||
.currentPosition(position)
|
||||
.build();
|
||||
@ -184,7 +185,7 @@ class UnmannedVehicleGeofenceServiceTest {
|
||||
|
||||
MovingObject unmannedVehicle = MovingObject.builder()
|
||||
.objectId("UV001")
|
||||
.objectType(MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.objectName("测试无人车")
|
||||
.currentPosition(position)
|
||||
.currentSpeed(15.0)
|
||||
@ -222,7 +223,7 @@ class UnmannedVehicleGeofenceServiceTest {
|
||||
|
||||
MovingObject aircraft = MovingObject.builder()
|
||||
.objectId("AC001")
|
||||
.objectType(MovingObject.ObjectType.AIRCRAFT)
|
||||
.objectType(MovingObjectType.AIRCRAFT)
|
||||
.objectName("测试航空器")
|
||||
.currentPosition(position)
|
||||
.build();
|
||||
@ -249,7 +250,7 @@ class UnmannedVehicleGeofenceServiceTest {
|
||||
// Arrange
|
||||
MovingObject unmannedVehicle = MovingObject.builder()
|
||||
.objectId("UV001")
|
||||
.objectType(MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.objectName("测试无人车")
|
||||
.currentPosition(null) // 位置为空
|
||||
.build();
|
||||
@ -273,7 +274,7 @@ class UnmannedVehicleGeofenceServiceTest {
|
||||
|
||||
MovingObject unmannedVehicle = MovingObject.builder()
|
||||
.objectId("UV001")
|
||||
.objectType(MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.objectName("测试无人车")
|
||||
.currentPosition(position)
|
||||
.currentSpeed(15.0)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.qaup.collision.pathconflict.service;
|
||||
|
||||
import com.qaup.collision.common.model.MovingObject;
|
||||
import com.qaup.collision.common.model.MovingObject.MovingObjectType;
|
||||
import com.qaup.collision.pathconflict.repository.ObjectRouteAssignmentRepository;
|
||||
import com.qaup.collision.pathconflict.repository.TransportRouteRepository;
|
||||
import com.qaup.collision.pathconflict.repository.ConflictAlertLogRepository;
|
||||
@ -65,7 +66,7 @@ class PathConflictDetectionServiceNullSpeedTest {
|
||||
|
||||
MovingObject obj1 = MovingObject.builder()
|
||||
.objectId("UV001")
|
||||
.objectType(MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.objectName("测试无人车1")
|
||||
.currentPosition(position1)
|
||||
.currentSpeed(null) // 速度为null
|
||||
@ -75,7 +76,7 @@ class PathConflictDetectionServiceNullSpeedTest {
|
||||
|
||||
MovingObject obj2 = MovingObject.builder()
|
||||
.objectId("UV002")
|
||||
.objectType(MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.objectName("测试无人车2")
|
||||
.currentPosition(position2)
|
||||
.currentSpeed(15.0) // 正常速度
|
||||
@ -120,7 +121,7 @@ class PathConflictDetectionServiceNullSpeedTest {
|
||||
|
||||
MovingObject obj1 = MovingObject.builder()
|
||||
.objectId("UV001")
|
||||
.objectType(MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.objectName("测试无人车1")
|
||||
.currentPosition(position1)
|
||||
.currentSpeed(null) // 速度为null
|
||||
@ -130,7 +131,7 @@ class PathConflictDetectionServiceNullSpeedTest {
|
||||
|
||||
MovingObject obj2 = MovingObject.builder()
|
||||
.objectId("UV002")
|
||||
.objectType(MovingObject.ObjectType.UNMANNED_VEHICLE)
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.objectName("测试无人车2")
|
||||
.currentPosition(position2)
|
||||
.currentSpeed(null) // 速度也为null
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>qaup-management</artifactId>
|
||||
<groupId>com.qaup</groupId>
|
||||
<version>3.8.9</version>
|
||||
<version>1.0.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||
import com.qaup.common.utils.poi.ExcelHandlerAdapter;
|
||||
@ -54,9 +54,9 @@ public @interface Excel
|
||||
public int scale() default -1;
|
||||
|
||||
/**
|
||||
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
|
||||
* BigDecimal 舍入规则 默认:RoundingMode.HALF_EVEN (值为6)
|
||||
*/
|
||||
public int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
|
||||
public int roundingMode() default 6;
|
||||
|
||||
/**
|
||||
* 导出时在excel中每个列的高度
|
||||
|
||||
@ -0,0 +1,77 @@
|
||||
package com.qaup.common.constant;
|
||||
|
||||
/**
|
||||
* 地理计算相关常量
|
||||
*
|
||||
* @author qaup
|
||||
*/
|
||||
public class GeoConstants {
|
||||
|
||||
/**
|
||||
* 地球半径(米)
|
||||
* 使用WGS84椭球体的赤道半径
|
||||
*/
|
||||
public static final double EARTH_RADIUS_METERS = 6378137.0;
|
||||
|
||||
/**
|
||||
* 地球半径(千米)
|
||||
*/
|
||||
public static final double EARTH_RADIUS_KM = 6378.137;
|
||||
|
||||
/**
|
||||
* WGS84椭球体赤道半径(米)
|
||||
* 用于高精度计算
|
||||
*/
|
||||
public static final double WGS84_EQUATORIAL_RADIUS = 6378137.0;
|
||||
|
||||
/**
|
||||
* WGS84椭球体极半径(米)
|
||||
*/
|
||||
public static final double WGS84_POLAR_RADIUS = 6356752.314245;
|
||||
|
||||
/**
|
||||
* 度到米的近似转换系数(在赤道附近)
|
||||
* 1度经度或纬度约等于111320米
|
||||
*/
|
||||
public static final double DEGREE_TO_METER = 111320.0;
|
||||
|
||||
/**
|
||||
* 米到度的近似转换系数
|
||||
*/
|
||||
public static final double METER_TO_DEGREE = 1.0 / DEGREE_TO_METER;
|
||||
|
||||
/**
|
||||
* 米每秒到公里每小时的转换系数
|
||||
*/
|
||||
public static final double MPS_TO_KPH = 3.6;
|
||||
|
||||
/**
|
||||
* 公里每小时到米每秒的转换系数
|
||||
*/
|
||||
public static final double KPH_TO_MPS = 1.0 / MPS_TO_KPH;
|
||||
|
||||
/**
|
||||
* WGS84坐标系SRID
|
||||
*/
|
||||
public static final int WGS84_SRID = 4326;
|
||||
|
||||
/**
|
||||
* 默认坐标精度(小数位数)
|
||||
*/
|
||||
public static final int DEFAULT_COORDINATE_PRECISION = 6;
|
||||
|
||||
/**
|
||||
* 距离计算精度(小数位数)
|
||||
*/
|
||||
public static final int DISTANCE_PRECISION = 2;
|
||||
|
||||
/**
|
||||
* 速度计算精度(小数位数)
|
||||
*/
|
||||
public static final int SPEED_PRECISION = 2;
|
||||
|
||||
/**
|
||||
* 方向计算精度(小数位数)
|
||||
*/
|
||||
public static final int DIRECTION_PRECISION = 2;
|
||||
}
|
||||
@ -18,6 +18,13 @@ import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
*/
|
||||
public class DateUtils extends org.apache.commons.lang3.time.DateUtils
|
||||
{
|
||||
/**
|
||||
* 私有构造函数,防止实例化
|
||||
*/
|
||||
private DateUtils()
|
||||
{
|
||||
}
|
||||
|
||||
public static String YYYY = "yyyy";
|
||||
|
||||
public static String YYYY_MM = "yyyy-MM";
|
||||
|
||||
200
qaup-common/src/main/java/com/qaup/common/utils/GeoUtils.java
Normal file
200
qaup-common/src/main/java/com/qaup/common/utils/GeoUtils.java
Normal file
@ -0,0 +1,200 @@
|
||||
package com.qaup.common.utils;
|
||||
|
||||
import com.qaup.common.constant.GeoConstants;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
/**
|
||||
* 地理计算工具类
|
||||
* 提供统一的地理距离、方向、速度计算方法
|
||||
*
|
||||
* @author qaup
|
||||
*/
|
||||
public class GeoUtils {
|
||||
|
||||
/**
|
||||
* 使用Haversine公式计算两点间的地理距离
|
||||
*
|
||||
* @param lat1 第一个点的纬度
|
||||
* @param lon1 第一个点的经度
|
||||
* @param lat2 第二个点的纬度
|
||||
* @param lon2 第二个点的经度
|
||||
* @return 距离(米)
|
||||
*/
|
||||
public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
|
||||
if (lat1 == lat2 && lon1 == lon2) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double lat1Rad = Math.toRadians(lat1);
|
||||
double lat2Rad = Math.toRadians(lat2);
|
||||
double deltaLatRad = Math.toRadians(lat2 - lat1);
|
||||
double deltaLonRad = Math.toRadians(lon2 - lon1);
|
||||
|
||||
double a = Math.sin(deltaLatRad / 2) * Math.sin(deltaLatRad / 2) +
|
||||
Math.cos(lat1Rad) * Math.cos(lat2Rad) *
|
||||
Math.sin(deltaLonRad / 2) * Math.sin(deltaLonRad / 2);
|
||||
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
|
||||
return GeoConstants.EARTH_RADIUS_METERS * c;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用Haversine公式计算两点间的地理距离,返回BigDecimal
|
||||
*
|
||||
* @param lat1 第一个点的纬度
|
||||
* @param lon1 第一个点的经度
|
||||
* @param lat2 第二个点的纬度
|
||||
* @param lon2 第二个点的经度
|
||||
* @return 距离(米),保留指定精度
|
||||
*/
|
||||
public static BigDecimal calculateDistanceAsBigDecimal(double lat1, double lon1, double lat2, double lon2) {
|
||||
double distance = calculateDistance(lat1, lon1, lat2, lon2);
|
||||
return BigDecimal.valueOf(distance).setScale(GeoConstants.DISTANCE_PRECISION, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两点间的方向角(方位角)
|
||||
*
|
||||
* @param lat1 起点纬度
|
||||
* @param lon1 起点经度
|
||||
* @param lat2 终点纬度
|
||||
* @param lon2 终点经度
|
||||
* @return 方向角(度,0-360度,正北为0度)
|
||||
*/
|
||||
public static double calculateBearing(double lat1, double lon1, double lat2, double lon2) {
|
||||
double lat1Rad = Math.toRadians(lat1);
|
||||
double lat2Rad = Math.toRadians(lat2);
|
||||
double deltaLonRad = Math.toRadians(lon2 - lon1);
|
||||
|
||||
double y = Math.sin(deltaLonRad) * Math.cos(lat2Rad);
|
||||
double x = Math.cos(lat1Rad) * Math.sin(lat2Rad) -
|
||||
Math.sin(lat1Rad) * Math.cos(lat2Rad) * Math.cos(deltaLonRad);
|
||||
|
||||
double bearingRad = Math.atan2(y, x);
|
||||
double bearingDeg = Math.toDegrees(bearingRad);
|
||||
|
||||
// 转换为0-360度范围
|
||||
return (bearingDeg + 360) % 360;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据距离和时间计算速度
|
||||
*
|
||||
* @param distanceMeters 距离(米)
|
||||
* @param timeSeconds 时间(秒)
|
||||
* @return 速度(公里/小时)
|
||||
*/
|
||||
public static double calculateSpeed(double distanceMeters, double timeSeconds) {
|
||||
if (timeSeconds <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double speedMps = distanceMeters / timeSeconds;
|
||||
return speedMps * GeoConstants.MPS_TO_KPH;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据距离和时间计算速度,返回指定精度
|
||||
*
|
||||
* @param distanceMeters 距离(米)
|
||||
* @param timeSeconds 时间(秒)
|
||||
* @return 速度(公里/小时),保留指定精度
|
||||
*/
|
||||
public static double calculateSpeedWithPrecision(double distanceMeters, double timeSeconds) {
|
||||
double speed = calculateSpeed(distanceMeters, timeSeconds);
|
||||
return Math.round(speed * Math.pow(10, GeoConstants.SPEED_PRECISION)) / Math.pow(10, GeoConstants.SPEED_PRECISION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 度到米的近似转换(适用于小范围计算)
|
||||
*
|
||||
* @param degrees 度数
|
||||
* @return 米数(近似值)
|
||||
*/
|
||||
public static double degreesToMeters(double degrees) {
|
||||
return degrees * GeoConstants.DEGREE_TO_METER;
|
||||
}
|
||||
|
||||
/**
|
||||
* 米到度的近似转换(适用于小范围计算)
|
||||
*
|
||||
* @param meters 米数
|
||||
* @return 度数(近似值)
|
||||
*/
|
||||
public static double metersToDegrees(double meters) {
|
||||
return meters * GeoConstants.METER_TO_DEGREE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 米每秒转换为公里每小时
|
||||
*
|
||||
* @param mps 米每秒
|
||||
* @return 公里每小时
|
||||
*/
|
||||
public static double mpsToKph(double mps) {
|
||||
return mps * GeoConstants.MPS_TO_KPH;
|
||||
}
|
||||
|
||||
/**
|
||||
* 公里每小时转换为米每秒
|
||||
*
|
||||
* @param kph 公里每小时
|
||||
* @return 米每秒
|
||||
*/
|
||||
public static double kphToMps(double kph) {
|
||||
return kph * GeoConstants.KPH_TO_MPS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化坐标到指定精度
|
||||
*
|
||||
* @param coordinate 坐标值
|
||||
* @param precision 精度(小数位数)
|
||||
* @return 格式化后的坐标字符串
|
||||
*/
|
||||
public static String formatCoordinate(double coordinate, int precision) {
|
||||
return String.format("%." + precision + "f", coordinate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化坐标到默认精度
|
||||
*
|
||||
* @param coordinate 坐标值
|
||||
* @return 格式化后的坐标字符串
|
||||
*/
|
||||
public static String formatCoordinate(double coordinate) {
|
||||
return formatCoordinate(coordinate, GeoConstants.DEFAULT_COORDINATE_PRECISION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证经度是否有效
|
||||
*
|
||||
* @param longitude 经度
|
||||
* @return 是否有效
|
||||
*/
|
||||
public static boolean isValidLongitude(double longitude) {
|
||||
return longitude >= -180.0 && longitude <= 180.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证纬度是否有效
|
||||
*
|
||||
* @param latitude 纬度
|
||||
* @return 是否有效
|
||||
*/
|
||||
public static boolean isValidLatitude(double latitude) {
|
||||
return latitude >= -90.0 && latitude <= 90.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证坐标是否有效
|
||||
*
|
||||
* @param longitude 经度
|
||||
* @param latitude 纬度
|
||||
* @return 是否有效
|
||||
*/
|
||||
public static boolean isValidCoordinate(double longitude, double latitude) {
|
||||
return isValidLongitude(longitude) && isValidLatitude(latitude);
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,13 @@ import com.qaup.common.core.text.StrFormatter;
|
||||
*/
|
||||
public class StringUtils extends org.apache.commons.lang3.StringUtils
|
||||
{
|
||||
/**
|
||||
* 私有构造函数,防止实例化
|
||||
*/
|
||||
private StringUtils()
|
||||
{
|
||||
}
|
||||
|
||||
/** 空字符串 */
|
||||
private static final String NULLSTR = "";
|
||||
|
||||
|
||||
@ -419,7 +419,7 @@ public class ExcelUtil<T>
|
||||
Object val = this.getCellValue(row, entry.getKey());
|
||||
|
||||
// 如果不存在实例则新建.
|
||||
entity = (entity == null ? clazz.newInstance() : entity);
|
||||
entity = (entity == null ? clazz.getDeclaredConstructor().newInstance() : entity);
|
||||
// 从map中得到对应列的field.
|
||||
Field field = (Field) entry.getValue()[0];
|
||||
Excel attr = (Excel) entry.getValue()[1];
|
||||
@ -1170,7 +1170,9 @@ public class ExcelUtil<T>
|
||||
}
|
||||
else if (value instanceof BigDecimal && -1 != attr.scale())
|
||||
{
|
||||
cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue());
|
||||
cell.setCellValue(
|
||||
(((BigDecimal) value).setScale(attr.scale(), java.math.RoundingMode.valueOf(attr.roundingMode()))).doubleValue()
|
||||
);
|
||||
}
|
||||
else if (!attr.handler().equals(ExcelHandlerAdapter.class))
|
||||
{
|
||||
@ -1391,7 +1393,7 @@ public class ExcelUtil<T>
|
||||
{
|
||||
try
|
||||
{
|
||||
Object instance = excel.handler().newInstance();
|
||||
Object instance = excel.handler().getDeclaredConstructor().newInstance();
|
||||
Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class });
|
||||
value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb);
|
||||
}
|
||||
|
||||
@ -313,7 +313,7 @@ public class ReflectUtils
|
||||
public static void makeAccessible(Method method)
|
||||
{
|
||||
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
|
||||
&& !method.isAccessible())
|
||||
&& !method.canAccess(null))
|
||||
{
|
||||
method.setAccessible(true);
|
||||
}
|
||||
@ -325,7 +325,7 @@ public class ReflectUtils
|
||||
public static void makeAccessible(Field field)
|
||||
{
|
||||
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())
|
||||
|| Modifier.isFinal(field.getModifiers())) && !field.isAccessible())
|
||||
|| Modifier.isFinal(field.getModifiers())) && !field.canAccess(null))
|
||||
{
|
||||
field.setAccessible(true);
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.qaup.common.utils.StringUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* spring工具类 方便在非spring管理环境中获取bean
|
||||
@ -25,13 +26,13 @@ public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationC
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
|
||||
public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException
|
||||
{
|
||||
SpringUtils.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
|
||||
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException
|
||||
{
|
||||
SpringUtils.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>qaup-management</artifactId>
|
||||
<groupId>com.qaup</groupId>
|
||||
<version>3.8.9</version>
|
||||
<version>1.0.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import com.alibaba.fastjson2.JSONReader;
|
||||
import com.alibaba.fastjson2.JSONWriter;
|
||||
import com.alibaba.fastjson2.filter.Filter;
|
||||
import com.qaup.common.constant.Constants;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Redis使用FastJson序列化
|
||||
@ -29,7 +30,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(T t) throws SerializationException
|
||||
public byte[] serialize(@Nullable T t) throws SerializationException
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
@ -39,7 +40,7 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(byte[] bytes) throws SerializationException
|
||||
public T deserialize(@Nullable byte[] bytes) throws SerializationException
|
||||
{
|
||||
if (bytes == null || bytes.length <= 0)
|
||||
{
|
||||
|
||||
@ -9,6 +9,8 @@ import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||
import com.qaup.common.constant.Constants;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* 资源文件配置加载
|
||||
*
|
||||
@ -36,7 +38,7 @@ public class I18nConfig implements WebMvcConfigurer
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry)
|
||||
public void addInterceptors(@NonNull InterceptorRegistry registry)
|
||||
{
|
||||
registry.addInterceptor(localeChangeInterceptor());
|
||||
}
|
||||
|
||||
@ -126,7 +126,11 @@ public class MyBatisConfig
|
||||
sessionFactory.setDataSource(dataSource);
|
||||
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
|
||||
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
|
||||
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
|
||||
sessionFactory.setConfigLocation(
|
||||
new DefaultResourceLoader().getResource(
|
||||
java.util.Objects.requireNonNull(configLocation, "mybatis.configLocation must not be null")
|
||||
)
|
||||
);
|
||||
return sessionFactory.getObject();
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
package com.qaup.framework.config;
|
||||
|
||||
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -16,7 +15,7 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class RedisConfig extends CachingConfigurerSupport
|
||||
public class RedisConfig
|
||||
{
|
||||
@Bean("redisTemplate")
|
||||
@SuppressWarnings(value = { "unchecked", "rawtypes" })
|
||||
|
||||
@ -5,6 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
@ -27,7 +28,7 @@ public class ResourcesConfig implements WebMvcConfigurer
|
||||
private RepeatSubmitInterceptor repeatSubmitInterceptor;
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry)
|
||||
public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry)
|
||||
{
|
||||
/** 本地文件上传路径 */
|
||||
registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
|
||||
@ -47,7 +48,7 @@ public class ResourcesConfig implements WebMvcConfigurer
|
||||
* 自定义拦截规则
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry)
|
||||
public void addInterceptors(@NonNull InterceptorRegistry registry)
|
||||
{
|
||||
registry.addInterceptor(repeatSubmitInterceptor)
|
||||
.addPathPatterns("/**");
|
||||
|
||||
@ -3,7 +3,6 @@ package com.qaup.framework.config.properties;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
@ -17,6 +16,7 @@ import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import com.qaup.common.annotation.Anonymous;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* 设置Anonymous注解允许匿名访问的url
|
||||
@ -66,18 +66,24 @@ public class PermitAllUrlProperties implements InitializingBean, ApplicationCont
|
||||
|
||||
// 获取方法上边的注解 替代path variable 为 *
|
||||
Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
|
||||
Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues()) //
|
||||
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
|
||||
Optional.ofNullable(method).ifPresent(anonymous ->
|
||||
Optional.ofNullable(info.getPathPatternsCondition())
|
||||
.ifPresent(condition -> condition.getPatternValues()
|
||||
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))))
|
||||
);
|
||||
|
||||
// 获取类上边的注解, 替代path variable 为 *
|
||||
Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
|
||||
Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues())
|
||||
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
|
||||
Optional.ofNullable(controller).ifPresent(anonymous ->
|
||||
Optional.ofNullable(info.getPathPatternsCondition())
|
||||
.ifPresent(condition -> condition.getPatternValues()
|
||||
.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException
|
||||
public void setApplicationContext(@NonNull ApplicationContext context) throws BeansException
|
||||
{
|
||||
this.applicationContext = context;
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ package com.qaup.framework.interceptor;
|
||||
import java.lang.reflect.Method;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
@ -10,8 +12,6 @@ import com.alibaba.fastjson2.JSON;
|
||||
import com.qaup.common.annotation.RepeatSubmit;
|
||||
import com.qaup.common.core.domain.AjaxResult;
|
||||
import com.qaup.common.utils.ServletUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 防止重复提交拦截器
|
||||
@ -21,10 +21,8 @@ import org.slf4j.LoggerFactory;
|
||||
@Component
|
||||
public abstract class RepeatSubmitInterceptor implements HandlerInterceptor
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(RepeatSubmitInterceptor.class);
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
|
||||
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception
|
||||
{
|
||||
if (handler instanceof HandlerMethod)
|
||||
{
|
||||
|
||||
@ -6,6 +6,7 @@ import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
@ -15,8 +16,6 @@ import com.qaup.common.core.domain.model.LoginUser;
|
||||
import com.qaup.common.utils.SecurityUtils;
|
||||
import com.qaup.common.utils.StringUtils;
|
||||
import com.qaup.framework.web.service.TokenService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* token过滤器 验证token有效性
|
||||
@ -25,14 +24,12 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@Component
|
||||
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
|
||||
|
||||
{
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain chain)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
LoginUser loginUser = tokenService.getLoginUser(request);
|
||||
|
||||
@ -12,8 +12,6 @@ import com.qaup.common.constant.HttpStatus;
|
||||
import com.qaup.common.core.domain.AjaxResult;
|
||||
import com.qaup.common.utils.ServletUtils;
|
||||
import com.qaup.common.utils.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 认证失败处理类 返回未授权
|
||||
@ -25,8 +23,6 @@ public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, S
|
||||
{
|
||||
private static final long serialVersionUID = -8970718410437077606L;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AuthenticationEntryPointImpl.class);
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
|
||||
throws IOException
|
||||
|
||||
@ -77,6 +77,7 @@ public class GlobalExceptionHandler
|
||||
/**
|
||||
* 请求参数类型不匹配
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request)
|
||||
{
|
||||
@ -130,6 +131,7 @@ public class GlobalExceptionHandler
|
||||
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
|
||||
{
|
||||
log.error(e.getMessage(), e);
|
||||
@SuppressWarnings("null")
|
||||
String message = e.getBindingResult().getFieldError().getDefaultMessage();
|
||||
return AjaxResult.error(message);
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>qaup-management</artifactId>
|
||||
<groupId>com.qaup</groupId>
|
||||
<version>3.8.9</version>
|
||||
<version>1.0.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>qaup-management</artifactId>
|
||||
<groupId>com.qaup</groupId>
|
||||
<version>3.8.9</version>
|
||||
<version>1.0.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>qaup-management</artifactId>
|
||||
<groupId>com.qaup</groupId>
|
||||
<version>3.8.9</version>
|
||||
<version>1.0.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@ -24,6 +24,50 @@
|
||||
<version>${qaup.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok支持 -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- SpringDoc OpenAPI UI 依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Jakarta Validation API -->
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.14.0</version>
|
||||
<configuration>
|
||||
<release>${java.version}</release>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
<parameters>true</parameters>
|
||||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
</compilerArgs>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@ -7,9 +7,9 @@ import com.qaup.common.core.domain.BaseEntity;
|
||||
|
||||
/**
|
||||
* 车辆类型(路径编码模式)对象 sys_vehicle_type
|
||||
*
|
||||
* @author Claude Code
|
||||
* @date 2025-07-13
|
||||
*
|
||||
* @author qaup
|
||||
* @date 2025-07-14
|
||||
*/
|
||||
public class SysVehicleType extends BaseEntity
|
||||
{
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
package com.qaup.system.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 轨迹查询请求DTO
|
||||
*
|
||||
* @author qaup
|
||||
* @date 2025-01-17
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TrajectoryQueryRequest {
|
||||
|
||||
/** 车辆ID列表 */
|
||||
private List<Long> vehicleIds;
|
||||
|
||||
/** 开始时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date startTime;
|
||||
|
||||
/** 结束时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date endTime;
|
||||
|
||||
/** 是否简化数据 */
|
||||
@Builder.Default
|
||||
private Boolean simplified = false;
|
||||
|
||||
/** 最大轨迹点数量 */
|
||||
@Builder.Default
|
||||
private Integer maxPoints = 1000;
|
||||
|
||||
/** 是否包含统计信息 */
|
||||
@Builder.Default
|
||||
private Boolean includeStatistics = true;
|
||||
|
||||
/** 是否包含GeoJSON格式 */
|
||||
@Builder.Default
|
||||
private Boolean includeGeoJson = false;
|
||||
|
||||
/** 数据质量过滤 */
|
||||
private String dataQuality;
|
||||
|
||||
/** 最小速度过滤(km/h) */
|
||||
private BigDecimal minSpeed;
|
||||
|
||||
/** 最大速度过滤(km/h) */
|
||||
private BigDecimal maxSpeed;
|
||||
|
||||
/** 区域过滤(WKT格式多边形) */
|
||||
private String areaWkt;
|
||||
|
||||
/** 是否只返回移动轨迹(过滤停车点) */
|
||||
@Builder.Default
|
||||
private Boolean movingOnly = false;
|
||||
|
||||
/** 车牌号列表(可选,用于按车牌查询) */
|
||||
private List<String> licensePlates;
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package com.qaup.system.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 轨迹点VO
|
||||
*
|
||||
* @author qaup
|
||||
* @date 2025-01-17
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TrajectoryPoint {
|
||||
|
||||
/** 经度 */
|
||||
private BigDecimal longitude;
|
||||
|
||||
/** 纬度 */
|
||||
private BigDecimal latitude;
|
||||
|
||||
/** 海拔高度(米) */
|
||||
private BigDecimal altitude;
|
||||
|
||||
/** 速度(km/h) */
|
||||
private BigDecimal speed;
|
||||
|
||||
/** 朝向角度(度) */
|
||||
private BigDecimal heading;
|
||||
|
||||
/** 位置时间戳 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date timestamp;
|
||||
|
||||
/** 数据质量 */
|
||||
private String dataQuality;
|
||||
|
||||
/** 原始位置数据(WKT格式) */
|
||||
private String location;
|
||||
|
||||
/** 是否为停车点 */
|
||||
private Boolean isStopPoint;
|
||||
|
||||
/** 与前一点的距离(米) */
|
||||
private BigDecimal distanceFromPrevious;
|
||||
|
||||
/** 与前一点的时间间隔(秒) */
|
||||
private Long timeIntervalFromPrevious;
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package com.qaup.system.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 轨迹统计信息VO
|
||||
*
|
||||
* @author qaup
|
||||
* @date 2025-01-17
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TrajectoryStatistics {
|
||||
|
||||
/** 总距离(公里) */
|
||||
private BigDecimal totalDistance;
|
||||
|
||||
/** 平均速度(km/h) */
|
||||
private BigDecimal averageSpeed;
|
||||
|
||||
/** 最大速度(km/h) */
|
||||
private BigDecimal maxSpeed;
|
||||
|
||||
/** 最小速度(km/h) */
|
||||
private BigDecimal minSpeed;
|
||||
|
||||
/** 行驶时长(秒) */
|
||||
private Long durationSeconds;
|
||||
|
||||
/** 行驶时长(格式化) */
|
||||
private String durationFormatted;
|
||||
|
||||
/** 轨迹点数量 */
|
||||
private Integer pointCount;
|
||||
|
||||
/** 首个轨迹点时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date firstPointTime;
|
||||
|
||||
/** 最后轨迹点时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date lastPointTime;
|
||||
|
||||
/** 停车次数 */
|
||||
private Integer stopCount;
|
||||
|
||||
/** 停车总时长(秒) */
|
||||
private Long totalStopDuration;
|
||||
|
||||
/** 最高海拔(米) */
|
||||
private BigDecimal maxAltitude;
|
||||
|
||||
/** 最低海拔(米) */
|
||||
private BigDecimal minAltitude;
|
||||
|
||||
/** 平均海拔(米) */
|
||||
private BigDecimal averageAltitude;
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
package com.qaup.system.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 车辆位置回放数据VO
|
||||
* 专门用于轨迹回放功能,包含回放所需的序列和时间信息
|
||||
*
|
||||
* @author qaup
|
||||
* @date 2025-01-17
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class VehicleLocationPlaybackVO {
|
||||
|
||||
/** 车辆ID */
|
||||
private Long vehicleId;
|
||||
|
||||
/** 车牌号 */
|
||||
private String licensePlate;
|
||||
|
||||
/** 车辆类型 */
|
||||
private String vehicleType;
|
||||
|
||||
/** 回放序列号 */
|
||||
private Long sequenceNumber;
|
||||
|
||||
/** 经度 */
|
||||
private BigDecimal longitude;
|
||||
|
||||
/** 纬度 */
|
||||
private BigDecimal latitude;
|
||||
|
||||
/** 海拔高度(米) */
|
||||
private BigDecimal altitude;
|
||||
|
||||
/** 速度(km/h) */
|
||||
private BigDecimal speed;
|
||||
|
||||
/** 朝向角度(度) */
|
||||
private BigDecimal heading;
|
||||
|
||||
/** 位置时间戳 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date timestamp;
|
||||
|
||||
/** 数据质量 */
|
||||
private String dataQuality;
|
||||
|
||||
/** 与上一点的时间间隔(毫秒) */
|
||||
private Long intervalMs;
|
||||
|
||||
/** 累计行驶距离(米) */
|
||||
private BigDecimal cumulativeDistance;
|
||||
|
||||
/** 累计行驶时间(秒) */
|
||||
private Long cumulativeTime;
|
||||
|
||||
/** 回放进度百分比 */
|
||||
private BigDecimal progressPercentage;
|
||||
|
||||
/** 是否为关键帧(起点、终点、转向点等) */
|
||||
private Boolean isKeyFrame;
|
||||
|
||||
/** 关键帧类型 */
|
||||
private String keyFrameType;
|
||||
|
||||
/** 回放建议延迟(毫秒) */
|
||||
private Long suggestedDelay;
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package com.qaup.system.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 车辆轨迹查询结果VO
|
||||
*
|
||||
* @author qaup
|
||||
* @date 2025-01-17
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class VehicleTrajectoryVO {
|
||||
|
||||
/** 车辆ID */
|
||||
private Long vehicleId;
|
||||
|
||||
/** 车牌号 */
|
||||
private String licensePlate;
|
||||
|
||||
/** 车辆类型 */
|
||||
private String vehicleType;
|
||||
|
||||
/** 品牌 */
|
||||
private String brand;
|
||||
|
||||
/** 所属单位 */
|
||||
private String owningUnit;
|
||||
|
||||
/** 轨迹开始时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date startTime;
|
||||
|
||||
/** 轨迹结束时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date endTime;
|
||||
|
||||
/** 轨迹统计信息 */
|
||||
private TrajectoryStatistics statistics;
|
||||
|
||||
/** 轨迹点数据 */
|
||||
private List<TrajectoryPoint> points;
|
||||
|
||||
/** GeoJSON格式轨迹线 */
|
||||
private String trajectoryLineGeoJson;
|
||||
|
||||
/** 是否为简化数据 */
|
||||
private Boolean simplified;
|
||||
|
||||
/** 数据质量 */
|
||||
private String dataQuality;
|
||||
}
|
||||
@ -1,7 +1,10 @@
|
||||
package com.qaup.system.mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.qaup.system.domain.SysVehicleLocation;
|
||||
import com.qaup.system.domain.vo.TrajectoryStatistics;
|
||||
|
||||
/**
|
||||
* 车辆运动信息Mapper接口
|
||||
@ -45,9 +48,81 @@ public interface SysVehicleLocationMapper
|
||||
|
||||
/**
|
||||
* 根据车牌号查询车辆最新位置信息
|
||||
*
|
||||
*
|
||||
* @param licensePlate 车牌号
|
||||
* @return 车辆运动信息
|
||||
*/
|
||||
public SysVehicleLocation selectLatestSysVehicleLocationByLicensePlate(String licensePlate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询车辆轨迹数据(支持采样优化)
|
||||
*
|
||||
* @param vehicleId 车辆ID
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param simplified 是否简化数据
|
||||
* @param maxPoints 最大轨迹点数量
|
||||
* @return 车辆运动信息列表
|
||||
*/
|
||||
public List<SysVehicleLocation> selectVehicleTrajectory(@Param("vehicleId") Long vehicleId,
|
||||
@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime,
|
||||
@Param("simplified") Boolean simplified,
|
||||
@Param("maxPoints") Integer maxPoints);
|
||||
|
||||
/**
|
||||
* 查询轨迹统计信息
|
||||
*
|
||||
* @param vehicleId 车辆ID
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 轨迹统计信息
|
||||
*/
|
||||
public TrajectoryStatistics selectTrajectoryStatistics(@Param("vehicleId") Long vehicleId,
|
||||
@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime);
|
||||
|
||||
/**
|
||||
* 查询轨迹回放数据
|
||||
*
|
||||
* @param vehicleId 车辆ID
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param intervalSeconds 时间间隔(秒)
|
||||
* @return 车辆运动信息列表
|
||||
*/
|
||||
public List<SysVehicleLocation> selectTrajectoryPlaybackData(@Param("vehicleId") Long vehicleId,
|
||||
@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime,
|
||||
@Param("intervalSeconds") Integer intervalSeconds);
|
||||
|
||||
/**
|
||||
* 查询车辆在指定区域内的轨迹
|
||||
*
|
||||
* @param vehicleId 车辆ID
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param areaWkt 区域WKT格式字符串
|
||||
* @return 车辆运动信息列表
|
||||
*/
|
||||
public List<SysVehicleLocation> selectVehicleTrajectoryInArea(@Param("vehicleId") Long vehicleId,
|
||||
@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime,
|
||||
@Param("areaWkt") String areaWkt);
|
||||
|
||||
/**
|
||||
* 批量查询多车辆轨迹数据
|
||||
*
|
||||
* @param vehicleIds 车辆ID列表
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param simplified 是否简化数据
|
||||
* @param maxPoints 最大轨迹点数量
|
||||
* @return 车辆运动信息列表
|
||||
*/
|
||||
public List<SysVehicleLocation> selectBatchVehicleTrajectory(@Param("vehicleIds") List<Long> vehicleIds,
|
||||
@Param("startTime") Date startTime,
|
||||
@Param("endTime") Date endTime,
|
||||
@Param("simplified") Boolean simplified,
|
||||
@Param("maxPoints") Integer maxPoints);
|
||||
}
|
||||
@ -1,7 +1,12 @@
|
||||
package com.qaup.system.service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.qaup.system.domain.SysVehicleLocation;
|
||||
import com.qaup.system.domain.vo.VehicleTrajectoryVO;
|
||||
import com.qaup.system.domain.vo.VehicleLocationPlaybackVO;
|
||||
import com.qaup.system.domain.vo.TrajectoryStatistics;
|
||||
import com.qaup.system.domain.dto.TrajectoryQueryRequest;
|
||||
|
||||
/**
|
||||
* 车辆运动信息Service接口
|
||||
@ -45,9 +50,64 @@ public interface ISysVehicleLocationService
|
||||
|
||||
/**
|
||||
* 根据车牌号查询车辆最新位置信息
|
||||
*
|
||||
*
|
||||
* @param licensePlate 车牌号
|
||||
* @return 车辆运动信息
|
||||
*/
|
||||
public SysVehicleLocation selectLatestSysVehicleLocationByLicensePlate(String licensePlate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取车辆轨迹数据
|
||||
*
|
||||
* @param vehicleId 车辆ID
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param simplified 是否简化数据
|
||||
* @param maxPoints 最大轨迹点数量
|
||||
* @return 车辆轨迹数据
|
||||
*/
|
||||
public VehicleTrajectoryVO getVehicleTrajectory(Long vehicleId, Date startTime, Date endTime,
|
||||
Boolean simplified, Integer maxPoints);
|
||||
|
||||
/**
|
||||
* 批量获取多车辆轨迹数据
|
||||
*
|
||||
* @param request 轨迹查询请求
|
||||
* @return 车辆轨迹数据列表
|
||||
*/
|
||||
public List<VehicleTrajectoryVO> getBatchVehicleTrajectory(TrajectoryQueryRequest request);
|
||||
|
||||
/**
|
||||
* 获取轨迹回放数据
|
||||
*
|
||||
* @param vehicleId 车辆ID
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param intervalSeconds 时间间隔(秒)
|
||||
* @return 轨迹回放数据列表
|
||||
*/
|
||||
public List<VehicleLocationPlaybackVO> getTrajectoryPlaybackData(Long vehicleId, Date startTime,
|
||||
Date endTime, Integer intervalSeconds);
|
||||
|
||||
/**
|
||||
* 获取车辆在指定区域内的轨迹
|
||||
*
|
||||
* @param vehicleId 车辆ID
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @param areaWkt 区域WKT格式字符串
|
||||
* @return 车辆轨迹数据
|
||||
*/
|
||||
public VehicleTrajectoryVO getVehicleTrajectoryInArea(Long vehicleId, Date startTime, Date endTime,
|
||||
String areaWkt);
|
||||
|
||||
/**
|
||||
* 获取轨迹统计信息
|
||||
*
|
||||
* @param vehicleId 车辆ID
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 轨迹统计信息
|
||||
*/
|
||||
public TrajectoryStatistics getTrajectoryStatistics(Long vehicleId, Date startTime, Date endTime);
|
||||
}
|
||||
@ -1,10 +1,27 @@
|
||||
package com.qaup.system.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.qaup.common.utils.GeoUtils;
|
||||
import com.qaup.system.mapper.SysVehicleLocationMapper;
|
||||
import com.qaup.system.domain.SysVehicleLocation;
|
||||
import com.qaup.system.domain.vo.VehicleTrajectoryVO;
|
||||
import com.qaup.system.domain.vo.VehicleLocationPlaybackVO;
|
||||
import com.qaup.system.domain.vo.TrajectoryStatistics;
|
||||
import com.qaup.system.domain.vo.TrajectoryPoint;
|
||||
import com.qaup.system.domain.dto.TrajectoryQueryRequest;
|
||||
import com.qaup.system.service.ISysVehicleLocationService;
|
||||
|
||||
/**
|
||||
@ -69,7 +86,7 @@ public class SysVehicleLocationServiceImpl implements ISysVehicleLocationService
|
||||
|
||||
/**
|
||||
* 根据车牌号查询车辆最新位置信息
|
||||
*
|
||||
*
|
||||
* @param licensePlate 车牌号
|
||||
* @return 车辆运动信息
|
||||
*/
|
||||
@ -78,4 +95,498 @@ public class SysVehicleLocationServiceImpl implements ISysVehicleLocationService
|
||||
{
|
||||
return sysVehicleLocationMapper.selectLatestSysVehicleLocationByLicensePlate(licensePlate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取车辆轨迹数据
|
||||
*/
|
||||
@Override
|
||||
public VehicleTrajectoryVO getVehicleTrajectory(Long vehicleId, Date startTime, Date endTime,
|
||||
Boolean simplified, Integer maxPoints) {
|
||||
// 参数校验
|
||||
if (vehicleId == null || startTime == null || endTime == null) {
|
||||
throw new IllegalArgumentException("车辆ID、开始时间和结束时间不能为空");
|
||||
}
|
||||
|
||||
if (startTime.after(endTime)) {
|
||||
throw new IllegalArgumentException("开始时间不能晚于结束时间");
|
||||
}
|
||||
|
||||
// 查询轨迹数据
|
||||
List<SysVehicleLocation> locations = sysVehicleLocationMapper
|
||||
.selectVehicleTrajectory(vehicleId, startTime, endTime, simplified, maxPoints);
|
||||
|
||||
if (CollectionUtils.isEmpty(locations)) {
|
||||
return createEmptyTrajectory(vehicleId, startTime, endTime);
|
||||
}
|
||||
|
||||
// 获取车辆基础信息(从第一条记录中获取)
|
||||
SysVehicleLocation firstLocation = locations.get(0);
|
||||
|
||||
// 转换轨迹点数据
|
||||
List<TrajectoryPoint> points = convertToTrajectoryPoints(locations);
|
||||
|
||||
// 获取统计信息
|
||||
TrajectoryStatistics statistics = getTrajectoryStatistics(vehicleId, startTime, endTime);
|
||||
|
||||
// 构建返回结果
|
||||
return VehicleTrajectoryVO.builder()
|
||||
.vehicleId(vehicleId)
|
||||
.licensePlate(firstLocation.getLicensePlate())
|
||||
.vehicleType(firstLocation.getVehicleType())
|
||||
.brand(firstLocation.getBrand())
|
||||
.owningUnit(firstLocation.getOwningUnit())
|
||||
.startTime(startTime)
|
||||
.endTime(endTime)
|
||||
.statistics(statistics)
|
||||
.points(points)
|
||||
.simplified(simplified)
|
||||
.dataQuality(calculateOverallDataQuality(locations))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取多车辆轨迹数据
|
||||
*/
|
||||
@Override
|
||||
public List<VehicleTrajectoryVO> getBatchVehicleTrajectory(TrajectoryQueryRequest request) {
|
||||
// 参数校验
|
||||
if (request == null || CollectionUtils.isEmpty(request.getVehicleIds())
|
||||
|| request.getStartTime() == null || request.getEndTime() == null) {
|
||||
throw new IllegalArgumentException("请求参数不能为空");
|
||||
}
|
||||
|
||||
List<VehicleTrajectoryVO> result = new ArrayList<>();
|
||||
|
||||
// 批量查询轨迹数据
|
||||
List<SysVehicleLocation> allLocations = sysVehicleLocationMapper
|
||||
.selectBatchVehicleTrajectory(request.getVehicleIds(), request.getStartTime(),
|
||||
request.getEndTime(), request.getSimplified(), request.getMaxPoints());
|
||||
|
||||
// 按车辆ID分组
|
||||
Map<Long, List<SysVehicleLocation>> locationsByVehicle = allLocations.stream()
|
||||
.collect(Collectors.groupingBy(SysVehicleLocation::getVehicleId));
|
||||
|
||||
// 为每个车辆构建轨迹数据
|
||||
for (Long vehicleId : request.getVehicleIds()) {
|
||||
List<SysVehicleLocation> vehicleLocations = locationsByVehicle.get(vehicleId);
|
||||
|
||||
if (CollectionUtils.isEmpty(vehicleLocations)) {
|
||||
result.add(createEmptyTrajectory(vehicleId, request.getStartTime(), request.getEndTime()));
|
||||
continue;
|
||||
}
|
||||
|
||||
VehicleTrajectoryVO trajectory = buildTrajectoryFromLocations(vehicleId, vehicleLocations,
|
||||
request.getStartTime(), request.getEndTime(), request.getSimplified(), request.getIncludeStatistics());
|
||||
result.add(trajectory);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取轨迹回放数据
|
||||
*/
|
||||
@Override
|
||||
public List<VehicleLocationPlaybackVO> getTrajectoryPlaybackData(Long vehicleId, Date startTime,
|
||||
Date endTime, Integer intervalSeconds) {
|
||||
// 参数校验
|
||||
if (vehicleId == null || startTime == null || endTime == null || intervalSeconds == null) {
|
||||
throw new IllegalArgumentException("参数不能为空");
|
||||
}
|
||||
|
||||
if (intervalSeconds <= 0) {
|
||||
throw new IllegalArgumentException("时间间隔必须大于0");
|
||||
}
|
||||
|
||||
// 查询回放数据
|
||||
List<SysVehicleLocation> locations = sysVehicleLocationMapper
|
||||
.selectTrajectoryPlaybackData(vehicleId, startTime, endTime, intervalSeconds);
|
||||
|
||||
if (CollectionUtils.isEmpty(locations)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 转换为回放数据
|
||||
return convertToPlaybackData(locations, startTime, endTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取车辆在指定区域内的轨迹
|
||||
*/
|
||||
@Override
|
||||
public VehicleTrajectoryVO getVehicleTrajectoryInArea(Long vehicleId, Date startTime, Date endTime,
|
||||
String areaWkt) {
|
||||
// 参数校验
|
||||
if (vehicleId == null || startTime == null || endTime == null || !StringUtils.hasText(areaWkt)) {
|
||||
throw new IllegalArgumentException("参数不能为空");
|
||||
}
|
||||
|
||||
// 查询区域内轨迹数据
|
||||
List<SysVehicleLocation> locations = sysVehicleLocationMapper
|
||||
.selectVehicleTrajectoryInArea(vehicleId, startTime, endTime, areaWkt);
|
||||
|
||||
if (CollectionUtils.isEmpty(locations)) {
|
||||
return createEmptyTrajectory(vehicleId, startTime, endTime);
|
||||
}
|
||||
|
||||
// 构建轨迹数据
|
||||
return buildTrajectoryFromLocations(vehicleId, locations, startTime, endTime, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取轨迹统计信息
|
||||
*/
|
||||
@Override
|
||||
public TrajectoryStatistics getTrajectoryStatistics(Long vehicleId, Date startTime, Date endTime) {
|
||||
// 参数校验
|
||||
if (vehicleId == null || startTime == null || endTime == null) {
|
||||
throw new IllegalArgumentException("参数不能为空");
|
||||
}
|
||||
|
||||
// 查询统计信息
|
||||
TrajectoryStatistics statistics = sysVehicleLocationMapper
|
||||
.selectTrajectoryStatistics(vehicleId, startTime, endTime);
|
||||
|
||||
if (statistics == null) {
|
||||
return createEmptyStatistics(startTime, endTime);
|
||||
}
|
||||
|
||||
// 格式化时长
|
||||
if (statistics.getDurationSeconds() != null) {
|
||||
statistics.setDurationFormatted(formatDuration(statistics.getDurationSeconds()));
|
||||
}
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
// ==================== 私有辅助方法 ====================
|
||||
|
||||
/**
|
||||
* 创建空轨迹对象
|
||||
*/
|
||||
private VehicleTrajectoryVO createEmptyTrajectory(Long vehicleId, Date startTime, Date endTime) {
|
||||
return VehicleTrajectoryVO.builder()
|
||||
.vehicleId(vehicleId)
|
||||
.startTime(startTime)
|
||||
.endTime(endTime)
|
||||
.statistics(createEmptyStatistics(startTime, endTime))
|
||||
.points(new ArrayList<>())
|
||||
.simplified(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建空统计信息
|
||||
*/
|
||||
private TrajectoryStatistics createEmptyStatistics(Date startTime, Date endTime) {
|
||||
return TrajectoryStatistics.builder()
|
||||
.pointCount(0)
|
||||
.firstPointTime(startTime)
|
||||
.lastPointTime(endTime)
|
||||
.averageSpeed(BigDecimal.ZERO)
|
||||
.maxSpeed(BigDecimal.ZERO)
|
||||
.minSpeed(BigDecimal.ZERO)
|
||||
.durationSeconds(0L)
|
||||
.durationFormatted("0分钟")
|
||||
.totalDistance(BigDecimal.ZERO)
|
||||
.stopCount(0)
|
||||
.totalStopDuration(0L)
|
||||
.maxAltitude(BigDecimal.ZERO)
|
||||
.minAltitude(BigDecimal.ZERO)
|
||||
.averageAltitude(BigDecimal.ZERO)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为轨迹点列表
|
||||
*/
|
||||
private List<TrajectoryPoint> convertToTrajectoryPoints(List<SysVehicleLocation> locations) {
|
||||
if (CollectionUtils.isEmpty(locations)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<TrajectoryPoint> points = new ArrayList<>();
|
||||
SysVehicleLocation previousLocation = null;
|
||||
|
||||
for (SysVehicleLocation location : locations) {
|
||||
TrajectoryPoint.TrajectoryPointBuilder pointBuilder = TrajectoryPoint.builder()
|
||||
.longitude(location.getLongitude())
|
||||
.latitude(location.getLatitude())
|
||||
.altitude(location.getAltitude())
|
||||
.speed(location.getSpeed())
|
||||
.heading(location.getHeading())
|
||||
.timestamp(location.getTimestamp())
|
||||
.dataQuality(location.getDataQuality())
|
||||
.location(location.getLocation())
|
||||
.isStopPoint(isStopPoint(location));
|
||||
|
||||
// 计算与前一点的距离和时间间隔
|
||||
if (previousLocation != null) {
|
||||
pointBuilder.distanceFromPrevious(calculateDistance(previousLocation, location));
|
||||
pointBuilder.timeIntervalFromPrevious(calculateTimeInterval(previousLocation, location));
|
||||
}
|
||||
|
||||
points.add(pointBuilder.build());
|
||||
previousLocation = location;
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为回放数据
|
||||
*/
|
||||
private List<VehicleLocationPlaybackVO> convertToPlaybackData(List<SysVehicleLocation> locations,
|
||||
Date startTime, Date endTime) {
|
||||
if (CollectionUtils.isEmpty(locations)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<VehicleLocationPlaybackVO> playbackData = new ArrayList<>();
|
||||
AtomicLong sequenceNumber = new AtomicLong(1);
|
||||
BigDecimal cumulativeDistance = BigDecimal.ZERO;
|
||||
long cumulativeTime = 0;
|
||||
long totalDuration = (endTime.getTime() - startTime.getTime()) / 1000; // 总时长(秒)
|
||||
|
||||
SysVehicleLocation previousLocation = null;
|
||||
|
||||
for (SysVehicleLocation location : locations) {
|
||||
// 计算累计距离和时间
|
||||
if (previousLocation != null) {
|
||||
BigDecimal distance = calculateDistance(previousLocation, location);
|
||||
cumulativeDistance = cumulativeDistance.add(distance);
|
||||
cumulativeTime += calculateTimeInterval(previousLocation, location);
|
||||
}
|
||||
|
||||
// 计算进度百分比
|
||||
long currentTime = (location.getTimestamp().getTime() - startTime.getTime()) / 1000;
|
||||
BigDecimal progressPercentage = totalDuration > 0 ?
|
||||
BigDecimal.valueOf(currentTime * 100.0 / totalDuration).setScale(2, RoundingMode.HALF_UP) :
|
||||
BigDecimal.ZERO;
|
||||
|
||||
// 计算与上一点的时间间隔
|
||||
Long intervalMs = previousLocation != null ?
|
||||
location.getTimestamp().getTime() - previousLocation.getTimestamp().getTime() : 0L;
|
||||
|
||||
VehicleLocationPlaybackVO playbackVO = VehicleLocationPlaybackVO.builder()
|
||||
.vehicleId(location.getVehicleId())
|
||||
.licensePlate(location.getLicensePlate())
|
||||
.vehicleType(location.getVehicleType())
|
||||
.sequenceNumber(sequenceNumber.getAndIncrement())
|
||||
.longitude(location.getLongitude())
|
||||
.latitude(location.getLatitude())
|
||||
.altitude(location.getAltitude())
|
||||
.speed(location.getSpeed())
|
||||
.heading(location.getHeading())
|
||||
.timestamp(location.getTimestamp())
|
||||
.dataQuality(location.getDataQuality())
|
||||
.intervalMs(intervalMs)
|
||||
.cumulativeDistance(cumulativeDistance)
|
||||
.cumulativeTime(cumulativeTime)
|
||||
.progressPercentage(progressPercentage)
|
||||
.isKeyFrame(isKeyFrame(location, previousLocation))
|
||||
.keyFrameType(getKeyFrameType(location, previousLocation, sequenceNumber.get() == 2,
|
||||
sequenceNumber.get() == locations.size() + 1))
|
||||
.suggestedDelay(calculateSuggestedDelay(intervalMs))
|
||||
.build();
|
||||
|
||||
playbackData.add(playbackVO);
|
||||
previousLocation = location;
|
||||
}
|
||||
|
||||
return playbackData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从位置数据构建轨迹对象
|
||||
*/
|
||||
private VehicleTrajectoryVO buildTrajectoryFromLocations(Long vehicleId, List<SysVehicleLocation> locations,
|
||||
Date startTime, Date endTime, Boolean simplified,
|
||||
Boolean includeStatistics) {
|
||||
if (CollectionUtils.isEmpty(locations)) {
|
||||
return createEmptyTrajectory(vehicleId, startTime, endTime);
|
||||
}
|
||||
|
||||
SysVehicleLocation firstLocation = locations.get(0);
|
||||
List<TrajectoryPoint> points = convertToTrajectoryPoints(locations);
|
||||
|
||||
TrajectoryStatistics statistics = null;
|
||||
if (includeStatistics != null && includeStatistics) {
|
||||
statistics = getTrajectoryStatistics(vehicleId, startTime, endTime);
|
||||
}
|
||||
|
||||
return VehicleTrajectoryVO.builder()
|
||||
.vehicleId(vehicleId)
|
||||
.licensePlate(firstLocation.getLicensePlate())
|
||||
.vehicleType(firstLocation.getVehicleType())
|
||||
.brand(firstLocation.getBrand())
|
||||
.owningUnit(firstLocation.getOwningUnit())
|
||||
.startTime(startTime)
|
||||
.endTime(endTime)
|
||||
.statistics(statistics)
|
||||
.points(points)
|
||||
.simplified(simplified)
|
||||
.dataQuality(calculateOverallDataQuality(locations))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两点间距离(米)
|
||||
*/
|
||||
private BigDecimal calculateDistance(SysVehicleLocation from, SysVehicleLocation to) {
|
||||
if (from == null || to == null ||
|
||||
from.getLongitude() == null || from.getLatitude() == null ||
|
||||
to.getLongitude() == null || to.getLatitude() == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
return GeoUtils.calculateDistanceAsBigDecimal(
|
||||
from.getLatitude().doubleValue(),
|
||||
from.getLongitude().doubleValue(),
|
||||
to.getLatitude().doubleValue(),
|
||||
to.getLongitude().doubleValue()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两点间时间间隔(秒)
|
||||
*/
|
||||
private Long calculateTimeInterval(SysVehicleLocation from, SysVehicleLocation to) {
|
||||
if (from == null || to == null || from.getTimestamp() == null || to.getTimestamp() == null) {
|
||||
return 0L;
|
||||
}
|
||||
return (to.getTimestamp().getTime() - from.getTimestamp().getTime()) / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为停车点
|
||||
*/
|
||||
private Boolean isStopPoint(SysVehicleLocation location) {
|
||||
if (location == null || location.getSpeed() == null) {
|
||||
return false;
|
||||
}
|
||||
return location.getSpeed().compareTo(BigDecimal.valueOf(2.0)) <= 0; // 速度小于等于2km/h认为是停车
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为关键帧
|
||||
*/
|
||||
private Boolean isKeyFrame(SysVehicleLocation current, SysVehicleLocation previous) {
|
||||
if (current == null) {
|
||||
return false;
|
||||
}
|
||||
if (previous == null) {
|
||||
return true; // 起点
|
||||
}
|
||||
|
||||
// 速度变化较大
|
||||
if (current.getSpeed() != null && previous.getSpeed() != null) {
|
||||
BigDecimal speedDiff = current.getSpeed().subtract(previous.getSpeed()).abs();
|
||||
if (speedDiff.compareTo(BigDecimal.valueOf(10.0)) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 方向变化较大
|
||||
if (current.getHeading() != null && previous.getHeading() != null) {
|
||||
BigDecimal headingDiff = current.getHeading().subtract(previous.getHeading()).abs();
|
||||
if (headingDiff.compareTo(BigDecimal.valueOf(30.0)) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关键帧类型
|
||||
*/
|
||||
private String getKeyFrameType(SysVehicleLocation current, SysVehicleLocation previous,
|
||||
boolean isFirst, boolean isLast) {
|
||||
if (isFirst) {
|
||||
return "START";
|
||||
}
|
||||
if (isLast) {
|
||||
return "END";
|
||||
}
|
||||
if (isStopPoint(current)) {
|
||||
return "STOP";
|
||||
}
|
||||
if (isKeyFrame(current, previous)) {
|
||||
return "TURN";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算建议延迟时间(毫秒)
|
||||
*/
|
||||
private Long calculateSuggestedDelay(Long intervalMs) {
|
||||
if (intervalMs == null || intervalMs <= 0) {
|
||||
return 1000L; // 默认1秒
|
||||
}
|
||||
|
||||
// 根据实际时间间隔调整回放速度
|
||||
if (intervalMs < 5000) {
|
||||
return intervalMs; // 小于5秒,按实际时间播放
|
||||
} else if (intervalMs < 30000) {
|
||||
return intervalMs / 2; // 5-30秒,2倍速播放
|
||||
} else {
|
||||
return Math.min(intervalMs / 5, 10000L); // 大于30秒,5倍速播放,最大10秒
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算整体数据质量
|
||||
*/
|
||||
private String calculateOverallDataQuality(List<SysVehicleLocation> locations) {
|
||||
if (CollectionUtils.isEmpty(locations)) {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
Map<String, Long> qualityCount = locations.stream()
|
||||
.filter(loc -> StringUtils.hasText(loc.getDataQuality()))
|
||||
.collect(Collectors.groupingBy(SysVehicleLocation::getDataQuality, Collectors.counting()));
|
||||
|
||||
if (qualityCount.isEmpty()) {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
// 按质量等级排序,返回最高质量
|
||||
if (qualityCount.containsKey("HIGH")) {
|
||||
return "HIGH";
|
||||
} else if (qualityCount.containsKey("MEDIUM")) {
|
||||
return "MEDIUM";
|
||||
} else if (qualityCount.containsKey("LOW")) {
|
||||
return "LOW";
|
||||
} else {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时长
|
||||
*/
|
||||
private String formatDuration(Long durationSeconds) {
|
||||
if (durationSeconds == null || durationSeconds <= 0) {
|
||||
return "0分钟";
|
||||
}
|
||||
|
||||
long hours = durationSeconds / 3600;
|
||||
long minutes = (durationSeconds % 3600) / 60;
|
||||
long seconds = durationSeconds % 60;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (hours > 0) {
|
||||
sb.append(hours).append("小时");
|
||||
}
|
||||
if (minutes > 0) {
|
||||
sb.append(minutes).append("分钟");
|
||||
}
|
||||
if (seconds > 0 && hours == 0) { // 只有在小于1小时时才显示秒
|
||||
sb.append(seconds).append("秒");
|
||||
}
|
||||
|
||||
return sb.length() > 0 ? sb.toString() : "0分钟";
|
||||
}
|
||||
}
|
||||
@ -43,7 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
vl.updated_at
|
||||
from vehicle_locations vl
|
||||
left join sys_vehicle_info vi on vl.vehicle_id = vi.vehicle_id
|
||||
left join sys_vehicle_type vt on vi.type_id = vt.type_id
|
||||
left join sys_vehicle_type vt on vi.type_code = vt.type_code
|
||||
</sql>
|
||||
|
||||
<select id="selectSysVehicleLocationList" parameterType="SysVehicleLocation" resultMap="SysVehicleLocationResult">
|
||||
@ -88,4 +88,138 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
<!-- 轨迹查询(支持采样优化) -->
|
||||
<select id="selectVehicleTrajectory" resultMap="SysVehicleLocationResult">
|
||||
<include refid="selectSysVehicleLocationVo"/>
|
||||
WHERE vl.vehicle_id = #{vehicleId}
|
||||
AND vl.timestamp BETWEEN #{startTime} AND #{endTime}
|
||||
<if test="simplified == true and maxPoints != null">
|
||||
AND vl.id IN (
|
||||
SELECT id FROM (
|
||||
SELECT id, ROW_NUMBER() OVER (ORDER BY timestamp) as rn,
|
||||
COUNT(*) OVER() as total_count
|
||||
FROM vehicle_locations
|
||||
WHERE vehicle_id = #{vehicleId}
|
||||
AND timestamp BETWEEN #{startTime} AND #{endTime}
|
||||
) t
|
||||
WHERE rn % GREATEST(1, total_count / #{maxPoints}) = 1
|
||||
)
|
||||
</if>
|
||||
ORDER BY vl.timestamp
|
||||
</select>
|
||||
|
||||
<!-- 轨迹统计信息查询 -->
|
||||
<resultMap type="com.qaup.system.domain.vo.TrajectoryStatistics" id="TrajectoryStatisticsResult">
|
||||
<result property="pointCount" column="point_count" />
|
||||
<result property="firstPointTime" column="first_point_time" />
|
||||
<result property="lastPointTime" column="last_point_time" />
|
||||
<result property="averageSpeed" column="average_speed" />
|
||||
<result property="maxSpeed" column="max_speed" />
|
||||
<result property="minSpeed" column="min_speed" />
|
||||
<result property="durationSeconds" column="duration_seconds" />
|
||||
<result property="totalDistance" column="total_distance" />
|
||||
<result property="maxAltitude" column="max_altitude" />
|
||||
<result property="minAltitude" column="min_altitude" />
|
||||
<result property="averageAltitude" column="average_altitude" />
|
||||
</resultMap>
|
||||
|
||||
<select id="selectTrajectoryStatistics" resultMap="TrajectoryStatisticsResult">
|
||||
SELECT
|
||||
COUNT(*) as point_count,
|
||||
MIN(timestamp) as first_point_time,
|
||||
MAX(timestamp) as last_point_time,
|
||||
AVG(speed) as average_speed,
|
||||
MAX(speed) as max_speed,
|
||||
MIN(speed) as min_speed,
|
||||
EXTRACT(EPOCH FROM (MAX(timestamp) - MIN(timestamp))) as duration_seconds,
|
||||
MAX(altitude) as max_altitude,
|
||||
MIN(altitude) as min_altitude,
|
||||
AVG(altitude) as average_altitude,
|
||||
-- 使用PostGIS计算总距离
|
||||
COALESCE(
|
||||
ST_Length(
|
||||
ST_MakeLine(
|
||||
location ORDER BY timestamp
|
||||
)::geography
|
||||
) / 1000, 0
|
||||
) as total_distance
|
||||
FROM vehicle_locations
|
||||
WHERE vehicle_id = #{vehicleId}
|
||||
AND timestamp BETWEEN #{startTime} AND #{endTime}
|
||||
</select>
|
||||
|
||||
<!-- 轨迹回放数据查询 -->
|
||||
<select id="selectTrajectoryPlaybackData" resultMap="SysVehicleLocationResult">
|
||||
WITH trajectory_with_intervals AS (
|
||||
SELECT vl.*,
|
||||
ROW_NUMBER() OVER (ORDER BY vl.timestamp) as seq_num,
|
||||
EXTRACT(EPOCH FROM (vl.timestamp - LAG(vl.timestamp) OVER (ORDER BY vl.timestamp))) as interval_seconds
|
||||
FROM vehicle_locations vl
|
||||
WHERE vl.vehicle_id = #{vehicleId}
|
||||
AND vl.timestamp BETWEEN #{startTime} AND #{endTime}
|
||||
),
|
||||
sampled_trajectory AS (
|
||||
SELECT * FROM trajectory_with_intervals
|
||||
WHERE seq_num = 1
|
||||
OR interval_seconds >= #{intervalSeconds}
|
||||
OR seq_num = (SELECT MAX(seq_num) FROM trajectory_with_intervals)
|
||||
)
|
||||
SELECT
|
||||
st.id,
|
||||
st.vehicle_id,
|
||||
vi.license_plate,
|
||||
vt.type_name,
|
||||
vi.brand,
|
||||
vi.owning_unit,
|
||||
ST_AsText(st.location) as location,
|
||||
ST_X(st.location) as longitude,
|
||||
ST_Y(st.location) as latitude,
|
||||
st.altitude,
|
||||
st.speed,
|
||||
st.heading,
|
||||
st.data_quality,
|
||||
st.timestamp,
|
||||
st.created_at,
|
||||
st.updated_at
|
||||
FROM sampled_trajectory st
|
||||
LEFT JOIN sys_vehicle_info vi ON st.vehicle_id = vi.vehicle_id
|
||||
LEFT JOIN sys_vehicle_type vt ON vi.type_code = vt.type_code
|
||||
ORDER BY st.timestamp
|
||||
</select>
|
||||
|
||||
<!-- 区域内轨迹查询 -->
|
||||
<select id="selectVehicleTrajectoryInArea" resultMap="SysVehicleLocationResult">
|
||||
<include refid="selectSysVehicleLocationVo"/>
|
||||
WHERE vl.vehicle_id = #{vehicleId}
|
||||
AND vl.timestamp BETWEEN #{startTime} AND #{endTime}
|
||||
AND ST_Contains(ST_GeomFromText(#{areaWkt}, 4326), vl.location)
|
||||
ORDER BY vl.timestamp
|
||||
</select>
|
||||
|
||||
<!-- 批量车辆轨迹查询 -->
|
||||
<select id="selectBatchVehicleTrajectory" resultMap="SysVehicleLocationResult">
|
||||
<include refid="selectSysVehicleLocationVo"/>
|
||||
WHERE vl.vehicle_id IN
|
||||
<foreach item="vehicleId" collection="vehicleIds" open="(" separator="," close=")">
|
||||
#{vehicleId}
|
||||
</foreach>
|
||||
AND vl.timestamp BETWEEN #{startTime} AND #{endTime}
|
||||
<if test="simplified == true and maxPoints != null">
|
||||
AND vl.id IN (
|
||||
SELECT id FROM (
|
||||
SELECT id, ROW_NUMBER() OVER (PARTITION BY vehicle_id ORDER BY timestamp) as rn,
|
||||
COUNT(*) OVER(PARTITION BY vehicle_id) as total_count
|
||||
FROM vehicle_locations
|
||||
WHERE vehicle_id IN
|
||||
<foreach item="vehicleId" collection="vehicleIds" open="(" separator="," close=")">
|
||||
#{vehicleId}
|
||||
</foreach>
|
||||
AND timestamp BETWEEN #{startTime} AND #{endTime}
|
||||
) t
|
||||
WHERE rn % GREATEST(1, total_count / #{maxPoints}) = 1
|
||||
)
|
||||
</if>
|
||||
ORDER BY vl.vehicle_id, vl.timestamp
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "qaup",
|
||||
"version": "3.8.9",
|
||||
"version": "1.0.1",
|
||||
"description": "后台管理系统",
|
||||
"author": "Tellme",
|
||||
"license": "MIT",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user