| | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.zy.acs.common.utils.Utils; |
| | | import com.zy.acs.framework.common.Cools; |
| | | import com.zy.acs.framework.exception.CoolException; |
| | | import com.zy.acs.manager.common.config.RedisProperties; |
| | | import com.zy.acs.manager.common.utils.MapDataUtils; |
| | | import com.zy.acs.manager.core.constant.MapDataConstant; |
| | | import com.zy.acs.manager.core.domain.VehicleFootprint; |
| | | import com.zy.acs.manager.core.service.astart.CodeNodeType; |
| | | import com.zy.acs.manager.core.service.astart.DynamicNodeType; |
| | | import com.zy.acs.manager.core.service.astart.MapDataDispatcher; |
| | | import com.zy.acs.manager.core.service.astart.NavigateNode; |
| | |
| | | import com.zy.acs.manager.manager.entity.AgvDetail; |
| | | import com.zy.acs.manager.manager.entity.AgvModel; |
| | | import com.zy.acs.manager.manager.entity.Code; |
| | | import com.zy.acs.manager.manager.enums.AgvModelType; |
| | | import com.zy.acs.manager.manager.service.AgvDetailService; |
| | | import com.zy.acs.manager.manager.service.AgvModelService; |
| | | import com.zy.acs.manager.manager.service.AgvService; |
| | |
| | | import java.io.InputStreamReader; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.StandardCopyOption; |
| | | import java.util.List; |
| | | import java.util.*; |
| | | import java.util.concurrent.ExecutorService; |
| | | import java.util.concurrent.Executors; |
| | | import java.util.concurrent.TimeUnit; |
| | |
| | | } |
| | | |
| | | |
| | | public boolean calcWaveScope() { |
| | | public boolean calcWaveScope(AgvModel agvModel) { |
| | | Integer lev = MapDataDispatcher.MAP_DEFAULT_LEV; |
| | | boolean lockAcquired = false; |
| | | |
| | |
| | | } |
| | | |
| | | // return this.calcWaveScopeByPython(lev); |
| | | return this.calcWaveScopeByJava(lev); |
| | | return this.calcWaveScopeByJava(lev, agvModel); |
| | | |
| | | } catch (Exception e) { |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | private boolean calcWaveScopeByPython(Integer lev) throws Exception { |
| | | private boolean calcWaveScopeByPython(Integer lev, AgvModel agvModel) throws Exception { |
| | | if (null == agvModel.getDiameter() || agvModel.getDiameter() <= 0) { |
| | | log.warn("There is no diameter or diameter value was wrong..."); |
| | | } |
| | | |
| | | // python |
| | | AgvModel agvModel = agvModelService.selectByType(AgvModelType.CTU_BOX_TRANSPORT_AGV.toString()); // can be optimized |
| | | Double avoidDistance = MapDataUtils.getVehicleWaveSafeDistance(agvModel.getDiameter(), MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR); |
| | | |
| | | if (null == pythonFile) { |
| | |
| | | return true; |
| | | } |
| | | |
| | | private boolean calcWaveScopeByJava(Integer lev) throws Exception { |
| | | |
| | | AgvModel agvModel = agvModelService.selectByType(AgvModelType.CTU_BOX_TRANSPORT_AGV.toString()); // can be optimized |
| | | Double avoidDistance = MapDataUtils.getVehicleWaveSafeDistance(agvModel.getDiameter(), MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR); |
| | | |
| | | // java |
| | | private boolean calcWaveScopeByJava(Integer lev, AgvModel agvModel) throws Exception { |
| | | String[][] codeMatrix = mapDataDispatcher.getCodeMatrix(lev); |
| | | Double[][][] cdaMatrix = mapDataDispatcher.getCdaMatrix(lev); |
| | | String[][] waveMatrix = mapDataDispatcher.initWaveMatrix(lev); |
| | | |
| | | // lock path |
| | | DynamicNode[][] dynamicMatrix = mapDataDispatcher.getDynamicMatrix(lev); |
| | | |
| | | // 记录每台车的安全半径,顺带标记已按矩形方式绘制的地码 |
| | | Map<String, Double> vehicleAvoidDistance = new HashMap<>(); |
| | | Set<String> preciseNodes = new HashSet<>(); |
| | | |
| | | // 先根据实时位置+角度绘制实际矩形占位 |
| | | List<Agv> agvList = agvService.list(new LambdaQueryWrapper<>()); |
| | | for (Agv agv : agvList) { |
| | | // 每次按调度列表刷新车辆当前地码与角度 |
| | | AgvDetail agvDetail = agvDetailService.selectByAgvId(agv.getId()); |
| | | if (null == agvDetail) { continue; } |
| | | Long recentCodeId = agvDetail.getRecentCode(); |
| | | if (null == recentCodeId) { continue; } |
| | | Code centerCode = codeService.getCacheById(recentCodeId); |
| | | if (null == centerCode) { continue; } |
| | | |
| | | AgvModel model = agvModelService.getByAgvId(agv.getId()); |
| | | if (null == model) { |
| | | model = agvModel; |
| | | } |
| | | double avoidDistance = resolveAvoidDistance(model, agvModel); |
| | | vehicleAvoidDistance.put(agv.getUuid(), avoidDistance); |
| | | |
| | | // 将旋转矩形映射到网格,得到需要占用的节点集合 |
| | | List<NavigateNode> footprintNodes = calcFootprintNodes(lev, centerCode, agvDetail, model, codeMatrix, cdaMatrix); |
| | | if (Cools.isEmpty(footprintNodes)) { |
| | | continue; |
| | | } |
| | | preciseNodes.add(centerCode.getData()); |
| | | for (NavigateNode navigateNode : footprintNodes) { |
| | | String waveNode = waveMatrix[navigateNode.getX()][navigateNode.getY()]; |
| | | waveMatrix[navigateNode.getX()][navigateNode.getY()] = MapDataUtils.generateWaveNode(waveNode, agv.getUuid()); |
| | | } |
| | | } |
| | | |
| | | // 再用原来的圆形波纹补上剩余的路径锁定节点 |
| | | for (int i = 0; i < dynamicMatrix.length; i++) { |
| | | for (int j = 0; j < dynamicMatrix[i].length; j++) { |
| | | DynamicNode dynamicNode = dynamicMatrix[i][j]; |
| | | String vehicle = dynamicNode.getVehicle(); |
| | | if (!DynamicNodeType.ACCESS.val.equals(vehicle) && !DynamicNodeType.BLOCK.val.equals(vehicle)) { |
| | | |
| | | List<NavigateNode> includeList = mapService.getWaveScopeByCode(lev, codeMatrix[i][j], avoidDistance); |
| | | |
| | | String codeData = codeMatrix[i][j]; |
| | | if (preciseNodes.contains(codeData)) { |
| | | // 已经按真实矩形写过一次,避免重复占用 |
| | | continue; |
| | | } |
| | | double avoidDistance = vehicleAvoidDistance.getOrDefault(vehicle, resolveAvoidDistance(null, agvModel)); |
| | | if (avoidDistance <= 0) { |
| | | continue; |
| | | } |
| | | List<NavigateNode> includeList = mapService.getWaveScopeByCode(lev, codeData, avoidDistance); |
| | | for (NavigateNode navigateNode : includeList) { |
| | | String waveNode = waveMatrix[navigateNode.getX()][navigateNode.getY()]; // overlay |
| | | String waveNode = waveMatrix[navigateNode.getX()][navigateNode.getY()]; |
| | | waveMatrix[navigateNode.getX()][navigateNode.getY()] = MapDataUtils.generateWaveNode(waveNode, vehicle); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // mapDataDispatcher.printMatrix(waveMatrix); |
| | | // 所有车辆同步完成后再统一写回 redis,减少频繁刷新 |
| | | mapDataDispatcher.setWaveMatrix(lev, waveMatrix); |
| | | |
| | | return true; |
| | | } |
| | | |
| | | // 不同车型可能直径不同,缺失时退回调用方传入的默认模型,最后再退回 0 |
| | | private double resolveAvoidDistance(AgvModel agvModel, AgvModel fallback) { |
| | | Integer diameter = null; |
| | | if (null != agvModel && null != agvModel.getDiameter() && agvModel.getDiameter() > 0) { |
| | | diameter = agvModel.getDiameter(); |
| | | } else if (null != fallback) { |
| | | diameter = fallback.getDiameter(); |
| | | } |
| | | return MapDataUtils.getVehicleWaveSafeDistance(diameter, MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR); |
| | | } |
| | | |
| | | /** |
| | | * 计算给定车辆在当前角度下真实覆盖的网格节点 |
| | | */ |
| | | private List<NavigateNode> calcFootprintNodes(Integer lev, Code centerCode, AgvDetail agvDetail, AgvModel agvModel |
| | | , String[][] codeMatrix, Double[][][] cdaMatrix) { |
| | | if (null == centerCode || null == agvDetail || null == agvModel) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | int[] codeMatrixIdx = mapDataDispatcher.getCodeMatrixIdx(lev, centerCode.getData()); |
| | | if (null == codeMatrixIdx) { |
| | | return new ArrayList<>(); |
| | | } |
| | | Double centerX = cdaMatrix[codeMatrixIdx[0]][codeMatrixIdx[1]][0]; |
| | | Double centerY = cdaMatrix[codeMatrixIdx[0]][codeMatrixIdx[1]][1]; |
| | | if (centerX == null || centerY == null) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | VehicleFootprint footprint = this.buildFootprint(agvModel); |
| | | if (null == footprint) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | double direction = Optional.ofNullable(agvDetail.getAgvAngle()).orElse(0D); |
| | | // 现场角度是以正北为 0, 统一转换成弧度方便三角函数 |
| | | double headingRad = Math.toRadians(90 - direction); |
| | | |
| | | // 旋转矩形先求出世界坐标包围盒,减少遍历 |
| | | double[][] corners = this.buildRectangleCorners(centerX, centerY, footprint, headingRad); |
| | | double minX = Arrays.stream(corners).mapToDouble(point -> point[0]).min().orElse(centerX); |
| | | double maxX = Arrays.stream(corners).mapToDouble(point -> point[0]).max().orElse(centerX); |
| | | double minY = Arrays.stream(corners).mapToDouble(point -> point[1]).min().orElse(centerY); |
| | | double maxY = Arrays.stream(corners).mapToDouble(point -> point[1]).max().orElse(centerY); |
| | | |
| | | List<NavigateNode> nodeList = new ArrayList<>(); |
| | | for (int i = 0; i < codeMatrix.length; i++) { |
| | | for (int j = 0; j < codeMatrix[i].length; j++) { |
| | | String codeData = codeMatrix[i][j]; |
| | | if (CodeNodeType.NONE.val.equals(codeData)) { |
| | | continue; |
| | | } |
| | | Double px = cdaMatrix[i][j][0]; |
| | | Double py = cdaMatrix[i][j][1]; |
| | | if (px == null || py == null) { |
| | | continue; |
| | | } |
| | | // 先利用 AABB 做一次粗滤,再做精确的点内检测 |
| | | if (px < minX || px > maxX || py < minY || py > maxY) { |
| | | continue; |
| | | } |
| | | // 是否命中 形状矩阵范围 |
| | | if (this.isInsideFootprint(px, py, centerX, centerY, footprint, headingRad)) { |
| | | nodeList.add(new NavigateNode(i, j, codeData)); |
| | | } |
| | | } |
| | | } |
| | | return nodeList; |
| | | } |
| | | |
| | | /** |
| | | * 根据车型参数(含 head/tail/width)构建投影用的矩形,必要时降级使用 length |
| | | */ |
| | | private VehicleFootprint buildFootprint(AgvModel agvModel) { |
| | | if (null == agvModel) { |
| | | return null; |
| | | } |
| | | // double baseLength = Optional.ofNullable(agvModel.getLength()).filter(len -> len > 0).map(Double::valueOf).orElse(0D); |
| | | // double defaultHalfLength = baseLength > 0 ? baseLength / 2.0 : 0; |
| | | |
| | | if (null == agvModel.getHeadOffset() || agvModel.getHeadOffset() <= 0) { |
| | | throw new CoolException(agvModel.getName() + "未设置 车头长度。"); |
| | | } |
| | | if (null == agvModel.getTailOffset() || agvModel.getTailOffset() <= 0) { |
| | | throw new CoolException(agvModel.getName() + "未设置 车尾长度。"); |
| | | } |
| | | if (null == agvModel.getWidth() || agvModel.getWidth() <= 0) { |
| | | throw new CoolException(agvModel.getName() + "未设置 车身宽度。"); |
| | | } |
| | | double head = MapDataUtils.getVehicleWaveSafeDistance(agvModel.getHeadOffset(), MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR); |
| | | double tail = MapDataUtils.getVehicleWaveSafeDistance(agvModel.getTailOffset(), MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR); |
| | | double halfWidth = MapDataUtils.getVehicleWaveSafeDistance(agvModel.getWidth() / 2, MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR); |
| | | |
| | | // padding 直接沿四个方向等比例外扩,等价于给车身预留安全距离 |
| | | // double padding = MapDataUtils.getVehicleWaveSafeDistance(agvModel.getDiameter(), MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR) / 2.0; |
| | | // if (padding > 0) { |
| | | // head += padding; |
| | | // tail += padding; |
| | | // halfWidth += padding; |
| | | // } |
| | | |
| | | // head = head <= 0 ? Math.max(tail, halfWidth) : head; |
| | | // tail = tail <= 0 ? head : tail; |
| | | // halfWidth = halfWidth <= 0 ? Math.max(head, tail) / 2.0 : halfWidth; |
| | | // |
| | | // if (head <= 0 || tail <= 0 || halfWidth <= 0) { |
| | | // return null; |
| | | // } |
| | | return new VehicleFootprint(head, tail, halfWidth); |
| | | } |
| | | |
| | | /** |
| | | * 旋转矩形的四个角坐标,用来快速生成包围盒 |
| | | */ |
| | | private double[][] buildRectangleCorners(double centerX, double centerY, VehicleFootprint footprint, double headingRad) { |
| | | double cos = Math.cos(headingRad); |
| | | double sin = Math.sin(headingRad); |
| | | |
| | | double[][] localPoints = new double[][]{ |
| | | {footprint.head, footprint.halfWidth}, |
| | | {footprint.head, -footprint.halfWidth}, |
| | | {-footprint.tail, footprint.halfWidth}, |
| | | {-footprint.tail, -footprint.halfWidth} |
| | | }; |
| | | |
| | | double[][] corners = new double[4][2]; |
| | | for (int idx = 0; idx < localPoints.length; idx++) { |
| | | double lx = localPoints[idx][0]; |
| | | double ly = localPoints[idx][1]; |
| | | double gx = centerX + lx * cos - ly * sin; |
| | | double gy = centerY + lx * sin + ly * cos; |
| | | corners[idx][0] = gx; |
| | | corners[idx][1] = gy; |
| | | } |
| | | return corners; |
| | | } |
| | | |
| | | private boolean isInsideFootprint(double px, double py, double centerX, double centerY, VehicleFootprint footprint, double headingRad) { |
| | | double dx = px - centerX; |
| | | double dy = py - centerY; |
| | | |
| | | // 把点绕回车辆本体的局部坐标,再做简单矩形判断 |
| | | double localX = dx * Math.cos(headingRad) + dy * Math.sin(headingRad); |
| | | double localY = -dx * Math.sin(headingRad) + dy * Math.cos(headingRad); |
| | | |
| | | return localX >= -footprint.tail && localX <= footprint.head |
| | | && localY >= -footprint.halfWidth && localY <= footprint.halfWidth; |
| | | } |
| | | |
| | | private File loadPythonFile() { |
| | | File scriptFile = null; |
| | | try { |