2. 创建机场区域PostGIS实体类(AirportArea) 3. 创建数据库迁移脚本(表结构、索引、分区) 4. 实现车辆位置Repository接口 5. 实现机场区域Repository接口 6. 创建PostGIS车辆服务(PostGISVehicleService) 7. 创建PostGIS区域服务(PostGISAreaService) 8. 创建统一空间查询服务(SpatialQueryService) 9. 实现区域配置导入工具(YAML到数据库) 10. 重构DataCollectorService(移除内存存储) 11. 重构AirportAreaService(基于数据库查询) 12. 移除MovingObjectRepository和相关内存存储代码 13. 移除AirportAreasProperties和YAML配置加载 14. 实现Redis缓存策略 15. 数据库连接池和性能优化配置 16. 创建单元测试和集成测试
288 lines
11 KiB
Java
288 lines
11 KiB
Java
package com.dongni.collisionavoidance.common.service;
|
|
|
|
import com.dongni.collisionavoidance.common.model.spatial.VehicleLocation;
|
|
import com.dongni.collisionavoidance.common.model.repository.VehicleLocationRepository;
|
|
import com.dongni.collisionavoidance.common.model.MovingObjectType;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.locationtech.jts.geom.Coordinate;
|
|
import org.locationtech.jts.geom.GeometryFactory;
|
|
import org.locationtech.jts.geom.Point;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import java.time.LocalDateTime;
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
|
|
/**
|
|
* 车辆位置服务类 - 基于PostGIS的空间数据管理
|
|
* 提供车辆位置的保存、查询、空间分析等功能
|
|
*/
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
@Slf4j
|
|
public class VehicleLocationService {
|
|
|
|
private final VehicleLocationRepository vehicleLocationRepository;
|
|
private final GeometryFactory geometryFactory = new GeometryFactory();
|
|
|
|
/**
|
|
* 保存车辆位置信息
|
|
*/
|
|
@Transactional
|
|
public VehicleLocation saveVehicleLocation(VehicleLocation vehicleLocation) {
|
|
try {
|
|
VehicleLocation saved = vehicleLocationRepository.save(vehicleLocation);
|
|
log.debug("保存车辆位置: vehicleId={}, location=({}, {})",
|
|
saved.getVehicleId(),
|
|
saved.getLocation().getX(),
|
|
saved.getLocation().getY());
|
|
return saved;
|
|
} catch (Exception e) {
|
|
log.error("保存车辆位置失败: vehicleId={}", vehicleLocation.getVehicleId(), e);
|
|
throw new RuntimeException("保存车辆位置失败", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 批量保存车辆位置信息
|
|
*/
|
|
@Transactional
|
|
public List<VehicleLocation> saveVehicleLocations(List<VehicleLocation> vehicleLocations) {
|
|
try {
|
|
List<VehicleLocation> saved = vehicleLocationRepository.saveAll(vehicleLocations);
|
|
log.info("批量保存车辆位置: 数量={}", saved.size());
|
|
return saved;
|
|
} catch (Exception e) {
|
|
log.error("批量保存车辆位置失败: 数量={}", vehicleLocations.size(), e);
|
|
throw new RuntimeException("批量保存车辆位置失败", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 创建车辆位置记录
|
|
*/
|
|
public VehicleLocation createVehicleLocation(String vehicleId, MovingObjectType vehicleType,
|
|
double longitude, double latitude,
|
|
Double altitude, Double heading, Double speed) {
|
|
Point location = geometryFactory.createPoint(new Coordinate(longitude, latitude));
|
|
location.setSRID(4326); // 设置WGS84坐标系
|
|
|
|
VehicleLocation vehicleLocation = new VehicleLocation();
|
|
vehicleLocation.setVehicleId(vehicleId);
|
|
vehicleLocation.setVehicleType(vehicleType);
|
|
vehicleLocation.setLocation(location);
|
|
vehicleLocation.setAltitude(altitude);
|
|
vehicleLocation.setHeading(heading);
|
|
vehicleLocation.setSpeed(speed);
|
|
vehicleLocation.setTimestamp(LocalDateTime.now());
|
|
|
|
return vehicleLocation;
|
|
}
|
|
|
|
/**
|
|
* 根据车辆ID获取最新位置
|
|
*/
|
|
public Optional<VehicleLocation> getLatestVehicleLocation(String vehicleId) {
|
|
try {
|
|
return vehicleLocationRepository.findLatestByVehicleId(vehicleId);
|
|
} catch (Exception e) {
|
|
log.error("获取车辆最新位置失败: vehicleId={}", vehicleId, e);
|
|
return Optional.empty();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 根据车辆类型获取活跃车辆
|
|
*/
|
|
public List<VehicleLocation> getActiveVehiclesByType(String vehicleType, int minutesBack) {
|
|
LocalDateTime since = LocalDateTime.now().minusMinutes(minutesBack);
|
|
try {
|
|
return vehicleLocationRepository.findActiveByVehicleType(vehicleType, since);
|
|
} catch (Exception e) {
|
|
log.error("获取活跃车辆失败: vehicleType={}, minutesBack={}", vehicleType, minutesBack, e);
|
|
return List.of();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 空间查询:获取指定半径范围内的车辆
|
|
*/
|
|
public List<VehicleLocation> getVehiclesWithinRadius(double longitude, double latitude,
|
|
double radiusMeters, int minutesBack) {
|
|
Point centerPoint = geometryFactory.createPoint(new Coordinate(longitude, latitude));
|
|
centerPoint.setSRID(4326);
|
|
LocalDateTime since = LocalDateTime.now().minusMinutes(minutesBack);
|
|
|
|
try {
|
|
return vehicleLocationRepository.findVehiclesWithinRadius(centerPoint, radiusMeters, since);
|
|
} catch (Exception e) {
|
|
log.error("半径查询失败: center=({}, {}), radius={}m", longitude, latitude, radiusMeters, e);
|
|
return List.of();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 空间查询:获取指定区域内的车辆
|
|
*/
|
|
public List<VehicleLocation> getVehiclesInArea(String areaWkt, int minutesBack) {
|
|
LocalDateTime since = LocalDateTime.now().minusMinutes(minutesBack);
|
|
try {
|
|
return vehicleLocationRepository.findVehiclesInArea(areaWkt, since);
|
|
} catch (Exception e) {
|
|
log.error("区域查询失败: areaWkt={}", areaWkt, e);
|
|
return List.of();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取车辆轨迹数据
|
|
*/
|
|
public List<VehicleLocation> getVehicleTrajectory(String vehicleId, LocalDateTime startTime, LocalDateTime endTime) {
|
|
try {
|
|
return vehicleLocationRepository.findVehicleTrajectory(vehicleId, startTime, endTime);
|
|
} catch (Exception e) {
|
|
log.error("获取车辆轨迹失败: vehicleId={}, 时间范围=[{}, {}]", vehicleId, startTime, endTime, e);
|
|
return List.of();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取指定高度范围内的车辆
|
|
*/
|
|
public List<VehicleLocation> getVehiclesByAltitudeRange(double minAltitude, double maxAltitude, int minutesBack) {
|
|
LocalDateTime since = LocalDateTime.now().minusMinutes(minutesBack);
|
|
try {
|
|
return vehicleLocationRepository.findVehiclesByAltitudeRange(minAltitude, maxAltitude, since);
|
|
} catch (Exception e) {
|
|
log.error("高度范围查询失败: 高度范围=[{}, {}]", minAltitude, maxAltitude, e);
|
|
return List.of();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取高速移动的车辆
|
|
*/
|
|
public List<VehicleLocation> getHighSpeedVehicles(double speedThreshold, int minutesBack) {
|
|
LocalDateTime since = LocalDateTime.now().minusMinutes(minutesBack);
|
|
try {
|
|
return vehicleLocationRepository.findHighSpeedVehicles(speedThreshold, since);
|
|
} catch (Exception e) {
|
|
log.error("高速车辆查询失败: speedThreshold={}", speedThreshold, e);
|
|
return List.of();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取最近的N个车辆
|
|
*/
|
|
public List<VehicleLocation> getNearestVehicles(double longitude, double latitude, int limit, int minutesBack) {
|
|
Point referencePoint = geometryFactory.createPoint(new Coordinate(longitude, latitude));
|
|
referencePoint.setSRID(4326);
|
|
LocalDateTime since = LocalDateTime.now().minusMinutes(minutesBack);
|
|
|
|
try {
|
|
return vehicleLocationRepository.findNearestVehicles(referencePoint, since, limit);
|
|
} catch (Exception e) {
|
|
log.error("最近车辆查询失败: reference=({}, {}), limit={}", longitude, latitude, limit, e);
|
|
return List.of();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 统计指定时间段内的唯一车辆数量
|
|
*/
|
|
public long countUniqueVehiclesInTimeRange(LocalDateTime startTime, LocalDateTime endTime) {
|
|
try {
|
|
return vehicleLocationRepository.countUniqueVehiclesInTimeRange(startTime, endTime);
|
|
} catch (Exception e) {
|
|
log.error("车辆统计失败: 时间范围=[{}, {}]", startTime, endTime, e);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 清理历史数据
|
|
*/
|
|
@Transactional
|
|
public int cleanupHistoricalData(LocalDateTime beforeTime) {
|
|
try {
|
|
int deletedCount = vehicleLocationRepository.deleteHistoricalData(beforeTime);
|
|
log.info("清理历史数据完成: 删除记录数={}, 清理时间点={}", deletedCount, beforeTime);
|
|
return deletedCount;
|
|
} catch (Exception e) {
|
|
log.error("清理历史数据失败: beforeTime={}", beforeTime, e);
|
|
throw new RuntimeException("清理历史数据失败", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 更新车辆位置 (如果存在则更新,否则创建新记录)
|
|
*/
|
|
@Transactional
|
|
public VehicleLocation updateOrCreateVehicleLocation(String vehicleId, MovingObjectType vehicleType,
|
|
double longitude, double latitude,
|
|
Double altitude, Double heading, Double speed) {
|
|
VehicleLocation vehicleLocation = createVehicleLocation(vehicleId, vehicleType,
|
|
longitude, latitude,
|
|
altitude, heading, speed);
|
|
vehicleLocation.setTimestamp(LocalDateTime.now());
|
|
|
|
return saveVehicleLocation(vehicleLocation);
|
|
}
|
|
|
|
/**
|
|
* 验证车辆位置数据的有效性
|
|
*/
|
|
public boolean isValidVehicleLocation(double longitude, double latitude, Double altitude, Double heading, Double speed) {
|
|
// 经度范围检查
|
|
if (longitude < -180 || longitude > 180) {
|
|
log.warn("无效的经度值: {}", longitude);
|
|
return false;
|
|
}
|
|
|
|
// 纬度范围检查
|
|
if (latitude < -90 || latitude > 90) {
|
|
log.warn("无效的纬度值: {}", latitude);
|
|
return false;
|
|
}
|
|
|
|
// 高度检查
|
|
if (altitude != null && (altitude < -1000 || altitude > 50000)) {
|
|
log.warn("无效的高度值: {}", altitude);
|
|
return false;
|
|
}
|
|
|
|
// 航向检查
|
|
if (heading != null && (heading < 0 || heading >= 360)) {
|
|
log.warn("无效的航向值: {}", heading);
|
|
return false;
|
|
}
|
|
|
|
// 速度检查
|
|
if (speed != null && (speed < 0 || speed > 1000)) {
|
|
log.warn("无效的速度值: {}", speed);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 根据车辆ID删除所有历史位置记录
|
|
*/
|
|
@Transactional
|
|
public void deleteVehicleLocations(String vehicleId) {
|
|
try {
|
|
vehicleLocationRepository.deleteAll(
|
|
vehicleLocationRepository.findVehicleTrajectory(vehicleId,
|
|
LocalDateTime.now().minusYears(10), LocalDateTime.now())
|
|
);
|
|
log.info("删除车辆位置记录: vehicleId={}", vehicleId);
|
|
} catch (Exception e) {
|
|
log.error("删除车辆位置记录失败: vehicleId={}", vehicleId, e);
|
|
throw new RuntimeException("删除车辆位置记录失败", e);
|
|
}
|
|
}
|
|
} |