| | |
| | | length: "length", |
| | | headOffset: "head (mm)", |
| | | tailOffset: "tail (mm)", |
| | | width: "width", |
| | | height: "height", |
| | | liftHeight: "lift height", |
| | | width: "width (mm)", |
| | | height: "height (mm)", |
| | | liftHeight: "lift height (mm)", |
| | | diameter: "diameter", |
| | | password: "password", |
| | | backpack: "backpack", |
| | |
| | | length: "é¿åº¦", |
| | | headOffset: "车头(毫米)", |
| | | tailOffset: "车尾(毫米)", |
| | | width: "宽度", |
| | | height: "é«åº¦", |
| | | liftHeight: "举åé«åº¦", |
| | | width: "宽度(毫米)", |
| | | height: "é«åº¦(毫米)", |
| | | liftHeight: "举åé«åº¦(毫米)", |
| | | diameter: "æè½¬ç´å¾", |
| | | password: "设å¤å¯ç ", |
| | | backpack: "èç¯æ°é", |
| | |
| | | AutocompleteInput, |
| | | Toolbar, |
| | | required, |
| | | minValue, |
| | | useDataProvider, |
| | | useNotify, |
| | | Form, |
| | |
| | | validate={required()} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={4} display="flex" gap={1}> |
| | | {/* <Grid item xs={4} display="flex" gap={1}> |
| | | <NumberInput |
| | | label="table.field.agvModel.length" |
| | | source="length" |
| | | /> |
| | | </Grid> |
| | | </Grid> */} |
| | | <Grid item xs={4} display="flex" gap={1}> |
| | | <NumberInput |
| | | label="table.field.agvModel.headOffset" |
| | | source="headOffset" |
| | | validate={required()} |
| | | validate={[required(), minValue(1)]} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={4} display="flex" gap={1}> |
| | | <NumberInput |
| | | label="table.field.agvModel.tailOffset" |
| | | source="tailOffset" |
| | | validate={required()} |
| | | validate={[required(), minValue(1)]} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={4} display="flex" gap={1}> |
| | | <NumberInput |
| | | label="table.field.agvModel.width" |
| | | source="width" |
| | | validate={[required(), minValue(1)]} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={4} display="flex" gap={1}> |
| | | <NumberInput |
| | | label="table.field.agvModel.height" |
| | | source="height" |
| | | validate={[required(), minValue(1)]} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <Grid item xs={4} display="flex" gap={1}> |
| | | <NumberInput |
| | | label="table.field.agvModel.liftHeight" |
| | | source="liftHeight" |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | {/* <Grid item xs={6} display="flex" gap={1}> |
| | | <NumberInput |
| | | label="table.field.agvModel.diameter" |
| | | source="diameter" |
| | | validate={required()} |
| | | /> |
| | | </Grid> |
| | | </Grid> */} |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <TextInput |
| | | label="table.field.agvModel.password" |
| | |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | {/* <NumberInput |
| | | label="table.field.agvModel.length" |
| | | source="length" |
| | | /> |
| | | /> */} |
| | | <NumberInput |
| | | label="table.field.agvModel.headOffset" |
| | | source="headOffset" |
| | | validate={required()} |
| | | min={1} |
| | | /> |
| | | <NumberInput |
| | | label="table.field.agvModel.tailOffset" |
| | | source="tailOffset" |
| | | validate={required()} |
| | | min={1} |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | label="table.field.agvModel.width" |
| | | source="width" |
| | | validate={required()} |
| | | min={1} |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | label="table.field.agvModel.height" |
| | | source="height" |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | label="table.field.agvModel.liftHeight" |
| | | source="liftHeight" |
| | | /> |
| | | </Stack> |
| | | {/* <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | label="table.field.agvModel.diameter" |
| | | source="diameter" |
| | | /> |
| | | </Stack> |
| | | </Stack> */} |
| | | <Stack direction='row' gap={2}> |
| | | <TextInput |
| | | label="table.field.agvModel.password" |
| | |
| | | expand={() => <AgvModelPanel />} |
| | | expandSingle={true} |
| | | omit={['id', 'uuid', 'liftHeight', 'mqttTopic', 'password', 'protocol' |
| | | // , 'headOffset', 'tailOffset' |
| | | , 'length', 'width', 'height', 'allDirectionBool', 'diameter' |
| | | // , 'headOffset', 'tailOffset', 'width' |
| | | , 'length', 'height', 'allDirectionBool', 'diameter' |
| | | , 'statusBool', 'updateBy', 'updateTime', 'createTime', 'createBy', 'memo']} |
| | | > |
| | | <NumberField source="id" /> |
| | |
| | | return JSON.toJSONString(set); |
| | | } |
| | | |
| | | public static Double getVehicleWaveSafeDistance(Integer diameter, Double factor) { |
| | | if (Cools.isEmpty(diameter)) { |
| | | public static Double getVehicleWaveSafeDistance(Integer val, Double factor) { |
| | | if (Cools.isEmpty(val)) { |
| | | return 0.0D; |
| | | } |
| | | factor = Optional.ofNullable(factor).orElse(1.0D); |
| | | return diameter * factor; |
| | | return val * factor; |
| | | } |
| | | |
| | | } |
| | |
| | | */ |
| | | public class MapDataConstant { |
| | | |
| | | public static final Double MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR = 1.00; |
| | | public static final Double MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR = 1.02; |
| | | |
| | | public static final Integer MAX_STEPS_SINGLE = 25; |
| | | |
| New file |
| | |
| | | package com.zy.acs.manager.core.domain; |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * 车è¾å¨èªèº«åæ ç³»ä¸çç©å½¢è½®å»æè¿° |
| | | */ |
| | | @Data |
| | | public class VehicleFootprint { |
| | | |
| | | private double head; |
| | | private double tail; |
| | | private double halfWidth; |
| | | |
| | | public VehicleFootprint(double head, double tail, double halfWidth) { |
| | | this.head = head; |
| | | this.tail = tail; |
| | | this.halfWidth = halfWidth; |
| | | } |
| | | |
| | | public double maxExtent() { |
| | | // double frontDiag = Math.hypot(head, halfWidth); |
| | | // double rearDiag = Math.hypot(tail, halfWidth); |
| | | // return Math.max(frontDiag, rearDiag); |
| | | return Math.hypot(Math.max(head, tail), halfWidth); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.zy.acs.manager.core.domain; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class VehicleRuntime { |
| | | |
| | | private VehicleFootprint footprint; |
| | | private double headingRad; |
| | | |
| | | public VehicleRuntime() { |
| | | } |
| | | |
| | | public VehicleRuntime(VehicleFootprint footprint, double headingRad) { |
| | | this.footprint = footprint; |
| | | this.headingRad = headingRad; |
| | | } |
| | | |
| | | } |
| | |
| | | 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.domain.VehicleRuntime; |
| | | 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 java.io.InputStreamReader; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.StandardCopyOption; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.concurrent.ExecutorService; |
| | | import java.util.concurrent.Executors; |
| | | import java.util.concurrent.TimeUnit; |
| | |
| | | return true; |
| | | } |
| | | |
| | | private boolean calcWaveScopeByJava(Integer lev, AgvModel agvModel) throws Exception { |
| | | if (null == agvModel.getDiameter() || agvModel.getDiameter() <= 0) { |
| | | log.warn("There is no diameter or diameter value was wrong..."); |
| | | } |
| | | |
| | | Double avoidDistance = MapDataUtils.getVehicleWaveSafeDistance(agvModel.getDiameter(), MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR); |
| | | |
| | | // java |
| | | private boolean calcWaveScopeByJava(Integer lev, AgvModel agvModel) { |
| | | assert null != agvModel.getDiameter(); |
| | | String[][] codeMatrix = mapDataDispatcher.getCodeMatrix(lev); |
| | | String[][] waveMatrix = mapDataDispatcher.initWaveMatrix(lev); |
| | | |
| | | // lock path |
| | | DynamicNode[][] dynamicMatrix = mapDataDispatcher.getDynamicMatrix(lev); |
| | | |
| | | // å½åè°åº¦è½¦åçæå¤§æè½¬åå¾ |
| | | double bufferRadius = buildFootprint(agvModel).maxExtent(); |
| | | |
| | | Map<String, VehicleRuntime> runtimeCache = new HashMap<>(); |
| | | 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)) { |
| | | if (DynamicNodeType.ACCESS.val.equals(vehicle) || DynamicNodeType.BLOCK.val.equals(vehicle)) { |
| | | continue; |
| | | } |
| | | String codeData = codeMatrix[i][j]; |
| | | if (CodeNodeType.NONE.val.equals(codeData)) { |
| | | continue; |
| | | } |
| | | |
| | | List<NavigateNode> includeList = mapService.getWaveScopeByCode(lev, codeMatrix[i][j], avoidDistance); |
| | | // cache |
| | | VehicleRuntime runtime = runtimeCache.computeIfAbsent(vehicle, this::loadRuntime); |
| | | assert null != runtime; assert null != runtime.getFootprint(); |
| | | |
| | | for (NavigateNode navigateNode : includeList) { |
| | | String waveNode = waveMatrix[navigateNode.getX()][navigateNode.getY()]; // overlay |
| | | waveMatrix[navigateNode.getX()][navigateNode.getY()] = MapDataUtils.generateWaveNode(waveNode, vehicle); |
| | | } |
| | | List<NavigateNode> includeList = mapService.getWaveScopeByCode(lev, codeData, runtime.getFootprint(), runtime.getHeadingRad(), bufferRadius); |
| | | |
| | | for (NavigateNode navigateNode : includeList) { |
| | | String waveNode = waveMatrix[navigateNode.getX()][navigateNode.getY()]; |
| | | waveMatrix[navigateNode.getX()][navigateNode.getY()] = MapDataUtils.generateWaveNode(waveNode, vehicle); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // mapDataDispatcher.printMatrix(waveMatrix); |
| | | mapDataDispatcher.setWaveMatrix(lev, waveMatrix); |
| | | |
| | | return true; |
| | | } |
| | | |
| | | private VehicleRuntime loadRuntime(String agvNo) { |
| | | AgvModel model = agvModelService.getByAgvNo(agvNo); |
| | | if (model == null) { |
| | | return null; |
| | | } |
| | | VehicleFootprint footprint = this.buildFootprint(model); |
| | | |
| | | AgvDetail detail = agvDetailService.selectByAgvNo(agvNo); |
| | | if (detail == null || null == detail.getAgvAngle()) { |
| | | throw new CoolException(agvNo + " does not have an agv angle"); |
| | | } |
| | | double headingDeg = detail.getAgvAngle(); |
| | | |
| | | return new VehicleRuntime(footprint, Math.toRadians(90 - headingDeg)); |
| | | } |
| | | |
| | | private double toMapLength(Integer mm) { |
| | | if (mm == null || mm <= 0) { |
| | | throw new IllegalArgumentException("Invalid map length: " + mm); |
| | | } |
| | | return MapDataUtils.getVehicleWaveSafeDistance(mm, MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR); |
| | | } |
| | | |
| | | private double resolveAvoidDistance(AgvModel agvModel) { |
| | | if (agvModel == null || agvModel.getDiameter() == null || agvModel.getDiameter() <= 0) { |
| | | return 0D; |
| | | } |
| | | return MapDataUtils.getVehicleWaveSafeDistance(agvModel.getDiameter(), MapDataConstant.MAX_DISTANCE_BETWEEN_ADJACENT_AGV_FACTOR); |
| | | } |
| | | |
| | | private VehicleFootprint buildFootprint(AgvModel agvModel) { |
| | | if (null == agvModel) { |
| | | throw new IllegalArgumentException("AgvModel is null"); |
| | | } |
| | | if (agvModel.getHeadOffset() == null || agvModel.getHeadOffset() <= 0) { |
| | | throw new IllegalArgumentException("Invalid head offset: " + agvModel.getHeadOffset()); |
| | | } |
| | | if (agvModel.getTailOffset() == null || agvModel.getTailOffset() <= 0) { |
| | | throw new IllegalArgumentException("Invalid tail offset: " + agvModel.getTailOffset()); |
| | | } |
| | | if (agvModel.getWidth() == null || agvModel.getWidth() <= 0) { |
| | | throw new IllegalArgumentException("Invalid width: " + agvModel.getWidth()); |
| | | } |
| | | |
| | | double head = toMapLength(agvModel.getHeadOffset()); |
| | | double tail = toMapLength(agvModel.getTailOffset()); |
| | | double halfWidth = toMapLength(agvModel.getWidth()) / 2; |
| | | |
| | | return new VehicleFootprint(head, tail, halfWidth); |
| | | } |
| | | |
| | | private File loadPythonFile() { |
| | | File scriptFile = null; |
| | | try { |
| | |
| | | if (detail.getAgvStatus() != null && detail.getAgvStatus().equals(AgvStatusType.CHARGE)) { |
| | | return false; |
| | | } |
| | | if (!agvService.judgeOnline(agv.getId())) { |
| | | return false; |
| | | } |
| | | |
| | | if (0 < taskService.count(new LambdaQueryWrapper<Task>() |
| | | .eq(Task::getAgvId, agv.getId()) |
| | |
| | | import com.zy.acs.manager.core.domain.LaneDto; |
| | | import com.zy.acs.manager.core.domain.SortCodeDto; |
| | | import com.zy.acs.manager.core.domain.UnlockPathTask; |
| | | import com.zy.acs.manager.core.domain.VehicleFootprint; |
| | | import com.zy.acs.manager.core.service.astart.*; |
| | | import com.zy.acs.manager.core.service.astart.domain.AStarNavigateNode; |
| | | import com.zy.acs.manager.core.service.astart.domain.DynamicNode; |
| | |
| | | * Created by vincent on 2023/6/14 |
| | | */ |
| | | @Slf4j |
| | | @Component("mapService") |
| | | @Component |
| | | public class MapService { |
| | | |
| | | private static final double EPS = 1e-7; |
| | |
| | | return includeList; |
| | | } |
| | | |
| | | public List<NavigateNode> getWaveScopeByCode(Integer lev, String code, VehicleFootprint footprint, double headingRad, double buffer) { |
| | | int[] centerIdx = mapDataDispatcher.getCodeMatrixIdx(lev, code); |
| | | if (centerIdx == null) { |
| | | throw new CoolException(code + " does not exist in codeMatrix"); |
| | | } |
| | | Double[][][] cdaMatrix = mapDataDispatcher.getCdaMatrix(lev); |
| | | Double centerX = cdaMatrix[centerIdx[0]][centerIdx[1]][0]; |
| | | Double centerY = cdaMatrix[centerIdx[0]][centerIdx[1]][1]; |
| | | if (centerX == null || centerY == null) { |
| | | throw new CoolException(code + " does not exist in cdaMatrix"); |
| | | } |
| | | |
| | | double searchRadius = footprint.maxExtent() + buffer; |
| | | |
| | | List<NavigateNode> candidates = this.getWaveScopeByCode(lev, code, searchRadius); |
| | | |
| | | List<NavigateNode> includeList = new ArrayList<>(); |
| | | for (NavigateNode node : candidates) { |
| | | Double px = cdaMatrix[node.getX()][node.getY()][0]; |
| | | Double py = cdaMatrix[node.getX()][node.getY()][1]; |
| | | |
| | | if (isInsideExpandedFootprint(px, py, centerX, centerY, footprint, headingRad, buffer)) { |
| | | includeList.add(node); |
| | | } |
| | | |
| | | } |
| | | return includeList; |
| | | } |
| | | |
| | | public void spreadWaveNode(NavigateNode originNode, NavigateNode currNode |
| | | , String[][] codeMatrix, Double[][][] cdaMatrix, Double radiusLenSquared |
| | | , List<NavigateNode> includeList, Set<NavigateNode> existNodes) { |
| | |
| | | |
| | | } |
| | | |
| | | private boolean isInsideExpandedFootprint(double px, double py, double centerX, double centerY |
| | | , VehicleFootprint footprint, double headingRad, double buffer) { |
| | | // ä¸ãéç½®æä»¥è½¦è¾ä¸ºåç¹ä¸è½¦å¤´ä¸ºxãyæ£æ¹åçåæ ç³» |
| | | // åé平移ï¼å°å¾åæ 系转å°ä»¥è½¦è¾ä¸å¿ä¸ºåç¹çåæ ç³»ï¼ä½¿è½¦è¾ä¸å¿åææ°çåç¹ï¼æ¹ä¾¿è®¡ç®ï¼ |
| | | double dx = px - centerX; |
| | | double dy = py - centerY; |
| | | |
| | | // åæåæ æè½¬å°è½¦è¾åæ ç³»ï¼localX æå车头ï¼localY æå车è¾å·¦ä¾§ï¼ï¼åæ¶åå°å¾çè½¦è¾æè½¬æ¹å |
| | | double localX = dx * Math.cos(headingRad) + dy * Math.sin(headingRad); |
| | | double localY = -dx * Math.sin(headingRad) + dy * Math.cos(headingRad); |
| | | |
| | | // äºã车è¾å¨èªèº«åæ ç³»ä¸çç©å½¢èå´ |
| | | double xMax = footprint.getHead(); // localXæ¹åï¼ ååï¼è½¦å¤´æ¹åï¼æ¯æ£å¼ |
| | | double xMin = -footprint.getTail(); // localXæ¹åï¼ ååï¼è½¦å°¾ï¼æ¯è´å¼ |
| | | |
| | | double yMax = footprint.getHalfWidth(); // localY æ¹åï¼ å·¦ä¾§ä¸ºæ£ |
| | | double yMin = -footprint.getHalfWidth(); // localY æ¹åï¼ å³ä¾§ä¸ºè´ |
| | | |
| | | // ä¸ãè®¡ç® ç¹ æ¯å¦å¨ âç©å½¢å¤æ© + buffer çåºåâé |
| | | // 计ç®ç¹è·ç¦» ç©å½¢è¾¹ç çâè¶
åºéâï¼è½å¨ç©å½¢å
æ¶è¶
åºé为 0 |
| | | double excessX = 0D; // é»è®¤ 0 表示è¿å¨ç©å½¢éé¢ |
| | | if (localX < xMin) { |
| | | excessX = xMin - localX; |
| | | } else if (localX > xMax) { |
| | | excessX = localX - xMax; |
| | | } |
| | | |
| | | double excessY = 0D; // é»è®¤ 0 表示è¿å¨ç©å½¢éé¢ |
| | | if (localY < yMin) { |
| | | excessY = yMin - localY; |
| | | } else if (localY > yMax) { |
| | | excessY = localY - yMax; |
| | | } |
| | | |
| | | // (excessX, excessY) 表示è·ç¦»åé |
| | | |
| | | // éè¿ æ¬§æ°è·ç¦»å¹³æ¹ç®æ³ï¼ è¥å°ç©å½¢çæçè·ç¦»ä¸è¶
è¿ bufferï¼è¯´æå¨â车è¾å½¢ç¶ + ç¼å²åå¾âå
|
| | | return excessX * excessX + excessY * excessY <= buffer * buffer; |
| | | } |
| | | |
| | | // v2 BFS ------------------------------------------------------------------------------ |
| | | // public List<NavigateNode> getWaveScopeByCode0(Integer lev, String code, Double radiusLen) { |
| | | // String[][] codeMatrix = mapDataDispatcher.getCodeMatrix(lev); |