| | |
| | | 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 com.zy.acs.manager.manager.service.CodeService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang.time.StopWatch; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.context.annotation.DependsOn; |
| | | import org.springframework.core.io.ClassPathResource; |
| | | import org.springframework.core.io.Resource; |
| | | import org.springframework.stereotype.Component; |
| | |
| | | import javax.annotation.PreDestroy; |
| | | import java.io.BufferedReader; |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.InputStreamReader; |
| | | import java.util.List; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.StandardCopyOption; |
| | | import java.util.*; |
| | | import java.util.concurrent.ExecutorService; |
| | | import java.util.concurrent.Executors; |
| | | import java.util.concurrent.TimeUnit; |
| | |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | | @DependsOn("mapDataDispatcher") |
| | | public class AvoidWaveCalculator { |
| | | |
| | | private static final ReentrantLock lock = new ReentrantLock(Boolean.TRUE); |
| | |
| | | private static final int LOCK_TIMEOUT = 5; |
| | | |
| | | private ExecutorService singleThreadExecutor; |
| | | |
| | | private File pythonFile = null; |
| | | |
| | | @Autowired |
| | | private MapDataDispatcher mapDataDispatcher; |
| | |
| | | public void execute() { |
| | | this.singleThreadExecutor = Executors.newSingleThreadExecutor(); |
| | | this.singleThreadExecutor.execute(() -> { |
| | | |
| | | try { Thread.sleep(200); } catch (InterruptedException ignore) {} |
| | | try { Thread.sleep(500); } catch (InterruptedException ignore) {} |
| | | |
| | | this.calcDynamicNodeWhenBoot(); |
| | | |
| | | // while (!Thread.currentThread().isInterrupted()) { |
| | | // |
| | | // this.calcWaveScope(); |
| | | // |
| | | // try { Thread.sleep(500); } catch (InterruptedException ignore) {} |
| | | // } |
| | | |
| | | }); |
| | | } |
| | | |
| | | public void calcWaveScope() { |
| | | |
| | | public boolean calcWaveScope(AgvModel agvModel) { |
| | | Integer lev = MapDataDispatcher.MAP_DEFAULT_LEV; |
| | | boolean lockAcquired = false; |
| | | |
| | | StopWatch stopWatch = new StopWatch(); |
| | | stopWatch.start(); |
| | | long startTime = System.currentTimeMillis(); |
| | | |
| | | try { |
| | | if (!(lockAcquired = lock.tryLock(LOCK_TIMEOUT, TimeUnit.SECONDS))) { |
| | | log.warn("AvoidWaveCalculator execute fail, cause can not acquire lock ..."); |
| | | return; |
| | | return false; |
| | | } |
| | | |
| | | // 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); |
| | | |
| | | long startTime = System.currentTimeMillis(); |
| | | |
| | | Resource resource = new ClassPathResource("agv.py"); |
| | | File file = resource.getFile(); |
| | | ProcessBuilder processBuilder = new ProcessBuilder( |
| | | "python" |
| | | , file.getAbsolutePath() |
| | | , String.valueOf(avoidDistance) |
| | | , redisProperties.getHost() |
| | | , redisProperties.getPassword() |
| | | , String.valueOf(redisProperties.getPort()) |
| | | , String.valueOf(redisProperties.getIndex()) |
| | | ); |
| | | processBuilder.redirectErrorStream(true); |
| | | |
| | | try { |
| | | Process process = processBuilder.start(); |
| | | |
| | | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); |
| | | String line; |
| | | StringBuilder builder = new StringBuilder(); |
| | | while ((line = reader.readLine()) != null) { |
| | | builder.append(line); |
| | | } |
| | | |
| | | int exitCode = process.waitFor(); |
| | | if (exitCode != 0) { |
| | | System.out.println("Python script exited with error code: " + exitCode); |
| | | return; |
| | | } |
| | | reader.close(); |
| | | |
| | | if (builder.length() <= 0) { |
| | | return; |
| | | } |
| | | |
| | | String result = builder.toString(); |
| | | |
| | | if (!Cools.isEmpty(result)) { |
| | | if (!"1".equals(result)) { |
| | | log.error("Fail python"); |
| | | } |
| | | } |
| | | } catch (IOException | InterruptedException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | log.error("python finish {}", System.currentTimeMillis() - startTime); |
| | | |
| | | // java |
| | | // String[][] codeMatrix = mapDataDispatcher.getCodeMatrix(lev); |
| | | // String[][] waveMatrix = mapDataDispatcher.initWaveMatrix(lev); |
| | | // |
| | | // // lock path |
| | | // DynamicNode[][] dynamicMatrix = mapDataDispatcher.getDynamicMatrix(lev); |
| | | // 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)) { |
| | | // AgvModel agvModel = agvModelService.getById(agvService.selectByUuid(vehicle).getAgvModel()); // can be optimized |
| | | // |
| | | // Double avoidDistance = MapDataUtils.getVehicleWaveSafeDistance(agvModel.getDiameter(), MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR); |
| | | // List<NavigateNode> includeList = mapService.getWaveScopeByCode(lev, codeMatrix[i][j], avoidDistance); |
| | | // |
| | | // for (NavigateNode navigateNode : includeList) { |
| | | // String waveNode = waveMatrix[navigateNode.getX()][navigateNode.getY()]; // overlay |
| | | // waveMatrix[navigateNode.getX()][navigateNode.getY()] = MapDataUtils.generateWaveNode(waveNode, vehicle); |
| | | // } |
| | | // } |
| | | // } |
| | | // } |
| | | // |
| | | //// mapDataDispatcher.printMatrix(waveMatrix); |
| | | // mapDataDispatcher.setWaveMatrix(lev, waveMatrix); |
| | | // return this.calcWaveScopeByPython(lev); |
| | | return this.calcWaveScopeByJava(lev, agvModel); |
| | | |
| | | } catch (Exception e) { |
| | | |
| | | log.error(this.getClass().getSimpleName(), e); |
| | | return false; |
| | | } finally { |
| | | |
| | | if (lockAcquired) { |
| | | lock.unlock(); |
| | | } |
| | | |
| | | stopWatch.stop(); |
| | | if (stopWatch.getTime() > 100) { |
| | | log.info("滤波函数花费时间为:{}毫秒......", stopWatch.getTime()); |
| | | long during = System.currentTimeMillis() - startTime; |
| | | if (during > 50) { |
| | | log.info("滤波函数花费时间为:{}毫秒......", during); |
| | | } |
| | | |
| | | } |
| | | } |
| | | |
| | | public void syncWaveBySingleVeh(String agvNo, String codeData) { |
| | | if (Cools.isEmpty(agvNo, codeData)) { |
| | | return; |
| | | 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..."); |
| | | } |
| | | |
| | | boolean lockAcquired = false; |
| | | Integer lev = MapDataDispatcher.MAP_DEFAULT_LEV; |
| | | // python |
| | | Double avoidDistance = MapDataUtils.getVehicleWaveSafeDistance(agvModel.getDiameter(), MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR); |
| | | |
| | | try { |
| | | if (!(lockAcquired = lock.tryLock(LOCK_TIMEOUT, TimeUnit.SECONDS))) { |
| | | log.warn("AvoidWaveCalculator syncWaveBySingleVeh fail, cause can not acquire lock ..."); |
| | | return; |
| | | if (null == pythonFile) { |
| | | pythonFile = loadPythonFile(); |
| | | } |
| | | |
| | | ProcessBuilder processBuilder = new ProcessBuilder( |
| | | "python" // 或者 "python3" 取决于系统配置 |
| | | , pythonFile.getAbsolutePath() |
| | | , String.valueOf(avoidDistance) |
| | | , redisProperties.getHost() |
| | | , redisProperties.getPassword() |
| | | , String.valueOf(redisProperties.getPort()) |
| | | , String.valueOf(redisProperties.getIndex()) |
| | | ); |
| | | |
| | | processBuilder.redirectErrorStream(true); |
| | | |
| | | Process process = processBuilder.start(); |
| | | |
| | | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); |
| | | String line; |
| | | StringBuilder builder = new StringBuilder(); |
| | | while ((line = reader.readLine()) != null) { |
| | | builder.append(line); |
| | | } |
| | | |
| | | int exitCode = process.waitFor(); |
| | | if (exitCode != 0) { |
| | | log.error("Python script exited with error code: {}", exitCode); |
| | | log.error("python error:{}", builder.toString()); |
| | | return false; |
| | | } |
| | | reader.close(); |
| | | |
| | | if (builder.length() <= 0) { |
| | | return false; |
| | | } |
| | | |
| | | String result = builder.toString(); |
| | | |
| | | if (Cools.isEmpty(result)) { |
| | | return false; |
| | | } |
| | | if (!"1".equals(result)) { |
| | | log.error("Failed to call python"); |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | private boolean calcWaveScopeByJava(Integer lev, AgvModel agvModel) throws Exception { |
| | | String[][] codeMatrix = mapDataDispatcher.getCodeMatrix(lev); |
| | | Double[][][] cdaMatrix = mapDataDispatcher.getCdaMatrix(lev); |
| | | String[][] waveMatrix = mapDataDispatcher.initWaveMatrix(lev); |
| | | 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); |
| | | |
| | | Agv agv = agvService.selectByUuid(agvNo); |
| | | AgvModel agvModel = agvModelService.getById(agv.getAgvModel()); |
| | | Double avoidDistance = MapDataUtils.getVehicleWaveSafeDistance(agvModel.getDiameter(), MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR); |
| | | |
| | | String[][] waveMatrix = mapDataDispatcher.getWaveMatrix(lev); |
| | | |
| | | List<NavigateNode> includeList = mapService.getWaveScopeByCode(lev, codeData, avoidDistance); |
| | | |
| | | for (NavigateNode navigateNode : includeList) { |
| | | // 将旋转矩形映射到网格,得到需要占用的节点集合 |
| | | 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()); |
| | | } |
| | | } |
| | | |
| | | mapDataDispatcher.setWaveMatrix(lev, waveMatrix); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("AvoidWaveCalculator.syncWaveBySingleVeh fail", e); |
| | | } finally { |
| | | |
| | | if (lockAcquired) { |
| | | lock.unlock(); |
| | | // 再用原来的圆形波纹补上剩余的路径锁定节点 |
| | | 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)) { |
| | | 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()]; |
| | | waveMatrix[navigateNode.getX()][navigateNode.getY()] = MapDataUtils.generateWaveNode(waveNode, vehicle); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 所有车辆同步完成后再统一写回 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 { |
| | | Resource resource = new ClassPathResource("agv.py"); |
| | | |
| | | InputStream is = resource.getInputStream(); |
| | | scriptFile = File.createTempFile("agv", ".py"); |
| | | scriptFile.deleteOnExit(); |
| | | |
| | | Files.copy(is, scriptFile.toPath(), StandardCopyOption.REPLACE_EXISTING); |
| | | |
| | | boolean executable = scriptFile.setExecutable(true); |
| | | } catch (Exception e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | return scriptFile; |
| | | } |
| | | |
| | | public void calcDynamicNodeWhenBoot() { |
| | |
| | | DynamicNode dynamicNode = dynamicMatrix[codeMatrixIdx[0]][codeMatrixIdx[1]]; |
| | | String vehicle = dynamicNode.getVehicle(); |
| | | if (vehicle.equals(DynamicNodeType.ACCESS.val)) { |
| | | mapDataDispatcher.modifyDynamicMatrix(null, Utils.singletonList(code.getData()), agv.getUuid()); |
| | | mapDataDispatcher.modifyDynamicMatrix(null, Utils.singletonList(codeMatrixIdx), agv.getUuid()); |
| | | } |
| | | } |
| | | |