package com.yj.earth.business.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.graphhopper.GHRequest; import com.graphhopper.GHResponse; import com.graphhopper.GraphHopper; import com.graphhopper.ResponsePath; import com.graphhopper.config.Profile; import com.graphhopper.util.shapes.GHPoint; import com.yj.earth.annotation.CheckAuth; import com.yj.earth.business.domain.FileInfo; import com.yj.earth.business.domain.WebSource; import com.yj.earth.business.service.FileInfoService; import com.yj.earth.business.service.WebSourceService; import com.yj.earth.common.config.GraphHopperProperties; import com.yj.earth.common.util.ApiResponse; import com.yj.earth.model.Point; import com.yj.earth.model.RouteRequest; import com.yj.earth.model.RouteResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @Data @Slf4j @Tag(name = "路径规划管理") @RestController @RequestMapping("/graphhopper") public class GraphHopperController { @Resource private WebSourceService webSourceService; @Resource private FileInfoService fileInfoService; @Resource private GraphHopperProperties graphHopperProperties; // 存储当前可用的 GraphHopper 实例 private volatile GraphHopper currentHopper; // 状态控制: 线程安全 private final AtomicBoolean isLoading = new AtomicBoolean(false); private final AtomicBoolean isLoaded = new AtomicBoolean(false); @Operation(summary = "加载地图数据") @PostMapping("/loadMap") public ApiResponse loadMap(@Parameter(description = "文件路径") @RequestParam String path) { File osmFile = new File(path); if (!osmFile.exists()) { return ApiResponse.failure("地图文件不存在: " + path); } if (!osmFile.isFile() || !osmFile.getName().endsWith(".pbf")) { return ApiResponse.failure("仅支持有效的.pbf格式OSM文件"); } // 防止并发加载 if (isLoading.get()) { return ApiResponse.failure("地图正在加载中、请稍后查询状态"); } // 标记加载状态 isLoading.set(true); isLoaded.set(false); // 异步执行: 删除旧数据 → 创建新实例 → 加载新地图 new Thread(() -> { GraphHopper newHopper = null; try { // 关键步骤1: 彻底删除旧地图数据目录 deleteOldGraphDir(); // 关键步骤2: 创建全新的GraphHopper实例 newHopper = createNewGraphHopperInstance(path); // 关键步骤3: 加载新地图 newHopper.importOrLoad(); // 关键步骤4: 加载成功 → 替换当前实例 + 更新状态 currentHopper = newHopper; isLoaded.set(true); log.info("地图加载成功"); } catch (Exception e) { // 加载失败 → 清理新实例资源 if (newHopper != null) { newHopper.close(); } isLoaded.set(false); e.printStackTrace(); log.error("地图加载失败: " + e.getMessage()); } finally { // 无论成功/失败、释放加载锁 isLoading.set(false); } }).start(); return ApiResponse.success(null); } @Operation(summary = "清除地图服务") @PostMapping("/clearMap") public ApiResponse clearMap() { // 防止并发操作(与加载操作互斥) if (isLoading.get()) { return ApiResponse.failure("地图正在加载中、无法清除、请稍后再试"); } // 标记正在处理清除操作 isLoading.set(true); try { // 1. 关闭当前GraphHopper实例 if (currentHopper != null) { log.info("开始关闭当前地图服务实例"); currentHopper.close(); currentHopper = null; log.info("地图服务实例已关闭"); } // 2. 删除地图数据目录 log.info("开始删除地图数据目录"); deleteOldGraphDir(); log.info("地图数据目录已删除"); // 3. 重置状态变量 isLoaded.set(false); return ApiResponse.success("地图服务已成功清除"); } catch (Exception e) { log.error("清除地图服务失败", e); return ApiResponse.failure("清除地图服务失败: " + e.getMessage()); } finally { // 释放操作锁 isLoading.set(false); } } @Operation(summary = "路径规划") @PostMapping("/route") public ApiResponse calculateRoute(@RequestBody RouteRequest request) { // 区分未加载地图和加载中两种状态 if (isLoading.get()) { return ApiResponse.failure("地图正在加载中,请稍后再试"); } if (!isLoaded.get() || currentHopper == null) { return ApiResponse.failure("地图未加载,请先加载地图"); } try { // 构建路径点列表 List ghPoints = new ArrayList<>(); ghPoints.add(new GHPoint(request.getStartLat(), request.getStartLng())); // 起点 // 添加途经点 if (request.getWaypoints() != null && !request.getWaypoints().isEmpty()) { for (Point waypoint : request.getWaypoints()) { ghPoints.add(new GHPoint(waypoint.getLat(), waypoint.getLng())); } } ghPoints.add(new GHPoint(request.getEndLat(), request.getEndLng())); // 终点 // 构建请求 String targetProfile = request.getProfile() != null ? request.getProfile() : "car"; GHRequest ghRequest = new GHRequest(ghPoints) .setProfile(targetProfile); // 用新实例计算路径 GHResponse response = currentHopper.route(ghRequest); // 处理错误 if (response.hasErrors()) { // 检查是否有超出范围的错误 boolean hasOutOfBoundsError = response.getErrors().stream() .anyMatch(e -> e instanceof com.graphhopper.util.exceptions.PointOutOfBoundsException); boolean hasPointNotFoundError = response.getErrors().stream() .anyMatch(e -> e instanceof com.graphhopper.util.exceptions.PointNotFoundException); if (hasOutOfBoundsError) { return ApiResponse.failure("路径超出地图范围"); } else if (hasPointNotFoundError) { return ApiResponse.failure("未超地图范围但找不到路径点"); } else { return ApiResponse.failure("路径计算异常: " + response.getErrors().get(0).getMessage()); } } // 解析结果 ResponsePath bestPath = response.getBest(); List pathPoints = new ArrayList<>(); bestPath.getPoints().forEach(ghPoint -> pathPoints.add(new Point(ghPoint.getLat(), ghPoint.getLon())) ); // 封装返回 RouteResponse routeResponse = new RouteResponse(bestPath.getDistance() / 1000, (double) (bestPath.getTime() / 60000), pathPoints); return ApiResponse.success(routeResponse); } catch (com.graphhopper.util.exceptions.PointOutOfBoundsException e) { // 捕获单点超出范围的异常 return ApiResponse.failure("路径超出地图范围"); } catch (Exception e) { return ApiResponse.failure("路径计算异常: " + e.getMessage()); } } @Operation(summary = "获取地图加载状态") @GetMapping("/status") public ApiResponse getMapStatus() { if (isLoading.get()) { return ApiResponse.success("地图正在加载中"); } else if (isLoaded.get() && currentHopper != null) { return ApiResponse.success("地图已加载完成"); } else { return ApiResponse.success("地图未加载"); } } @Operation(summary = "获取交通方式") @PostMapping("/profiles") public ApiResponse profiles() { return ApiResponse.success(graphHopperProperties.getProfiles()); } /** * 创建全新的 GraphHopper 实例 */ private GraphHopper createNewGraphHopperInstance(String osmFilePath) { GraphHopper hopper = new GraphHopper(); // 配置基础参数 hopper.setOSMFile(osmFilePath); hopper.setGraphHopperLocation(graphHopperProperties.getGraphLocation()); // 配置交通方式 + 权重策略 List profileList = new ArrayList<>(); for (String profileName : graphHopperProperties.getProfiles()) { Profile profile = new Profile(profileName); profile.setVehicle(profileName); profile.setWeighting("fastest"); profileList.add(profile); } hopper.setProfiles(profileList); return hopper; } /** * 递归删除旧地图数据目录 */ private void deleteOldGraphDir() { File graphDir = new File(graphHopperProperties.getGraphLocation()); if (!graphDir.exists()) { log.info("旧地图目录不存在、无需删除: " + graphHopperProperties.getGraphLocation()); return; } // 递归删除所有文件和子目录 File[] files = graphDir.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { deleteOldGraphDir(file); } else { boolean deleted = file.delete(); log.info("删除旧地图文件: " + file.getAbsolutePath() + " → " + (deleted ? "成功" : "失败")); } } } // 删除空目录 boolean dirDeleted = graphDir.delete(); System.out.println("删除旧地图目录: " + graphDir.getAbsolutePath() + " → " + (dirDeleted ? "成功" : "失败")); } // 重载:递归删除子目录 private void deleteOldGraphDir(File subDir) { File[] files = subDir.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { deleteOldGraphDir(file); } else { file.delete(); } } } subDir.delete(); } }