From 462bf3dc77b60d8d945f5a6446fa7d4bf4ce3c77 Mon Sep 17 00:00:00 2001 From: Tian jianyong <11429339@qq.com> Date: Tue, 15 Jul 2025 16:51:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=88=AA=E7=A9=BA=E5=99=A8?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E6=8E=A5=E5=8F=A3=E7=9A=84=E6=A8=A1=E6=8B=9F?= =?UTF-8?q?=E5=92=8C=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- VERSION.md | 2 +- changelog.md | 78 ++ doc/design/vehicle-trajectory-api.md | 232 +++++ doc/{ => guide}/guide.md | 246 +++++ doc/requirement/aircraft_status_api.md | 30 + doc/requirement/area.md | 10 + doc/requirement/requirements.md | 48 + doc/requirement/route_api.md | 895 ++++++++++++++++++ doc/work/数据库合并执行指南_20250115.md | 1 - doc/work/数据库合并方案_20250115.md | 2 +- doc/work/数据库设置检查清单_20250115.md | 2 +- doc/work/架构优化报告_20250712.md | 2 +- doc/work/车辆ID映射修复_20250115.md | 2 +- doc/work/项目代码合并方案_20250115.md | 2 +- doc/work/项目合并完成总结_20250115.md | 2 +- pom.xml | 4 +- qaup-admin/pom.xml | 2 +- .../system/SysVehicleLocationController.java | 87 +- qaup-admin/src/main/resources/application.yml | 21 +- qaup-collision/pom.xml | 2 +- .../area/service/AreaConfigImportService.java | 2 - .../common/adapter/QuapDataAdapter.java | 76 +- .../qaup/collision/common/model/Aircraft.java | 119 ++- .../collision/common/model/AircraftRoute.java | 165 ++++ .../common/model/AirportVehicle.java | 41 +- .../collision/common/model/MovingObject.java | 51 +- .../common/model/MovingObjectType.java | 53 -- .../common/model/UnmannedVehicle.java | 87 +- .../model/enums/VehicleTypeConverter.java | 51 +- .../common/model/spatial/VehicleLocation.java | 2 +- .../common/service/SpatialQueryService.java | 3 +- .../service/VehicleLocationService.java | 29 +- .../service/VehiclePermissionService.java | 26 +- .../datacollector/dao/DataCollectorDao.java | 156 ++- .../datacollector/dto/AircraftRouteDTO.java | 128 +++ .../datacollector/dto/AircraftStatusDTO.java | 56 ++ .../service/DataCollectorService.java | 212 ++++- .../UnmannedVehicleControlService.java | 11 +- .../VehicleDataPersistenceService.java | 2 +- .../service/SpeedCalculationService.java | 16 +- .../UnmannedVehicleGeofenceService.java | 9 +- .../model/dto/ConflictAlertEvent.java | 7 +- .../service/PathConflictDetectionService.java | 23 +- .../rule/model/entity/SpatialRule.java | 2 +- .../service/LocationRuleQueryService.java | 8 +- .../service/RealTimeViolationDetector.java | 3 +- .../rule/service/RuleExecutionEngine.java | 2 +- .../rule/service/RulePriorityService.java | 2 +- .../rule/service/SpatialRuleService.java | 2 +- .../service/VehicleTypePermissionService.java | 2 +- .../impl/LocationRuleQueryServiceImpl.java | 7 +- .../impl/RealTimeViolationDetectorImpl.java | 2 +- .../service/impl/RuleExecutionEngineImpl.java | 9 +- .../service/impl/RulePriorityServiceImpl.java | 3 +- .../impl/RuleViolationProcessorImpl.java | 42 - .../service/impl/SpatialRuleServiceImpl.java | 3 +- .../VehicleTypePermissionServiceImpl.java | 37 +- .../RuleEventWebSocketPublisher.java | 7 +- .../controller/GeopositionController.java | 2 +- .../controller/MovingObjectType.java | 5 + .../event/AircraftRouteUpdateEvent.java | 57 ++ .../AircraftRouteUpdateEventListener.java | 70 ++ .../common/adapter/QuapDataAdapterTest.java | 35 +- .../UnmannedVehicleGeofenceServiceTest.java | 17 +- ...ConflictDetectionServiceNullSpeedTest.java | 9 +- qaup-common/pom.xml | 2 +- .../com/qaup/common/annotation/Excel.java | 6 +- .../qaup/common/constant/GeoConstants.java | 77 ++ .../java/com/qaup/common/utils/DateUtils.java | 7 + .../java/com/qaup/common/utils/GeoUtils.java | 200 ++++ .../com/qaup/common/utils/StringUtils.java | 7 + .../com/qaup/common/utils/poi/ExcelUtil.java | 8 +- .../common/utils/reflect/ReflectUtils.java | 4 +- .../qaup/common/utils/spring/SpringUtils.java | 5 +- qaup-framework/pom.xml | 2 +- .../config/FastJson2JsonRedisSerializer.java | 5 +- .../com/qaup/framework/config/I18nConfig.java | 4 +- .../qaup/framework/config/MyBatisConfig.java | 6 +- .../qaup/framework/config/RedisConfig.java | 3 +- .../framework/config/ResourcesConfig.java | 5 +- .../properties/PermitAllUrlProperties.java | 18 +- .../interceptor/RepeatSubmitInterceptor.java | 8 +- .../filter/JwtAuthenticationTokenFilter.java | 9 +- .../handle/AuthenticationEntryPointImpl.java | 4 - .../web/exception/GlobalExceptionHandler.java | 2 + qaup-generator/pom.xml | 2 +- qaup-quartz/pom.xml | 2 +- qaup-system/pom.xml | 46 +- .../qaup/system/domain/SysVehicleType.java | 6 +- .../domain/dto/TrajectoryQueryRequest.java | 70 ++ .../system/domain/vo/TrajectoryPoint.java | 57 ++ .../domain/vo/TrajectoryStatistics.java | 67 ++ .../domain/vo/VehicleLocationPlaybackVO.java | 79 ++ .../system/domain/vo/VehicleTrajectoryVO.java | 61 ++ .../mapper/SysVehicleLocationMapper.java | 79 +- .../service/ISysVehicleLocationService.java | 64 +- .../impl/SysVehicleLocationServiceImpl.java | 515 +++++++++- .../system/SysVehicleLocationMapper.xml | 138 ++- qaup-ui/package.json | 2 +- qaup-ui/src/views/index.vue | 2 +- sql/fix_vehicle_type_enum.sql | 26 + sql/qaup_database_complete_init.sql | 4 +- tools/mock_server.py | 296 +++++- tools/test_websocket.html | 20 +- 105 files changed, 4697 insertions(+), 546 deletions(-) create mode 100644 doc/design/vehicle-trajectory-api.md rename doc/{ => guide}/guide.md (55%) create mode 100644 doc/requirement/aircraft_status_api.md create mode 100644 doc/requirement/area.md create mode 100644 doc/requirement/route_api.md create mode 100644 qaup-collision/src/main/java/com/qaup/collision/common/model/AircraftRoute.java delete mode 100644 qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObjectType.java create mode 100644 qaup-collision/src/main/java/com/qaup/collision/datacollector/dto/AircraftRouteDTO.java create mode 100644 qaup-collision/src/main/java/com/qaup/collision/datacollector/dto/AircraftStatusDTO.java create mode 100644 qaup-collision/src/main/java/com/qaup/collision/websocket/controller/MovingObjectType.java create mode 100644 qaup-collision/src/main/java/com/qaup/collision/websocket/event/AircraftRouteUpdateEvent.java create mode 100644 qaup-collision/src/main/java/com/qaup/collision/websocket/listener/AircraftRouteUpdateEventListener.java create mode 100644 qaup-common/src/main/java/com/qaup/common/constant/GeoConstants.java create mode 100644 qaup-common/src/main/java/com/qaup/common/utils/GeoUtils.java create mode 100644 qaup-system/src/main/java/com/qaup/system/domain/dto/TrajectoryQueryRequest.java create mode 100644 qaup-system/src/main/java/com/qaup/system/domain/vo/TrajectoryPoint.java create mode 100644 qaup-system/src/main/java/com/qaup/system/domain/vo/TrajectoryStatistics.java create mode 100644 qaup-system/src/main/java/com/qaup/system/domain/vo/VehicleLocationPlaybackVO.java create mode 100644 qaup-system/src/main/java/com/qaup/system/domain/vo/VehicleTrajectoryVO.java create mode 100644 sql/fix_vehicle_type_enum.sql diff --git a/README.md b/README.md index 0197e7bd..498ac0ac 100644 --- a/README.md +++ b/README.md @@ -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` \ No newline at end of file diff --git a/VERSION.md b/VERSION.md index 4209dba2..ed63cdf2 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -0.3.8 \ No newline at end of file +0.3.9 \ No newline at end of file diff --git a/changelog.md b/changelog.md index 7bddd904..f423eff4 100644 --- a/changelog.md +++ b/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` + - 路由段数据结构完善 + +### 🛠️ **技术债务清理** + +- **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** diff --git a/doc/design/vehicle-trajectory-api.md b/doc/design/vehicle-trajectory-api.md new file mode 100644 index 00000000..31b2ad3a --- /dev/null +++ b/doc/design/vehicle-trajectory-api.md @@ -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" +``` diff --git a/doc/guide.md b/doc/guide/guide.md similarity index 55% rename from doc/guide.md rename to doc/guide/guide.md index 89c4a610..deea1ff4 100644 --- a/doc/guide.md +++ b/doc/guide/guide.md @@ -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 ` + +2. **连接超时**: + - 检查Mock服务是否运行在端口8090 + - 确认Java服务是否运行在端口8080 + +3. **状态不变化**: + - 等待完整的状态周期(122秒) + - 检查Mock服务日志 + +#### 服务状态检查 + +```bash +# 检查Mock服务状态 +curl -I http://localhost:8090/login + +# 检查Java服务状态 +curl -I http://localhost:8080/actuator/health +``` ## 核心数据结构 diff --git a/doc/requirement/aircraft_status_api.md b/doc/requirement/aircraft_status_api.md new file mode 100644 index 00000000..2d3cbaf2 --- /dev/null +++ b/doc/requirement/aircraft_status_api.md @@ -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 +} \ No newline at end of file diff --git a/doc/requirement/area.md b/doc/requirement/area.md new file mode 100644 index 00000000..8c2d1502 --- /dev/null +++ b/doc/requirement/area.md @@ -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)) \ No newline at end of file diff --git a/doc/requirement/requirements.md b/doc/requirement/requirements.md index ff7dbecd..68fb2a04 100644 --- a/doc/requirement/requirements.md +++ b/doc/requirement/requirements.md @@ -2,6 +2,54 @@ ## 需求列表(按时间跟踪) +### 2025-06-03 + +- 需求:无人车轨迹回放 +- 分析: + - 需要对无人车的运行轨迹进行查询和显示 +- 功能模块: + - 数据存储模块:存储无人车的位置变更数据(具备) + - 数据处理模块:根据无人车的位置历史数据,提供轨迹查询API(新增) + - 前端展示模块:对无人车的运行轨迹进行查询和显示(新增) + +### 2025-06-02 + +- 需求:无人车路由导入 +- 分析: + - 需要对无人车的路由(文件形式)进行导入 + - 需要对无人车的路由列表进行查询和显示 +- 功能模块: + - 后台管理模块:导入无人车的路由列表(新增)并提供查询接口 + - 数据存储模块:存储无人车的路由列表(新增) +- 技术细节: + - 路由数据结构(编号、名称、路径地理信息、生效时间段、优先级等) + - 导入功能:支持文件上传和解析(CSV、JSON等格式) + - 查询功能:提供分页查询和条件过滤 + - 显示功能:在前端页面展示路由列表和详情 + +### 2025-06-01 + +- 需求:采集和显示无人车的运行状态 +- 分析: + - 需要获取无人车的运行状态数据(当前路径、运行时间、运行距离、电池电量等) + - 需要对无人车的运行状态进行显示 +- 功能模块: + - 数据采集模块:获取无人车的运行状态数据(新增) + - 数据存储模块:存储无人车的运行状态数据(新增) + - 数据处理模块:根据无人车的运行状态数据,发送websocket无人车状态消息到前端(新增) + +### 2025-05-01 + +- 需求:基于路径的碰撞预警,当航空器或机场车辆的路径与无人车路径之间可能发生碰撞时,进行预警或报警 +- 分析: + - 需要获取车辆的实时位置数据 + - 需要获取航空器和车辆的路径数据 + - 需要对车辆进行碰撞避免预警 +- 功能模块: + - 数据采集模块:获取车辆的实时位置数据(具备)和路径数据(新增) + - 数据存储模块:存储车辆的实时位置数据和路径数据(增加路径数据) + - 数据处理模块:根据车辆的实时位置数据,对车辆进行碰撞避免预警(新增) + ### 2025-05-01 - 需求: diff --git a/doc/requirement/route_api.md b/doc/requirement/route_api.md new file mode 100644 index 00000000..e1910dc7 --- /dev/null +++ b/doc/requirement/route_api.md @@ -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一致 \ No newline at end of file diff --git a/doc/work/数据库合并执行指南_20250115.md b/doc/work/数据库合并执行指南_20250115.md index edc3b132..354aa8ae 100644 --- a/doc/work/数据库合并执行指南_20250115.md +++ b/doc/work/数据库合并执行指南_20250115.md @@ -65,7 +65,6 @@ SELECT * FROM vehicle_complete_info LIMIT 5; - `sys_vehicle_info` - 车辆基础信息 - `sys_driver_info` - 驾驶员信息 - `sys_vehicle_type` - 车辆类型 -- 其他若依框架基础表... ### 新增表(来自CollisionAvoidanceSystem) - `vehicle_locations` - 车辆实时位置(支持PostGIS空间查询) diff --git a/doc/work/数据库合并方案_20250115.md b/doc/work/数据库合并方案_20250115.md index 5d3b9641..7eea951e 100644 --- a/doc/work/数据库合并方案_20250115.md +++ b/doc/work/数据库合并方案_20250115.md @@ -7,7 +7,7 @@ 将CollisionAvoidanceSystem项目的空间数据表合并到QAUP-Management项目数据库中,实现统一的数据库架构。 # 项目概述 -QAUP-Management是基于若依框架的车辆管理系统,CollisionAvoidanceSystem是机场碰撞避免系统。两个项目都使用PostgreSQL数据库,表结构完全互补,无冲突。 +QAUP-Management是车辆管理系统,CollisionAvoidanceSystem是机场碰撞避免系统。两个项目都使用PostgreSQL数据库,表结构完全互补,无冲突。 --- *以下部分由 AI 在协议执行过程中维护* diff --git a/doc/work/数据库设置检查清单_20250115.md b/doc/work/数据库设置检查清单_20250115.md index f0ece3cc..cc56b410 100644 --- a/doc/work/数据库设置检查清单_20250115.md +++ b/doc/work/数据库设置检查清单_20250115.md @@ -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 ``` diff --git a/doc/work/架构优化报告_20250712.md b/doc/work/架构优化报告_20250712.md index 48871511..c33a95aa 100644 --- a/doc/work/架构优化报告_20250712.md +++ b/doc/work/架构优化报告_20250712.md @@ -2,7 +2,7 @@ **项目名称**: QAUP 机场车辆管理和碰撞避免系统 **优化日期**: 2025-01-15 -**项目版本**: 3.8.9 +**项目版本**: 1.0.1 **分析人员**: Claude Code Assistant ## 📋 执行摘要 diff --git a/doc/work/车辆ID映射修复_20250115.md b/doc/work/车辆ID映射修复_20250115.md index dd022415..4995bd72 100644 --- a/doc/work/车辆ID映射修复_20250115.md +++ b/doc/work/车辆ID映射修复_20250115.md @@ -14,7 +14,7 @@ ## 项目概述 QAUP机场车辆管理系统,包含: -- QAUP-Management:基础车辆信息管理(若依框架) +- QAUP-Management:基础车辆信息管理 - CollisionAvoidanceSystem:实时位置监控和碰撞避免 --- diff --git a/doc/work/项目代码合并方案_20250115.md b/doc/work/项目代码合并方案_20250115.md index 6a03a938..e203604e 100644 --- a/doc/work/项目代码合并方案_20250115.md +++ b/doc/work/项目代码合并方案_20250115.md @@ -21,7 +21,7 @@ **QAUP-Management技术栈:** - Spring Boot 2.5.15 + Java 8 - MyBatis + PostgreSQL -- 若依框架(用户权限、菜单管理等) +- 用户权限、菜单管理等) - Maven多模块结构(qaup-admin、qaup-system、qaup-framework等) **CollisionAvoidanceSystem技术栈:** diff --git a/doc/work/项目合并完成总结_20250115.md b/doc/work/项目合并完成总结_20250115.md index 332dce6f..45862d03 100644 --- a/doc/work/项目合并完成总结_20250115.md +++ b/doc/work/项目合并完成总结_20250115.md @@ -33,7 +33,7 @@ ## 🔧 数据适配器架构 ### QuapDataAdapter功能 -- **数据桥接**: 连接若依Service层与CollisionAvoidanceSystem +- **数据桥接**: 连接Service层与CollisionAvoidanceSystem - **类型转换**: SysVehicleInfo ↔ VehicleLocation - **统一接口**: 避免重复编写DAO组件 - **测试覆盖**: 15个单元测试全部通过 diff --git a/pom.xml b/pom.xml index ca5847d3..458ee0e8 100644 --- a/pom.xml +++ b/pom.xml @@ -6,10 +6,10 @@ com.qaup qaup-management - 3.8.9 + 1.0.1 - 3.8.9 + 1.0.1 UTF-8 UTF-8 17 diff --git a/qaup-admin/pom.xml b/qaup-admin/pom.xml index cfe45cc3..16c1b860 100644 --- a/qaup-admin/pom.xml +++ b/qaup-admin/pom.xml @@ -5,7 +5,7 @@ qaup-management com.qaup - 3.8.9 + 1.0.1 4.0.0 jar diff --git a/qaup-admin/src/main/java/com/qaup/web/controller/system/SysVehicleLocationController.java b/qaup-admin/src/main/java/com/qaup/web/controller/system/SysVehicleLocationController.java index 72ea97f1..f4cf06b0 100644 --- a/qaup-admin/src/main/java/com/qaup/web/controller/system/SysVehicleLocationController.java +++ b/qaup-admin/src/main/java/com/qaup/web/controller/system/SysVehicleLocationController.java @@ -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); } -} \ No newline at end of file + + /** + * 车辆轨迹查询接口 + */ + @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 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 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); + } +} \ No newline at end of file diff --git a/qaup-admin/src/main/resources/application.yml b/qaup-admin/src/main/resources/application.yml index 23edc7c3..ec9ac156 100644 --- a/qaup-admin/src/main/resources/application.yml +++ b/qaup-admin/src/main/resources/application.yml @@ -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 diff --git a/qaup-collision/pom.xml b/qaup-collision/pom.xml index d8a6c114..d7954741 100644 --- a/qaup-collision/pom.xml +++ b/qaup-collision/pom.xml @@ -5,7 +5,7 @@ qaup-management com.qaup - 3.8.9 + 1.0.1 4.0.0 diff --git a/qaup-collision/src/main/java/com/qaup/collision/area/service/AreaConfigImportService.java b/qaup-collision/src/main/java/com/qaup/collision/area/service/AreaConfigImportService.java index e6c01d80..a9439cf6 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/area/service/AreaConfigImportService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/area/service/AreaConfigImportService.java @@ -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") diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/adapter/QuapDataAdapter.java b/qaup-collision/src/main/java/com/qaup/collision/common/adapter/QuapDataAdapter.java index 2fdf20e0..9a31e58f 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/common/adapter/QuapDataAdapter.java +++ b/qaup-collision/src/main/java/com/qaup/collision/common/adapter/QuapDataAdapter.java @@ -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); - } - /** * 验证适配器初始化状态 * diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/model/Aircraft.java b/qaup-collision/src/main/java/com/qaup/collision/common/model/Aircraft.java index 51ee28de..4c991b14 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/common/model/Aircraft.java +++ b/qaup-collision/src/main/java/com/qaup/collision/common/model/Aircraft.java @@ -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; + } } diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/model/AircraftRoute.java b/qaup-collision/src/main/java/com/qaup/collision/common/model/AircraftRoute.java new file mode 100644 index 00000000..bdaadf34 --- /dev/null +++ b/qaup-collision/src/main/java/com/qaup/collision/common/model/AircraftRoute.java @@ -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 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 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); + } +} \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/model/AirportVehicle.java b/qaup-collision/src/main/java/com/qaup/collision/common/model/AirportVehicle.java index 4b8ea799..82703f1f 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/common/model/AirportVehicle.java +++ b/qaup-collision/src/main/java/com/qaup/collision/common/model/AirportVehicle.java @@ -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; + } } diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObject.java b/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObject.java index 1a06ae93..9064208e 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObject.java +++ b/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObject.java @@ -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; } } diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObjectType.java b/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObjectType.java deleted file mode 100644 index c09e5834..00000000 --- a/qaup-collision/src/main/java/com/qaup/collision/common/model/MovingObjectType.java +++ /dev/null @@ -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; - } -} diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/model/UnmannedVehicle.java b/qaup-collision/src/main/java/com/qaup/collision/common/model/UnmannedVehicle.java index 568029b1..b95da962 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/common/model/UnmannedVehicle.java +++ b/qaup-collision/src/main/java/com/qaup/collision/common/model/UnmannedVehicle.java @@ -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; // 度转米的近似值 + } } diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/model/enums/VehicleTypeConverter.java b/qaup-collision/src/main/java/com/qaup/collision/common/model/enums/VehicleTypeConverter.java index f3581568..d57a03de 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/common/model/enums/VehicleTypeConverter.java +++ b/qaup-collision/src/main/java/com/qaup/collision/common/model/enums/VehicleTypeConverter.java @@ -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; } } diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/model/spatial/VehicleLocation.java b/qaup-collision/src/main/java/com/qaup/collision/common/model/spatial/VehicleLocation.java index f3b54f36..ab0cb505 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/common/model/spatial/VehicleLocation.java +++ b/qaup-collision/src/main/java/com/qaup/collision/common/model/spatial/VehicleLocation.java @@ -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; diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/service/SpatialQueryService.java b/qaup-collision/src/main/java/com/qaup/collision/common/service/SpatialQueryService.java index 153fdb2f..8e9afcac 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/common/service/SpatialQueryService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/common/service/SpatialQueryService.java @@ -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) { diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/service/VehicleLocationService.java b/qaup-collision/src/main/java/com/qaup/collision/common/service/VehicleLocationService.java index d148351d..47b7f036 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/common/service/VehicleLocationService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/common/service/VehicleLocationService.java @@ -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 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 applicableRules = locationRuleQueryService.findApplicableRules( diff --git a/qaup-collision/src/main/java/com/qaup/collision/common/service/VehiclePermissionService.java b/qaup-collision/src/main/java/com/qaup/collision/common/service/VehiclePermissionService.java index 6d3f9ac3..30619fdf 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/common/service/VehiclePermissionService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/common/service/VehiclePermissionService.java @@ -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) { diff --git a/qaup-collision/src/main/java/com/qaup/collision/datacollector/dao/DataCollectorDao.java b/qaup-collision/src/main/java/com/qaup/collision/datacollector/dao/DataCollectorDao.java index ae4c43f9..92ea6adf 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/datacollector/dao/DataCollectorDao.java +++ b/qaup-collision/src/main/java/com/qaup/collision/datacollector/dao/DataCollectorDao.java @@ -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 requestEntity = new HttpEntity<>(headers); + + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + requestEntity, + new ParameterizedTypeReference>() {} + ); + + Response 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 requestEntity = new HttpEntity<>(headers); + + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + requestEntity, + new ParameterizedTypeReference>() {} + ); + + Response 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 requestEntity = new HttpEntity<>(headers); + + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + requestEntity, + new ParameterizedTypeReference>() {} + ); + + Response 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; + } + } } diff --git a/qaup-collision/src/main/java/com/qaup/collision/datacollector/dto/AircraftRouteDTO.java b/qaup-collision/src/main/java/com/qaup/collision/datacollector/dto/AircraftRouteDTO.java new file mode 100644 index 00000000..8628a981 --- /dev/null +++ b/qaup-collision/src/main/java/com/qaup/collision/datacollector/dto/AircraftRouteDTO.java @@ -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 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> coordinates; + } + + /** + * 属性信息 + */ + @Data + @NoArgsConstructor + @AllArgsConstructor + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Properties { + + /** + * 路径段编码 + */ + private String code; + } +} \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/datacollector/dto/AircraftStatusDTO.java b/qaup-collision/src/main/java/com/qaup/collision/datacollector/dto/AircraftStatusDTO.java new file mode 100644 index 00000000..494163e1 --- /dev/null +++ b/qaup-collision/src/main/java/com/qaup/collision/datacollector/dto/AircraftStatusDTO.java @@ -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; +} \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java b/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java index 6120dbdd..237f811c 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/DataCollectorService.java @@ -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 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 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 + List 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 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 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 vehicleInfoOptional = quapDataAdapter.findVehicleByLicensePlate(movingObject.getObjectId()); if (vehicleInfoOptional.isEmpty()) { @@ -640,7 +824,7 @@ public class DataCollectorService { // 过滤出无人车对象 List 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 diff --git a/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/UnmannedVehicleControlService.java b/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/UnmannedVehicleControlService.java index ef087219..f7709161 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/UnmannedVehicleControlService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/UnmannedVehicleControlService.java @@ -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; } } } \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/VehicleDataPersistenceService.java b/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/VehicleDataPersistenceService.java index 97965049..749f8bfe 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/VehicleDataPersistenceService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/datacollector/service/VehicleDataPersistenceService.java @@ -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; diff --git a/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/SpeedCalculationService.java b/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/SpeedCalculationService.java index 8a720b5d..cf0ea222 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/SpeedCalculationService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/SpeedCalculationService.java @@ -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); } /** diff --git a/qaup-collision/src/main/java/com/qaup/collision/geofence/service/UnmannedVehicleGeofenceService.java b/qaup-collision/src/main/java/com/qaup/collision/geofence/service/UnmannedVehicleGeofenceService.java index 401dddbd..84acdaee 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/geofence/service/UnmannedVehicleGeofenceService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/geofence/service/UnmannedVehicleGeofenceService.java @@ -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 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" 并检查 diff --git a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/dto/ConflictAlertEvent.java b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/dto/ConflictAlertEvent.java index 50f5f61f..ea0c6ae2 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/dto/ConflictAlertEvent.java +++ b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/model/dto/ConflictAlertEvent.java @@ -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 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距离冲突点距离 (米) diff --git a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/service/PathConflictDetectionService.java b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/service/PathConflictDetectionService.java index e28dc5d8..7fe08ac0 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/pathconflict/service/PathConflictDetectionService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/pathconflict/service/PathConflictDetectionService.java @@ -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 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 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 alertType; // Changed to Optional private final Optional 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 alertType, // Changed to Optional Optional 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 getAlertType() { return alertType; } // Changed public Optional 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 } } \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/model/entity/SpatialRule.java b/qaup-collision/src/main/java/com/qaup/collision/rule/model/entity/SpatialRule.java index d6cb4e60..2381fad3 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/model/entity/SpatialRule.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/model/entity/SpatialRule.java @@ -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; diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/LocationRuleQueryService.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/LocationRuleQueryService.java index ba4e596d..a309e707 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/LocationRuleQueryService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/LocationRuleQueryService.java @@ -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 getRestrictedRules(org.locationtech.jts.geom.Point location, com.qaup.collision.common.model.MovingObjectType vehicleType, java.time.LocalDateTime timestamp); + List getRestrictedRules(Point location, MovingObjectType vehicleType, LocalDateTime timestamp); /** * 获取车辆类型允许访问的规则列表 @@ -185,7 +185,7 @@ public interface LocationRuleQueryService { * @param timestamp 时间戳 * @return 允许的规则列表 */ - java.util.List getAllowedRules(org.locationtech.jts.geom.Point location, com.qaup.collision.common.model.MovingObjectType vehicleType, java.time.LocalDateTime timestamp); + List getAllowedRules(Point location, MovingObjectType vehicleType, LocalDateTime timestamp); /** * 规则覆盖统计信息 diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/RealTimeViolationDetector.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/RealTimeViolationDetector.java index b14ca14a..64972366 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/RealTimeViolationDetector.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/RealTimeViolationDetector.java @@ -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; diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/RuleExecutionEngine.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/RuleExecutionEngine.java index db36b95a..6af1c2ac 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/RuleExecutionEngine.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/RuleExecutionEngine.java @@ -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; diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/RulePriorityService.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/RulePriorityService.java index 1b67e601..ed3832d9 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/RulePriorityService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/RulePriorityService.java @@ -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; diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/SpatialRuleService.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/SpatialRuleService.java index 56d9ca30..5a519495 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/SpatialRuleService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/SpatialRuleService.java @@ -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; diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/VehicleTypePermissionService.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/VehicleTypePermissionService.java index 0b844147..ba94e08e 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/VehicleTypePermissionService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/VehicleTypePermissionService.java @@ -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; diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/LocationRuleQueryServiceImpl.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/LocationRuleQueryServiceImpl.java index cb053050..001e7981 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/LocationRuleQueryServiceImpl.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/LocationRuleQueryServiceImpl.java @@ -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); diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RealTimeViolationDetectorImpl.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RealTimeViolationDetectorImpl.java index 536f2554..0ae24737 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RealTimeViolationDetectorImpl.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RealTimeViolationDetectorImpl.java @@ -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 diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RuleExecutionEngineImpl.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RuleExecutionEngineImpl.java index 4edc62fe..03006f2f 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RuleExecutionEngineImpl.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RuleExecutionEngineImpl.java @@ -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 vehicleState) { - return generateSimpleDescription(rule, vehicleState); - } - // =============================================== // 统计和查询方法(无人车专用) // =============================================== diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RulePriorityServiceImpl.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RulePriorityServiceImpl.java index 675b47ec..0feadc68 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RulePriorityServiceImpl.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RulePriorityServiceImpl.java @@ -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; diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RuleViolationProcessorImpl.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RuleViolationProcessorImpl.java index 05612574..5349bae0 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RuleViolationProcessorImpl.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/RuleViolationProcessorImpl.java @@ -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 executeResponseActions(RuleViolationEvent event) { List 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); - } - } } \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/SpatialRuleServiceImpl.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/SpatialRuleServiceImpl.java index 8531d729..fa6131f2 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/SpatialRuleServiceImpl.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/SpatialRuleServiceImpl.java @@ -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; diff --git a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/VehicleTypePermissionServiceImpl.java b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/VehicleTypePermissionServiceImpl.java index 25ad0729..e2f782b2 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/VehicleTypePermissionServiceImpl.java +++ b/qaup-collision/src/main/java/com/qaup/collision/rule/service/impl/VehicleTypePermissionServiceImpl.java @@ -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 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 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> 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; - } - } } \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/websocket/broadcaster/RuleEventWebSocketPublisher.java b/qaup-collision/src/main/java/com/qaup/collision/websocket/broadcaster/RuleEventWebSocketPublisher.java index ef8537e7..1e48df70 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/websocket/broadcaster/RuleEventWebSocketPublisher.java +++ b/qaup-collision/src/main/java/com/qaup/collision/websocket/broadcaster/RuleEventWebSocketPublisher.java @@ -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) { diff --git a/qaup-collision/src/main/java/com/qaup/collision/websocket/controller/GeopositionController.java b/qaup-collision/src/main/java/com/qaup/collision/websocket/controller/GeopositionController.java index 8e176b4c..88bb20b3 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/websocket/controller/GeopositionController.java +++ b/qaup-collision/src/main/java/com/qaup/collision/websocket/controller/GeopositionController.java @@ -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; diff --git a/qaup-collision/src/main/java/com/qaup/collision/websocket/controller/MovingObjectType.java b/qaup-collision/src/main/java/com/qaup/collision/websocket/controller/MovingObjectType.java new file mode 100644 index 00000000..48c6b8b6 --- /dev/null +++ b/qaup-collision/src/main/java/com/qaup/collision/websocket/controller/MovingObjectType.java @@ -0,0 +1,5 @@ +package com.qaup.collision.websocket.controller; + +public class MovingObjectType { + +} diff --git a/qaup-collision/src/main/java/com/qaup/collision/websocket/event/AircraftRouteUpdateEvent.java b/qaup-collision/src/main/java/com/qaup/collision/websocket/event/AircraftRouteUpdateEvent.java new file mode 100644 index 00000000..ac18924d --- /dev/null +++ b/qaup-collision/src/main/java/com/qaup/collision/websocket/event/AircraftRouteUpdateEvent.java @@ -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; +} \ No newline at end of file diff --git a/qaup-collision/src/main/java/com/qaup/collision/websocket/listener/AircraftRouteUpdateEventListener.java b/qaup-collision/src/main/java/com/qaup/collision/websocket/listener/AircraftRouteUpdateEventListener.java new file mode 100644 index 00000000..bcd2fe94 --- /dev/null +++ b/qaup-collision/src/main/java/com/qaup/collision/websocket/listener/AircraftRouteUpdateEventListener.java @@ -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; + } + } +} \ No newline at end of file diff --git a/qaup-collision/src/test/java/com/qaup/collision/common/adapter/QuapDataAdapterTest.java b/qaup-collision/src/test/java/com/qaup/collision/common/adapter/QuapDataAdapterTest.java index 92c8660e..af70d2aa 100644 --- a/qaup-collision/src/test/java/com/qaup/collision/common/adapter/QuapDataAdapterTest.java +++ b/qaup-collision/src/test/java/com/qaup/collision/common/adapter/QuapDataAdapterTest.java @@ -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() { diff --git a/qaup-collision/src/test/java/com/qaup/collision/geofence/service/UnmannedVehicleGeofenceServiceTest.java b/qaup-collision/src/test/java/com/qaup/collision/geofence/service/UnmannedVehicleGeofenceServiceTest.java index f51741af..a9c96b7d 100644 --- a/qaup-collision/src/test/java/com/qaup/collision/geofence/service/UnmannedVehicleGeofenceServiceTest.java +++ b/qaup-collision/src/test/java/com/qaup/collision/geofence/service/UnmannedVehicleGeofenceServiceTest.java @@ -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) diff --git a/qaup-collision/src/test/java/com/qaup/collision/pathconflict/service/PathConflictDetectionServiceNullSpeedTest.java b/qaup-collision/src/test/java/com/qaup/collision/pathconflict/service/PathConflictDetectionServiceNullSpeedTest.java index 287dc62e..98e0308c 100644 --- a/qaup-collision/src/test/java/com/qaup/collision/pathconflict/service/PathConflictDetectionServiceNullSpeedTest.java +++ b/qaup-collision/src/test/java/com/qaup/collision/pathconflict/service/PathConflictDetectionServiceNullSpeedTest.java @@ -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 diff --git a/qaup-common/pom.xml b/qaup-common/pom.xml index 9742926f..77307958 100644 --- a/qaup-common/pom.xml +++ b/qaup-common/pom.xml @@ -5,7 +5,7 @@ qaup-management com.qaup - 3.8.9 + 1.0.1 4.0.0 diff --git a/qaup-common/src/main/java/com/qaup/common/annotation/Excel.java b/qaup-common/src/main/java/com/qaup/common/annotation/Excel.java index 95ff3e2b..b2fc897c 100644 --- a/qaup-common/src/main/java/com/qaup/common/annotation/Excel.java +++ b/qaup-common/src/main/java/com/qaup/common/annotation/Excel.java @@ -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中每个列的高度 diff --git a/qaup-common/src/main/java/com/qaup/common/constant/GeoConstants.java b/qaup-common/src/main/java/com/qaup/common/constant/GeoConstants.java new file mode 100644 index 00000000..bd27e997 --- /dev/null +++ b/qaup-common/src/main/java/com/qaup/common/constant/GeoConstants.java @@ -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; +} diff --git a/qaup-common/src/main/java/com/qaup/common/utils/DateUtils.java b/qaup-common/src/main/java/com/qaup/common/utils/DateUtils.java index 0d74de6f..99e707cd 100644 --- a/qaup-common/src/main/java/com/qaup/common/utils/DateUtils.java +++ b/qaup-common/src/main/java/com/qaup/common/utils/DateUtils.java @@ -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"; diff --git a/qaup-common/src/main/java/com/qaup/common/utils/GeoUtils.java b/qaup-common/src/main/java/com/qaup/common/utils/GeoUtils.java new file mode 100644 index 00000000..7550840f --- /dev/null +++ b/qaup-common/src/main/java/com/qaup/common/utils/GeoUtils.java @@ -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); + } +} diff --git a/qaup-common/src/main/java/com/qaup/common/utils/StringUtils.java b/qaup-common/src/main/java/com/qaup/common/utils/StringUtils.java index d1ec1b77..7f190049 100644 --- a/qaup-common/src/main/java/com/qaup/common/utils/StringUtils.java +++ b/qaup-common/src/main/java/com/qaup/common/utils/StringUtils.java @@ -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 = ""; diff --git a/qaup-common/src/main/java/com/qaup/common/utils/poi/ExcelUtil.java b/qaup-common/src/main/java/com/qaup/common/utils/poi/ExcelUtil.java index a6ae1ad7..3ab3f8df 100644 --- a/qaup-common/src/main/java/com/qaup/common/utils/poi/ExcelUtil.java +++ b/qaup-common/src/main/java/com/qaup/common/utils/poi/ExcelUtil.java @@ -419,7 +419,7 @@ public class ExcelUtil 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 } 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 { 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); } diff --git a/qaup-common/src/main/java/com/qaup/common/utils/reflect/ReflectUtils.java b/qaup-common/src/main/java/com/qaup/common/utils/reflect/ReflectUtils.java index 2e4e527f..9165c9fb 100644 --- a/qaup-common/src/main/java/com/qaup/common/utils/reflect/ReflectUtils.java +++ b/qaup-common/src/main/java/com/qaup/common/utils/reflect/ReflectUtils.java @@ -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); } diff --git a/qaup-common/src/main/java/com/qaup/common/utils/spring/SpringUtils.java b/qaup-common/src/main/java/com/qaup/common/utils/spring/SpringUtils.java index 20336b10..a253adef 100644 --- a/qaup-common/src/main/java/com/qaup/common/utils/spring/SpringUtils.java +++ b/qaup-common/src/main/java/com/qaup/common/utils/spring/SpringUtils.java @@ -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; } diff --git a/qaup-framework/pom.xml b/qaup-framework/pom.xml index 7fd90566..b063fdd9 100644 --- a/qaup-framework/pom.xml +++ b/qaup-framework/pom.xml @@ -5,7 +5,7 @@ qaup-management com.qaup - 3.8.9 + 1.0.1 4.0.0 diff --git a/qaup-framework/src/main/java/com/qaup/framework/config/FastJson2JsonRedisSerializer.java b/qaup-framework/src/main/java/com/qaup/framework/config/FastJson2JsonRedisSerializer.java index 04df8306..054004af 100644 --- a/qaup-framework/src/main/java/com/qaup/framework/config/FastJson2JsonRedisSerializer.java +++ b/qaup-framework/src/main/java/com/qaup/framework/config/FastJson2JsonRedisSerializer.java @@ -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 implements RedisSerializer } @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 implements RedisSerializer } @Override - public T deserialize(byte[] bytes) throws SerializationException + public T deserialize(@Nullable byte[] bytes) throws SerializationException { if (bytes == null || bytes.length <= 0) { diff --git a/qaup-framework/src/main/java/com/qaup/framework/config/I18nConfig.java b/qaup-framework/src/main/java/com/qaup/framework/config/I18nConfig.java index 3083427c..6c573210 100644 --- a/qaup-framework/src/main/java/com/qaup/framework/config/I18nConfig.java +++ b/qaup-framework/src/main/java/com/qaup/framework/config/I18nConfig.java @@ -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()); } diff --git a/qaup-framework/src/main/java/com/qaup/framework/config/MyBatisConfig.java b/qaup-framework/src/main/java/com/qaup/framework/config/MyBatisConfig.java index ef227504..f3918822 100644 --- a/qaup-framework/src/main/java/com/qaup/framework/config/MyBatisConfig.java +++ b/qaup-framework/src/main/java/com/qaup/framework/config/MyBatisConfig.java @@ -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(); } } \ No newline at end of file diff --git a/qaup-framework/src/main/java/com/qaup/framework/config/RedisConfig.java b/qaup-framework/src/main/java/com/qaup/framework/config/RedisConfig.java index 760e0194..cb2d19e8 100644 --- a/qaup-framework/src/main/java/com/qaup/framework/config/RedisConfig.java +++ b/qaup-framework/src/main/java/com/qaup/framework/config/RedisConfig.java @@ -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" }) diff --git a/qaup-framework/src/main/java/com/qaup/framework/config/ResourcesConfig.java b/qaup-framework/src/main/java/com/qaup/framework/config/ResourcesConfig.java index 818b39af..62ae6acd 100644 --- a/qaup-framework/src/main/java/com/qaup/framework/config/ResourcesConfig.java +++ b/qaup-framework/src/main/java/com/qaup/framework/config/ResourcesConfig.java @@ -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("/**"); diff --git a/qaup-framework/src/main/java/com/qaup/framework/config/properties/PermitAllUrlProperties.java b/qaup-framework/src/main/java/com/qaup/framework/config/properties/PermitAllUrlProperties.java index 1821c648..00365115 100644 --- a/qaup-framework/src/main/java/com/qaup/framework/config/properties/PermitAllUrlProperties.java +++ b/qaup-framework/src/main/java/com/qaup/framework/config/properties/PermitAllUrlProperties.java @@ -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; } diff --git a/qaup-framework/src/main/java/com/qaup/framework/interceptor/RepeatSubmitInterceptor.java b/qaup-framework/src/main/java/com/qaup/framework/interceptor/RepeatSubmitInterceptor.java index 6223747e..c7bbe03d 100644 --- a/qaup-framework/src/main/java/com/qaup/framework/interceptor/RepeatSubmitInterceptor.java +++ b/qaup-framework/src/main/java/com/qaup/framework/interceptor/RepeatSubmitInterceptor.java @@ -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) { diff --git a/qaup-framework/src/main/java/com/qaup/framework/security/filter/JwtAuthenticationTokenFilter.java b/qaup-framework/src/main/java/com/qaup/framework/security/filter/JwtAuthenticationTokenFilter.java index cc3a307e..55fd14c7 100644 --- a/qaup-framework/src/main/java/com/qaup/framework/security/filter/JwtAuthenticationTokenFilter.java +++ b/qaup-framework/src/main/java/com/qaup/framework/security/filter/JwtAuthenticationTokenFilter.java @@ -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); diff --git a/qaup-framework/src/main/java/com/qaup/framework/security/handle/AuthenticationEntryPointImpl.java b/qaup-framework/src/main/java/com/qaup/framework/security/handle/AuthenticationEntryPointImpl.java index 178c90c6..85e72ba4 100644 --- a/qaup-framework/src/main/java/com/qaup/framework/security/handle/AuthenticationEntryPointImpl.java +++ b/qaup-framework/src/main/java/com/qaup/framework/security/handle/AuthenticationEntryPointImpl.java @@ -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 diff --git a/qaup-framework/src/main/java/com/qaup/framework/web/exception/GlobalExceptionHandler.java b/qaup-framework/src/main/java/com/qaup/framework/web/exception/GlobalExceptionHandler.java index 785ae7ab..34e8f768 100644 --- a/qaup-framework/src/main/java/com/qaup/framework/web/exception/GlobalExceptionHandler.java +++ b/qaup-framework/src/main/java/com/qaup/framework/web/exception/GlobalExceptionHandler.java @@ -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); } diff --git a/qaup-generator/pom.xml b/qaup-generator/pom.xml index d97ccd5b..759ce6e6 100644 --- a/qaup-generator/pom.xml +++ b/qaup-generator/pom.xml @@ -5,7 +5,7 @@ qaup-management com.qaup - 3.8.9 + 1.0.1 4.0.0 diff --git a/qaup-quartz/pom.xml b/qaup-quartz/pom.xml index 057fa73c..49c30226 100644 --- a/qaup-quartz/pom.xml +++ b/qaup-quartz/pom.xml @@ -5,7 +5,7 @@ qaup-management com.qaup - 3.8.9 + 1.0.1 4.0.0 diff --git a/qaup-system/pom.xml b/qaup-system/pom.xml index 70bde102..9b3764cc 100644 --- a/qaup-system/pom.xml +++ b/qaup-system/pom.xml @@ -5,7 +5,7 @@ qaup-management com.qaup - 3.8.9 + 1.0.1 4.0.0 @@ -24,6 +24,50 @@ ${qaup.version} + + + org.projectlombok + lombok + provided + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + + + jakarta.validation + jakarta.validation-api + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.0 + + ${java.version} + ${project.build.sourceEncoding} + true + + -parameters + + + + org.projectlombok + lombok + ${lombok.version} + + + + + + + \ No newline at end of file diff --git a/qaup-system/src/main/java/com/qaup/system/domain/SysVehicleType.java b/qaup-system/src/main/java/com/qaup/system/domain/SysVehicleType.java index 2889ea48..eccb52d8 100644 --- a/qaup-system/src/main/java/com/qaup/system/domain/SysVehicleType.java +++ b/qaup-system/src/main/java/com/qaup/system/domain/SysVehicleType.java @@ -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 { diff --git a/qaup-system/src/main/java/com/qaup/system/domain/dto/TrajectoryQueryRequest.java b/qaup-system/src/main/java/com/qaup/system/domain/dto/TrajectoryQueryRequest.java new file mode 100644 index 00000000..842da772 --- /dev/null +++ b/qaup-system/src/main/java/com/qaup/system/domain/dto/TrajectoryQueryRequest.java @@ -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 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 licensePlates; +} \ No newline at end of file diff --git a/qaup-system/src/main/java/com/qaup/system/domain/vo/TrajectoryPoint.java b/qaup-system/src/main/java/com/qaup/system/domain/vo/TrajectoryPoint.java new file mode 100644 index 00000000..2b64267a --- /dev/null +++ b/qaup-system/src/main/java/com/qaup/system/domain/vo/TrajectoryPoint.java @@ -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; +} diff --git a/qaup-system/src/main/java/com/qaup/system/domain/vo/TrajectoryStatistics.java b/qaup-system/src/main/java/com/qaup/system/domain/vo/TrajectoryStatistics.java new file mode 100644 index 00000000..4a707d1a --- /dev/null +++ b/qaup-system/src/main/java/com/qaup/system/domain/vo/TrajectoryStatistics.java @@ -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; +} diff --git a/qaup-system/src/main/java/com/qaup/system/domain/vo/VehicleLocationPlaybackVO.java b/qaup-system/src/main/java/com/qaup/system/domain/vo/VehicleLocationPlaybackVO.java new file mode 100644 index 00000000..1d355c26 --- /dev/null +++ b/qaup-system/src/main/java/com/qaup/system/domain/vo/VehicleLocationPlaybackVO.java @@ -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; +} diff --git a/qaup-system/src/main/java/com/qaup/system/domain/vo/VehicleTrajectoryVO.java b/qaup-system/src/main/java/com/qaup/system/domain/vo/VehicleTrajectoryVO.java new file mode 100644 index 00000000..b4968c07 --- /dev/null +++ b/qaup-system/src/main/java/com/qaup/system/domain/vo/VehicleTrajectoryVO.java @@ -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 points; + + /** GeoJSON格式轨迹线 */ + private String trajectoryLineGeoJson; + + /** 是否为简化数据 */ + private Boolean simplified; + + /** 数据质量 */ + private String dataQuality; +} diff --git a/qaup-system/src/main/java/com/qaup/system/mapper/SysVehicleLocationMapper.java b/qaup-system/src/main/java/com/qaup/system/mapper/SysVehicleLocationMapper.java index 23623dcf..dc27e271 100644 --- a/qaup-system/src/main/java/com/qaup/system/mapper/SysVehicleLocationMapper.java +++ b/qaup-system/src/main/java/com/qaup/system/mapper/SysVehicleLocationMapper.java @@ -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); -} \ No newline at end of file + + /** + * 查询车辆轨迹数据(支持采样优化) + * + * @param vehicleId 车辆ID + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param simplified 是否简化数据 + * @param maxPoints 最大轨迹点数量 + * @return 车辆运动信息列表 + */ + public List 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 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 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 selectBatchVehicleTrajectory(@Param("vehicleIds") List vehicleIds, + @Param("startTime") Date startTime, + @Param("endTime") Date endTime, + @Param("simplified") Boolean simplified, + @Param("maxPoints") Integer maxPoints); +} \ No newline at end of file diff --git a/qaup-system/src/main/java/com/qaup/system/service/ISysVehicleLocationService.java b/qaup-system/src/main/java/com/qaup/system/service/ISysVehicleLocationService.java index 601096a8..097ffc72 100644 --- a/qaup-system/src/main/java/com/qaup/system/service/ISysVehicleLocationService.java +++ b/qaup-system/src/main/java/com/qaup/system/service/ISysVehicleLocationService.java @@ -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); -} \ No newline at end of file + + /** + * 获取车辆轨迹数据 + * + * @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 getBatchVehicleTrajectory(TrajectoryQueryRequest request); + + /** + * 获取轨迹回放数据 + * + * @param vehicleId 车辆ID + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param intervalSeconds 时间间隔(秒) + * @return 轨迹回放数据列表 + */ + public List 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); +} \ No newline at end of file diff --git a/qaup-system/src/main/java/com/qaup/system/service/impl/SysVehicleLocationServiceImpl.java b/qaup-system/src/main/java/com/qaup/system/service/impl/SysVehicleLocationServiceImpl.java index 63d9e559..29e5812b 100644 --- a/qaup-system/src/main/java/com/qaup/system/service/impl/SysVehicleLocationServiceImpl.java +++ b/qaup-system/src/main/java/com/qaup/system/service/impl/SysVehicleLocationServiceImpl.java @@ -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); } -} \ No newline at end of file + + /** + * 获取车辆轨迹数据 + */ + @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 locations = sysVehicleLocationMapper + .selectVehicleTrajectory(vehicleId, startTime, endTime, simplified, maxPoints); + + if (CollectionUtils.isEmpty(locations)) { + return createEmptyTrajectory(vehicleId, startTime, endTime); + } + + // 获取车辆基础信息(从第一条记录中获取) + SysVehicleLocation firstLocation = locations.get(0); + + // 转换轨迹点数据 + List 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 getBatchVehicleTrajectory(TrajectoryQueryRequest request) { + // 参数校验 + if (request == null || CollectionUtils.isEmpty(request.getVehicleIds()) + || request.getStartTime() == null || request.getEndTime() == null) { + throw new IllegalArgumentException("请求参数不能为空"); + } + + List result = new ArrayList<>(); + + // 批量查询轨迹数据 + List allLocations = sysVehicleLocationMapper + .selectBatchVehicleTrajectory(request.getVehicleIds(), request.getStartTime(), + request.getEndTime(), request.getSimplified(), request.getMaxPoints()); + + // 按车辆ID分组 + Map> locationsByVehicle = allLocations.stream() + .collect(Collectors.groupingBy(SysVehicleLocation::getVehicleId)); + + // 为每个车辆构建轨迹数据 + for (Long vehicleId : request.getVehicleIds()) { + List 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 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 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 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 convertToTrajectoryPoints(List locations) { + if (CollectionUtils.isEmpty(locations)) { + return new ArrayList<>(); + } + + List 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 convertToPlaybackData(List locations, + Date startTime, Date endTime) { + if (CollectionUtils.isEmpty(locations)) { + return new ArrayList<>(); + } + + List 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 locations, + Date startTime, Date endTime, Boolean simplified, + Boolean includeStatistics) { + if (CollectionUtils.isEmpty(locations)) { + return createEmptyTrajectory(vehicleId, startTime, endTime); + } + + SysVehicleLocation firstLocation = locations.get(0); + List 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 locations) { + if (CollectionUtils.isEmpty(locations)) { + return "UNKNOWN"; + } + + Map 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分钟"; + } +} \ No newline at end of file diff --git a/qaup-system/src/main/resources/mapper/system/SysVehicleLocationMapper.xml b/qaup-system/src/main/resources/mapper/system/SysVehicleLocationMapper.xml index 208f23e5..8462a7f3 100644 --- a/qaup-system/src/main/resources/mapper/system/SysVehicleLocationMapper.xml +++ b/qaup-system/src/main/resources/mapper/system/SysVehicleLocationMapper.xml @@ -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 - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/qaup-ui/package.json b/qaup-ui/package.json index 04517221..6b38aa8d 100644 --- a/qaup-ui/package.json +++ b/qaup-ui/package.json @@ -1,6 +1,6 @@ { "name": "qaup", - "version": "3.8.9", + "version": "1.0.1", "description": "后台管理系统", "author": "Tellme", "license": "MIT", diff --git a/qaup-ui/src/views/index.vue b/qaup-ui/src/views/index.vue index 607fac19..e8ce1f9f 100644 --- a/qaup-ui/src/views/index.vue +++ b/qaup-ui/src/views/index.vue @@ -17,7 +17,7 @@ export default { data() { return { // 版本号 - version: "3.8.9" + version: "1.0.1" } }, methods: { diff --git a/sql/fix_vehicle_type_enum.sql b/sql/fix_vehicle_type_enum.sql new file mode 100644 index 00000000..fa415ea6 --- /dev/null +++ b/sql/fix_vehicle_type_enum.sql @@ -0,0 +1,26 @@ +-- 修复MovingObjectType枚举不匹配问题 +-- 将AIRPORT_VEHICLE替换为SPECIAL_VEHICLE和NORMAL_VEHICLE + +-- 更新停机坪限速规则的车辆类型 +DELETE FROM "public"."spatial_rule_vehicle_types" +WHERE "rule_id" = 'SPEED_LIMIT_APRON' AND "vehicle_type" = 'AIRPORT_VEHICLE'; + +-- 如果SPECIAL_VEHICLE不存在,添加它 +INSERT INTO "public"."spatial_rule_vehicle_types" ("rule_id", "vehicle_type") +VALUES ('SPEED_LIMIT_APRON', 'SPECIAL_VEHICLE') +ON CONFLICT ("rule_id", "vehicle_type") DO NOTHING; + +-- 更新滑行道限速规则的车辆类型 +DELETE FROM "public"."spatial_rule_vehicle_types" +WHERE "rule_id" = 'SPEED_LIMIT_TAXIWAY' AND "vehicle_type" = 'AIRPORT_VEHICLE'; + +-- 如果SPECIAL_VEHICLE不存在,添加它 +INSERT INTO "public"."spatial_rule_vehicle_types" ("rule_id", "vehicle_type") +VALUES ('SPEED_LIMIT_TAXIWAY', 'SPECIAL_VEHICLE') +ON CONFLICT ("rule_id", "vehicle_type") DO NOTHING; + +-- 验证更新结果 +SELECT rule_id, vehicle_type +FROM "public"."spatial_rule_vehicle_types" +WHERE rule_id IN ('SPEED_LIMIT_APRON', 'SPEED_LIMIT_TAXIWAY') +ORDER BY rule_id, vehicle_type; \ No newline at end of file diff --git a/sql/qaup_database_complete_init.sql b/sql/qaup_database_complete_init.sql index 43df29a6..18778393 100644 --- a/sql/qaup_database_complete_init.sql +++ b/sql/qaup_database_complete_init.sql @@ -1,7 +1,7 @@ -- ================================================================ -- QAUP 机场车辆管理和碰撞避免系统统一数据库初始化脚本 -- 数据库名称:qaup --- 版本:3.8.9 +-- 版本:1.0.1 -- 创建时间:2025-07-12 -- 描述:该脚本包含完整的数据库结构,用于系统的全新部署 -- ================================================================ @@ -408,7 +408,7 @@ DO $$ BEGIN RAISE NOTICE '========================================='; RAISE NOTICE 'QAUP 数据库初始化完成!'; - RAISE NOTICE '数据库版本: 3.8.9'; + RAISE NOTICE '数据库版本: 1.0.1'; RAISE NOTICE '创建时间: %', CURRENT_TIMESTAMP; RAISE NOTICE '========================================= '; RAISE NOTICE '已创建表格数量: %', ( diff --git a/tools/mock_server.py b/tools/mock_server.py index 44866007..e8b625b2 100644 --- a/tools/mock_server.py +++ b/tools/mock_server.py @@ -51,7 +51,7 @@ DIST_50M = 50 # 时间配置(秒) WAIT_TIME_AFTER_RETURN = 0.1 # 返回起点后的等待时间(进一步优化:减少到0.1秒) -UPDATE_INTERVAL = 5.0 # 位置更新间隔, 默认 1 秒 +UPDATE_INTERVAL = 1.0 # 位置更新间隔, 默认 1 秒 TRAFFIC_LIGHT_SWITCH_INTERVAL = 10.0 # 红绿灯切换间隔 # 根据 route.md 定义的坐标点 @@ -84,6 +84,84 @@ UNMANNED_B_END = {"longitude": 120.086263, "latitude": 36.370484} # 无 AIRCRAFT_SIZE_M = 30.0 VEHICLE_SIZE_M = 10.0 +# CA3456 航空器状态模拟 - 进港->停留1分钟->出港循环 +ca3456_status = { + "flightNo": "CA3456", + "type": "IN", # IN: 进港, OUT: 出港 + "inRunway": "35", + "outRunway": "34", + "contactCross": "F1", + "seat": "138", + "timestamp": int(time.time() * 1000), + "status_start_time": time.time(), + "cycle_duration": 62, # 总循环时间: 60秒 + 2秒切换时间 + "arrival_duration": 30, # 进港阶段持续时间 + "wait_duration": 60, # 停留时间(1分钟) + "departure_duration": 30 # 出港阶段持续时间 +} + +# 航空器路由数据 - 根据API文档格式 +aircraft_routes = { + "arrival": { + "type": "IN", + "status": "COMPLETE", + "codes": "F1,L4,138", + "geometry": None, + "geoPath": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": [ + [120.086263, 36.370484], # 起点 + [120.085000, 36.370000], # 中间点1 + [120.084000, 36.369500], # 中间点2 + [120.083000, 36.369000], # 中间点3 + [120.082000, 36.368500], # 中间点4 + [120.081000, 36.368000], # 中间点5 + [120.080996, 36.369105] # 终点(机位138) + ] + }, + "properties": { + "code": "L4" + } + } + ] + } + }, + "departure": { + "type": "OUT", + "status": "COMPLETE", + "codes": "138,L4,F1", + "geometry": None, + "geoPath": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": [ + [120.080996, 36.369105], # 起点(机位138) + [120.081000, 36.368000], # 中间点1 + [120.082000, 36.368500], # 中间点2 + [120.083000, 36.369000], # 中间点3 + [120.084000, 36.369500], # 中间点4 + [120.085000, 36.370000], # 中间点5 + [120.086263, 36.370484] # 终点 + ] + }, + "properties": { + "code": "L4" + } + } + ] + } + } +} + # 飞机数据 - 根据 route.md 配置 aircraft_data = [ { @@ -286,16 +364,21 @@ for vehicle in vehicle_data: def calculate_distance_to_target(vehicle, target_lat, target_lon): """计算车辆到目标位的距离(米)""" - dlat = target_lat - vehicle["latitude"] - dlon = target_lon - vehicle["longitude"] - + # 转换为弧度 + lat1_rad = math.radians(vehicle["latitude"]) + lon1_rad = math.radians(vehicle["longitude"]) + lat2_rad = math.radians(target_lat) + lon2_rad = math.radians(target_lon) + + dlat = lat2_rad - lat1_rad + dlon = lon2_rad - lon1_rad + # 使用 Haversine 公式计算距离 - R = 6371000 # 地球半径(米) a = math.sin(dlat/2) * math.sin(dlat/2) + \ - math.cos(math.radians(vehicle["latitude"])) * math.cos(math.radians(target_lat)) * \ + math.cos(lat1_rad) * math.cos(lat2_rad) * \ math.sin(dlon/2) * math.sin(dlon/2) c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) - return R * c + return EARTH_RADIUS * c @app.route('/api/VehicleCommandInfo', methods=['POST']) def handle_vehicle_command(): @@ -625,9 +708,28 @@ last_light_switch_time = time.time() def check_auth(): auth_header = request.headers.get('Authorization') - if not auth_header or auth_header != AUTH_TOKEN: + print(f"收到的Authorization头: '{auth_header}'") + print(f"期望的AUTH_TOKEN: '{AUTH_TOKEN}'") + + if not auth_header: + print("认证失败: 缺少Authorization头") return False - return True + + # 检查是否包含Bearer前缀 + if not auth_header.startswith('Bearer '): + print("认证失败: 不是Bearer格式") + return False + + # 提取token部分 + token = auth_header[7:] # 去掉 "Bearer " 前缀 + expected_token = AUTH_TOKEN[7:] # 去掉 "Bearer " 前缀 + + print(f"提取的token: '{token}'") + print(f"期望的token: '{expected_token}'") + + result = token == expected_token + print(f"认证结果: {result}") + return result @app.route('/openApi/getCurrentFlightPositions', methods=['GET', 'OPTIONS']) def get_flight_positions(): @@ -755,24 +857,6 @@ def get_traffic_light_signals(): """获取红绿灯信号状态 (旧的轮询接口)""" if request.method == 'OPTIONS': return '', 204 - - # --- 注释掉原有逻辑 (2025-05-06 by AI Assistant) --- - # 原因: 为了测试新的 TrafficLightHttpServer 推送机制,暂时禁用此轮询接口返回数据。 - # 模拟和发送红绿灯状态的功能现在由 tools/mock_traffic_light_client.py 负责。 - # 如需恢复轮询测试,请取消下面的注释并注释掉新的 return 语句。 - # # 更新红绿灯状态 - # # switch_traffic_light_state() # 采用模拟的红绿灯脚本,注释掉此处的调用 - # - # # 更新时间戳 - # for signal in traffic_light_data: - # signal[\"timestamp\"] = int(time.time() * 1000) - # - # return jsonify({ - # \"status\": 200, - # \"msg\": \"获取红绿灯信号成功\", - # \"data\": traffic_light_data - # }) - # --- 注释结束 --- # 新增: 返回空数据,以禁用轮询数据源 return jsonify({ @@ -804,14 +888,35 @@ def login(): if request.method == 'OPTIONS': return '', 204 - # 从 URL query parameters 中获取用户名和密码 + # 支持多种方式获取用户名和密码 + username = None + password = None + + # 优先从 URL query parameters 中获取(符合API文档示例) username = request.args.get('username') password = request.args.get('password') + # 如果query参数中没有,尝试从form data中获取 + if not username or not password: + username = request.form.get('username') or username + password = request.form.get('password') or password + + # 如果form data中也没有,尝试从JSON body中获取 + if not username or not password: + try: + json_data = request.get_json(silent=True) + if json_data: + username = json_data.get('username') or username + password = json_data.get('password') or password + except: + pass + print(f"收到登录请求: username={username}, password={password}") print(f"请求 URL: {request.url}") print(f"请求方法: {request.method}") print(f"请求参数: {request.args}") + print(f"请求表单: {request.form}") + print(f"请求JSON: {request.get_json(silent=True)}") if not username or not password: return jsonify({ @@ -988,5 +1093,140 @@ def get_unmanned_vehicle_state(): print(f"Error in get_unmanned_vehicle_state: {str(e)}") return jsonify([]), 500 +def update_ca3456_status(): + """更新CA3456航空器状态 - 进港->停留1分钟->出港循环""" + global ca3456_status + current_time = time.time() + elapsed_time = current_time - ca3456_status["status_start_time"] + + # 计算当前在循环中的位置 + cycle_time = elapsed_time % ca3456_status["cycle_duration"] + + if cycle_time < ca3456_status["arrival_duration"]: + # 进港阶段 + ca3456_status["type"] = "IN" + logging.info(f"CA3456 进港阶段: {cycle_time:.1f}s") + elif cycle_time < ca3456_status["arrival_duration"] + ca3456_status["wait_duration"]: + # 停留阶段 + ca3456_status["type"] = "ARRIVED" + logging.info(f"CA3456 停留阶段: {cycle_time:.1f}s") + else: + # 出港阶段 + ca3456_status["type"] = "OUT" + logging.info(f"CA3456 出港阶段: {cycle_time:.1f}s") + + ca3456_status["timestamp"] = int(current_time * 1000) + +# 新增API端点 + +@app.route('/userInfoController/refreshToken', methods=['GET', 'OPTIONS']) +def refresh_token(): + """Token刷新接口""" + if request.method == 'OPTIONS': + return '', 204 + + if not check_auth(): + return jsonify({ + "status": 401, + "msg": "认证失败", + "data": None + }), 401 + + # 返回新的token(实际上还是同一个) + return jsonify({ + "status": 200, + "msg": "Token刷新成功", + "data": { + "token": "Bearer dianxin-token-2024", + "expiresIn": 86400 # 24小时过期 + } + }) + +@app.route('/runwayPathPlanningController/findArrTaxiwayByRunwayAndContactCrossAndSeat', methods=['GET', 'OPTIONS']) +def get_arrival_taxiway_route(): + """获取进港滑行路线""" + if request.method == 'OPTIONS': + return '', 204 + + if not check_auth(): + return jsonify({ + "status": 401, + "msg": "认证失败", + "data": None + }), 401 + + # 获取请求参数 + in_runway = request.args.get('inRunway', '35') + out_runway = request.args.get('outRunway', '34') + contact_cross = request.args.get('contactCross', 'F1') + seat = request.args.get('seat', '138') + + logging.info(f"进港路线查询: inRunway={in_runway}, outRunway={out_runway}, contactCross={contact_cross}, seat={seat}") + + # 返回进港路线数据 + return jsonify({ + "status": 200, + "msg": "进港滑行路线查询成功", + "data": aircraft_routes["arrival"] + }) + +@app.route('/runwayPathPlanningController/findDepTaxiwayByRunwayAndContactCrossAndSeat', methods=['GET', 'OPTIONS']) +def get_departure_taxiway_route(): + """获取出港滑行路线""" + if request.method == 'OPTIONS': + return '', 204 + + if not check_auth(): + return jsonify({ + "status": 401, + "msg": "认证失败", + "data": None + }), 401 + + # 获取请求参数 + in_runway = request.args.get('inRunway', '35') + out_runway = request.args.get('outRunway', '34') + start_seat = request.args.get('startSeat', '138') + + logging.info(f"出港路线查询: inRunway={in_runway}, outRunway={out_runway}, startSeat={start_seat}") + + # 返回出港路线数据 + return jsonify({ + "status": 200, + "msg": "出港滑行路线查询成功", + "data": aircraft_routes["departure"] + }) + +@app.route('/aircraftStatusController/getAircraftStatus', methods=['GET', 'OPTIONS']) +def get_aircraft_status(): + """获取航空器状态""" + if request.method == 'OPTIONS': + return '', 204 + + if not check_auth(): + return jsonify({ + "status": 401, + "msg": "认证失败", + "data": None + }), 401 + + # 更新CA3456状态 + update_ca3456_status() + + # 返回当前状态 + return jsonify({ + "status": 200, + "msg": "航空器状态查询成功", + "data": { + "type": ca3456_status["type"], + "flightNo": ca3456_status["flightNo"], + "inRunway": ca3456_status["inRunway"], + "outRunway": ca3456_status["outRunway"], + "contactCross": ca3456_status["contactCross"], + "seat": ca3456_status["seat"], + "timestamp": ca3456_status["timestamp"] + } + }) + if __name__ == '__main__': app.run(host='localhost', port=8090, debug=True) \ No newline at end of file diff --git a/tools/test_websocket.html b/tools/test_websocket.html index e81ac8ef..08f3047b 100644 --- a/tools/test_websocket.html +++ b/tools/test_websocket.html @@ -263,7 +263,12 @@ log('collisionLog', `碰撞预警: ${message.payload?.message || JSON.stringify(message.payload)}`, 'error'); break; case 'rule_violation': - log('collisionLog', `规则违规: ${message.payload?.ruleType || '未知规则'}`, 'warning'); + const vehicleLicense = message.payload?.vehicleLicense || '未知车辆'; + const ruleName = message.payload?.ruleName || '未知规则'; + const violationType = message.payload?.violationType || '未知类型'; + const alertLevel = message.payload?.alertLevel || '未知级别'; + const description = message.payload?.description || '无描述'; + log('collisionLog', `规则违规: ${vehicleLicense} - ${ruleName} (${violationType}/${alertLevel}): ${description}`, 'warning'); break; case 'rule_execution_status': log('collisionLog', `规则执行状态: ${message.payload?.status || '未知状态'}`, 'info'); @@ -277,6 +282,19 @@ case 'traffic_light_status': log('collisionLog', `红绿灯状态: ${message.payload?.intersection_id || '未知路口'}`, 'info'); break; + case 'aircraftRouteUpdate': + const flightNo = message.payload?.flightNo || '未知航班'; + const routeType = message.payload?.routeType || '未知路由'; + const routeStatus = message.payload?.status || '未知状态'; + log('collisionLog', `航空器路由更新: ${flightNo} - ${routeType} (${routeStatus})`, 'info'); + break; + case 'path_conflict_alert': + const object1 = message.payload?.object1Name || '未知对象1'; + const object2 = message.payload?.object2Name || '未知对象2'; + const conflictType = message.payload?.alertType || '未知类型'; + const conflictLevel = message.payload?.alertLevel || '未知级别'; + log('collisionLog', `路径冲突告警: ${object1} vs ${object2} (${conflictType}/${conflictLevel})`, 'error'); + break; default: log('collisionLog', `未知消息类型: ${message.type}`, 'info'); }