3个文件已修改
1 文件已重命名
46个文件已添加
| | |
| | | # Compiled class file |
| | | *.class |
| | | |
| | | # Log file |
| | | *.log |
| | | |
| | | # BlueJ files |
| | | *.ctxt |
| | | |
| | | # Mobile Tools for Java (J2ME) |
| | | .mtj.tmp/ |
| | |
| | | # Package Files # |
| | | *.jar |
| | | *.war |
| | | *.nar |
| | | *.ear |
| | | *.zip |
| | | *.tar.gz |
| | | *.rar |
| | | |
| | | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml |
| | | hs_err_pid* |
| | | target |
| | | .idea |
| | | .gitignore |
| | | .gitignore |
New file |
| | |
| | | Apache License |
| | | Version 2.0, January 2004 |
| | | http://www.apache.org/licenses/ |
| | | |
| | | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
| | | |
| | | 1. Definitions. |
| | | |
| | | "License" shall mean the terms and conditions for use, reproduction, |
| | | and distribution as defined by Sections 1 through 9 of this document. |
| | | |
| | | "Licensor" shall mean the copyright owner or entity authorized by |
| | | the copyright owner that is granting the License. |
| | | |
| | | "Legal Entity" shall mean the union of the acting entity and all |
| | | other entities that control, are controlled by, or are under common |
| | | control with that entity. For the purposes of this definition, |
| | | "control" means (i) the power, direct or indirect, to cause the |
| | | direction or management of such entity, whether by contract or |
| | | otherwise, or (ii) ownership of fifty percent (50%) or more of the |
| | | outstanding shares, or (iii) beneficial ownership of such entity. |
| | | |
| | | "You" (or "Your") shall mean an individual or Legal Entity |
| | | exercising permissions granted by this License. |
| | | |
| | | "Source" form shall mean the preferred form for making modifications, |
| | | including but not limited to software source code, documentation |
| | | source, and configuration files. |
| | | |
| | | "Object" form shall mean any form resulting from mechanical |
| | | transformation or translation of a Source form, including but |
| | | not limited to compiled object code, generated documentation, |
| | | and conversions to other media types. |
| | | |
| | | "Work" shall mean the work of authorship, whether in Source or |
| | | Object form, made available under the License, as indicated by a |
| | | copyright notice that is included in or attached to the work |
| | | (an example is provided in the Appendix below). |
| | | |
| | | "Derivative Works" shall mean any work, whether in Source or Object |
| | | form, that is based on (or derived from) the Work and for which the |
| | | editorial revisions, annotations, elaborations, or other modifications |
| | | represent, as a whole, an original work of authorship. For the purposes |
| | | of this License, Derivative Works shall not include works that remain |
| | | separable from, or merely link (or bind by name) to the interfaces of, |
| | | the Work and Derivative Works thereof. |
| | | |
| | | "Contribution" shall mean any work of authorship, including |
| | | the original version of the Work and any modifications or additions |
| | | to that Work or Derivative Works thereof, that is intentionally |
| | | submitted to Licensor for inclusion in the Work by the copyright owner |
| | | or by an individual or Legal Entity authorized to submit on behalf of |
| | | the copyright owner. For the purposes of this definition, "submitted" |
| | | means any form of electronic, verbal, or written communication sent |
| | | to the Licensor or its representatives, including but not limited to |
| | | communication on electronic mailing lists, source code control systems, |
| | | and issue tracking systems that are managed by, or on behalf of, the |
| | | Licensor for the purpose of discussing and improving the Work, but |
| | | excluding communication that is conspicuously marked or otherwise |
| | | designated in writing by the copyright owner as "Not a Contribution." |
| | | |
| | | "Contributor" shall mean Licensor and any individual or Legal Entity |
| | | on behalf of whom a Contribution has been received by Licensor and |
| | | subsequently incorporated within the Work. |
| | | |
| | | 2. Grant of Copyright License. Subject to the terms and conditions of |
| | | this License, each Contributor hereby grants to You a perpetual, |
| | | worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
| | | copyright license to reproduce, prepare Derivative Works of, |
| | | publicly display, publicly perform, sublicense, and distribute the |
| | | Work and such Derivative Works in Source or Object form. |
| | | |
| | | 3. Grant of Patent License. Subject to the terms and conditions of |
| | | this License, each Contributor hereby grants to You a perpetual, |
| | | worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
| | | (except as stated in this section) patent license to make, have made, |
| | | use, offer to sell, sell, import, and otherwise transfer the Work, |
| | | where such license applies only to those patent claims licensable |
| | | by such Contributor that are necessarily infringed by their |
| | | Contribution(s) alone or by combination of their Contribution(s) |
| | | with the Work to which such Contribution(s) was submitted. If You |
| | | institute patent litigation against any entity (including a |
| | | cross-claim or counterclaim in a lawsuit) alleging that the Work |
| | | or a Contribution incorporated within the Work constitutes direct |
| | | or contributory patent infringement, then any patent licenses |
| | | granted to You under this License for that Work shall terminate |
| | | as of the date such litigation is filed. |
| | | |
| | | 4. Redistribution. You may reproduce and distribute copies of the |
| | | Work or Derivative Works thereof in any medium, with or without |
| | | modifications, and in Source or Object form, provided that You |
| | | meet the following conditions: |
| | | |
| | | (a) You must give any other recipients of the Work or |
| | | Derivative Works a copy of this License; and |
| | | |
| | | (b) You must cause any modified files to carry prominent notices |
| | | stating that You changed the files; and |
| | | |
| | | (c) You must retain, in the Source form of any Derivative Works |
| | | that You distribute, all copyright, patent, trademark, and |
| | | attribution notices from the Source form of the Work, |
| | | excluding those notices that do not pertain to any part of |
| | | the Derivative Works; and |
| | | |
| | | (d) If the Work includes a "NOTICE" text file as part of its |
| | | distribution, then any Derivative Works that You distribute must |
| | | include a readable copy of the attribution notices contained |
| | | within such NOTICE file, excluding those notices that do not |
| | | pertain to any part of the Derivative Works, in at least one |
| | | of the following places: within a NOTICE text file distributed |
| | | as part of the Derivative Works; within the Source form or |
| | | documentation, if provided along with the Derivative Works; or, |
| | | within a display generated by the Derivative Works, if and |
| | | wherever such third-party notices normally appear. The contents |
| | | of the NOTICE file are for informational purposes only and |
| | | do not modify the License. You may add Your own attribution |
| | | notices within Derivative Works that You distribute, alongside |
| | | or as an addendum to the NOTICE text from the Work, provided |
| | | that such additional attribution notices cannot be construed |
| | | as modifying the License. |
| | | |
| | | You may add Your own copyright statement to Your modifications and |
| | | may provide additional or different license terms and conditions |
| | | for use, reproduction, or distribution of Your modifications, or |
| | | for any such Derivative Works as a whole, provided Your use, |
| | | reproduction, and distribution of the Work otherwise complies with |
| | | the conditions stated in this License. |
| | | |
| | | 5. Submission of Contributions. Unless You explicitly state otherwise, |
| | | any Contribution intentionally submitted for inclusion in the Work |
| | | by You to the Licensor shall be under the terms and conditions of |
| | | this License, without any additional terms or conditions. |
| | | Notwithstanding the above, nothing herein shall supersede or modify |
| | | the terms of any separate license agreement you may have executed |
| | | with Licensor regarding such Contributions. |
| | | |
| | | 6. Trademarks. This License does not grant permission to use the trade |
| | | names, trademarks, service marks, or product names of the Licensor, |
| | | except as required for reasonable and customary use in describing the |
| | | origin of the Work and reproducing the content of the NOTICE file. |
| | | |
| | | 7. Disclaimer of Warranty. Unless required by applicable law or |
| | | agreed to in writing, Licensor provides the Work (and each |
| | | Contributor provides its Contributions) on an "AS IS" BASIS, |
| | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
| | | implied, including, without limitation, any warranties or conditions |
| | | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
| | | PARTICULAR PURPOSE. You are solely responsible for determining the |
| | | appropriateness of using or redistributing the Work and assume any |
| | | risks associated with Your exercise of permissions under this License. |
| | | |
| | | 8. Limitation of Liability. In no event and under no legal theory, |
| | | whether in tort (including negligence), contract, or otherwise, |
| | | unless required by applicable law (such as deliberate and grossly |
| | | negligent acts) or agreed to in writing, shall any Contributor be |
| | | liable to You for damages, including any direct, indirect, special, |
| | | incidental, or consequential damages of any character arising as a |
| | | result of this License or out of the use or inability to use the |
| | | Work (including but not limited to damages for loss of goodwill, |
| | | work stoppage, computer failure or malfunction, or any and all |
| | | other commercial damages or losses), even if such Contributor |
| | | has been advised of the possibility of such damages. |
| | | |
| | | 9. Accepting Warranty or Additional Liability. While redistributing |
| | | the Work or Derivative Works thereof, You may choose to offer, |
| | | and charge a fee for, acceptance of support, warranty, indemnity, |
| | | or other liability obligations and/or rights consistent with this |
| | | License. However, in accepting such obligations, You may act only |
| | | on Your own behalf and on Your sole responsibility, not on behalf |
| | | of any other Contributor, and only if You agree to indemnify, |
| | | defend, and hold each Contributor harmless for any liability |
| | | incurred by, or claims asserted against, such Contributor by reason |
| | | of your accepting any such warranty or additional liability. |
| | | |
| | | END OF TERMS AND CONDITIONS |
| | | |
| | | APPENDIX: How to apply the Apache License to your work. |
| | | |
| | | To apply the Apache License to your work, attach the following |
| | | boilerplate notice, with the fields enclosed by brackets "[]" |
| | | replaced with your own identifying information. (Don't include |
| | | the brackets!) The text should be enclosed in the appropriate |
| | | comment syntax for the file format. We also recommend that a |
| | | file or class name and description of purpose be included on the |
| | | same "printed page" as the copyright notice for easier |
| | | identification within third-party archives. |
| | | |
| | | Copyright [yyyy] [name of copyright owner] |
| | | |
| | | Licensed under the Apache License, Version 2.0 (the "License"); |
| | | you may not use this file except in compliance with the License. |
| | | You may obtain a copy of the License at |
| | | |
| | | http://www.apache.org/licenses/LICENSE-2.0 |
| | | |
| | | Unless required by applicable law or agreed to in writing, software |
| | | distributed under the License is distributed on an "AS IS" BASIS, |
| | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | See the License for the specific language governing permissions and |
| | | limitations under the License. |
| | |
| | | ## algo-java |
| | | ## 介绍 |
| | | |
| | | 本项目技术站: |
| | | jdk8 + springboot + dubbo + mysql + redis + zookeeper |
| | | |
| | | api模块提供最基础的实体类,枚举类等,最重要是需要暴露出去的抽象接口。 |
| | | |
| | | service写具体的接口的实现类,提供具体的实现类。 |
| | | |
| | | rpc-server里面是一个启动类,因为依赖service模块,可以理解为生产提供者。 |
| | | |
| | | web-server则是主要提供http接口给第三方,如前端调用,也可以引入其他项目的api模块,之后只要有生产者注入到注册中心就可以提供服务了,直接调用api里面的抽象接口方法。 |
| | | |
| | | 中科大的老师,负责再service模块里写算法即可 |
| | | |
| | | |
| | | |
| | | 1.service已经集成了redis和mysql也都有示例 |
| | | 2.api模块里只要写接口,供第三方系统使用 |
| | | 3. |
New file |
| | |
| | | [ |
| | | { |
| | | "agvId": "105", |
| | | "status": 0, |
| | | "position": "213", |
| | | "empty": "2", |
| | | "direction": "90", |
| | | "vol": 85, |
| | | "error": 0, |
| | | "autoCharge": 30, |
| | | "lowVol": 15, |
| | | "currentPathIndex": 1, |
| | | "nextPointArrivalTime": 1692345600000, |
| | | "backpack": [ |
| | | { |
| | | "index": 0, |
| | | "loaded": false, |
| | | "taskId": "TASK_001" |
| | | }, |
| | | { |
| | | "index": 1, |
| | | "loaded": false, |
| | | "taskId": null |
| | | }, |
| | | { |
| | | "index": 2, |
| | | "loaded": false, |
| | | "taskId": null |
| | | } |
| | | ], |
| | | |
| | | "physicalConfig": { |
| | | "maxSpeed": 2.0, |
| | | "normalSpeed": 1.5, |
| | | "maxAcceleration": 1.0, |
| | | "maxDeceleration": 1.5, |
| | | "turnTime90": 2.0, |
| | | "turnTime180": 4.0, |
| | | "minSafetyDistance": 3.0, |
| | | "minFollowingDistance": 2.0, |
| | | "ctuLength": 1.5, |
| | | "ctuWidth": 1.0, |
| | | "startupTime": 1.0, |
| | | "stopTime": 1.5, |
| | | "standardPointDistance": 1.0 |
| | | } |
| | | } |
| | | ] |
New file |
| | |
| | | [ |
| | | { |
| | | "agvId": "CTU_001", |
| | | "status": 0, |
| | | "position": "1547", |
| | | "empty": "2", |
| | | "direction": "90", |
| | | "vol": 85, |
| | | "error": 0, |
| | | "autoCharge": 30, |
| | | "lowVol": 15, |
| | | "currentPathIndex": 1, |
| | | "nextPointArrivalTime": 1692345600000, |
| | | "backpack": [ |
| | | { |
| | | "index": 0, |
| | | "loaded": false, |
| | | "taskId": "TASK_001" |
| | | }, |
| | | { |
| | | "index": 1, |
| | | "loaded": false, |
| | | "taskId": null |
| | | }, |
| | | { |
| | | "index": 2, |
| | | "loaded": false, |
| | | "taskId": null |
| | | } |
| | | ], |
| | | |
| | | "physicalConfig": { |
| | | "maxSpeed": 2.0, |
| | | "normalSpeed": 1.5, |
| | | "maxAcceleration": 1.0, |
| | | "maxDeceleration": 1.5, |
| | | "turnTime90": 2.0, |
| | | "turnTime180": 4.0, |
| | | "minSafetyDistance": 3.0, |
| | | "minFollowingDistance": 2.0, |
| | | "ctuLength": 1.5, |
| | | "ctuWidth": 1.0, |
| | | "startupTime": 1.0, |
| | | "stopTime": 1.5, |
| | | "standardPointDistance": 1.0 |
| | | } |
| | | }, |
| | | { |
| | | "agvId": "CTU_002", |
| | | "status": 0, |
| | | "position": "592", |
| | | "empty": "3", |
| | | "direction": "270", |
| | | "vol": 75, |
| | | "error": 0, |
| | | "autoCharge": 30, |
| | | "lowVol": 15, |
| | | "currentPathIndex": 0, |
| | | "nextPointArrivalTime": 1692345620000, |
| | | "backpack": [ |
| | | { |
| | | "index": 0, |
| | | "loaded": false, |
| | | "taskId": null |
| | | }, |
| | | { |
| | | "index": 1, |
| | | "loaded": false, |
| | | "taskId": null |
| | | }, |
| | | { |
| | | "index": 2, |
| | | "loaded": false, |
| | | "taskId": null |
| | | } |
| | | ], |
| | | "remainingPath": { |
| | | "agvId": "CTU_002", |
| | | "segId": "SEG_002_CTU_002_pickup", |
| | | "codeList": [ |
| | | { |
| | | "code": "592", |
| | | "direction": "270", |
| | | "actionType": "2", |
| | | "taskId": "TASK_002", |
| | | "posType": null, |
| | | "lev": 0, |
| | | "isTargetPoint": false |
| | | }, |
| | | { |
| | | "code": "599", |
| | | "direction": "0", |
| | | "actionType": "2", |
| | | "taskId": "TASK_002", |
| | | "posType": null, |
| | | "lev": 0, |
| | | "isTargetPoint": false |
| | | }, |
| | | { |
| | | "code": "647", |
| | | "direction": "0", |
| | | "actionType": "2", |
| | | "taskId": "TASK_002", |
| | | "posType": "1", |
| | | "lev": 0, |
| | | "isTargetPoint": true |
| | | } |
| | | ] |
| | | }, |
| | | "physicalConfig": { |
| | | "maxSpeed": 2.0, |
| | | "normalSpeed": 1.5, |
| | | "maxAcceleration": 1.0, |
| | | "maxDeceleration": 1.5, |
| | | "turnTime90": 2.0, |
| | | "turnTime180": 4.0, |
| | | "minSafetyDistance": 3.0, |
| | | "minFollowingDistance": 2.0, |
| | | "ctuLength": 1.5, |
| | | "ctuWidth": 1.0, |
| | | "startupTime": 1.0, |
| | | "stopTime": 1.5, |
| | | "standardPointDistance": 1.0 |
| | | } |
| | | }, |
| | | { |
| | | "agvId": "CTU_003", |
| | | "status": 0, |
| | | "position": "733", |
| | | "empty": "3", |
| | | "direction": "0", |
| | | "vol": 90, |
| | | "error": 0, |
| | | "autoCharge": 30, |
| | | "lowVol": 15, |
| | | "currentPathIndex": 0, |
| | | "nextPointArrivalTime": 1692345640000, |
| | | "backpack": [ |
| | | { |
| | | "index": 0, |
| | | "loaded": false, |
| | | "taskId": null |
| | | }, |
| | | { |
| | | "index": 1, |
| | | "loaded": false, |
| | | "taskId": null |
| | | }, |
| | | { |
| | | "index": 2, |
| | | "loaded": false, |
| | | "taskId": null |
| | | } |
| | | ], |
| | | "remainingPath": null, |
| | | "physicalConfig": { |
| | | "maxSpeed": 2.0, |
| | | "normalSpeed": 1.5, |
| | | "maxAcceleration": 1.0, |
| | | "maxDeceleration": 1.5, |
| | | "turnTime90": 2.0, |
| | | "turnTime180": 4.0, |
| | | "minSafetyDistance": 3.0, |
| | | "minFollowingDistance": 2.0, |
| | | "ctuLength": 1.5, |
| | | "ctuWidth": 1.0, |
| | | "startupTime": 1.0, |
| | | "stopTime": 1.5, |
| | | "standardPointDistance": 1.0 |
| | | } |
| | | }, |
| | | { |
| | | "agvId": "CTU_004", |
| | | "status": 0, |
| | | "position": "686", |
| | | "empty": "2", |
| | | "direction": "180", |
| | | "vol": 25, |
| | | "error": 0, |
| | | "autoCharge": 30, |
| | | "lowVol": 15, |
| | | "currentPathIndex": 2, |
| | | "nextPointArrivalTime": 1692345660000, |
| | | "backpack": [ |
| | | { |
| | | "index": 0, |
| | | "loaded": true, |
| | | "taskId": "TASK_004" |
| | | }, |
| | | { |
| | | "index": 1, |
| | | "loaded": true, |
| | | "taskId": "TASK_005" |
| | | }, |
| | | { |
| | | "index": 2, |
| | | "loaded": false, |
| | | "taskId": null |
| | | } |
| | | ], |
| | | "remainingPath": { |
| | | "agvId": "CTU_004", |
| | | "segId": "SEG_004_CTU_004_charging", |
| | | "codeList": [ |
| | | { |
| | | "code": "686", |
| | | "direction": "180", |
| | | "actionType": "3", |
| | | "taskId": null, |
| | | "posType": null, |
| | | "lev": 0, |
| | | "isTargetPoint": false |
| | | }, |
| | | { |
| | | "code": "679", |
| | | "direction": "270", |
| | | "actionType": "3", |
| | | "taskId": null, |
| | | "posType": null, |
| | | "lev": 0, |
| | | "isTargetPoint": false |
| | | }, |
| | | { |
| | | "code": "654", |
| | | "direction": "270", |
| | | "actionType": "3", |
| | | "taskId": null, |
| | | "posType": null, |
| | | "lev": 0, |
| | | "isTargetPoint": false |
| | | }, |
| | | { |
| | | "code": "647", |
| | | "direction": "270", |
| | | "actionType": "3", |
| | | "taskId": null, |
| | | "posType": null, |
| | | "lev": 0, |
| | | "isTargetPoint": true |
| | | } |
| | | ] |
| | | }, |
| | | "physicalConfig": { |
| | | "maxSpeed": 2.0, |
| | | "normalSpeed": 1.5, |
| | | "maxAcceleration": 1.0, |
| | | "maxDeceleration": 1.5, |
| | | "turnTime90": 2.0, |
| | | "turnTime180": 4.0, |
| | | "minSafetyDistance": 3.0, |
| | | "minFollowingDistance": 2.0, |
| | | "ctuLength": 1.5, |
| | | "ctuWidth": 1.0, |
| | | "startupTime": 1.0, |
| | | "stopTime": 1.5, |
| | | "standardPointDistance": 1.0 |
| | | } |
| | | } |
| | | ] |
New file |
| | |
| | | [ |
| | | { |
| | | "taskId": "TASK_001", |
| | | "agvId": "105", |
| | | "start": "238", |
| | | "end": "250", |
| | | "type": "1", |
| | | "priority": 5, |
| | | "taskTime": "2023-08-18 10:00:00" |
| | | } |
| | | ] |
New file |
| | |
| | | [ |
| | | { |
| | | "taskId": "TASK_001", |
| | | "agvId": null, |
| | | "start": "346", |
| | | "end": "448", |
| | | "type": "2", |
| | | "priority": 5, |
| | | "taskTime": "2023-08-18 10:00:00" |
| | | }, |
| | | { |
| | | "taskId": "TASK_002", |
| | | "agvId": null, |
| | | "start": "592", |
| | | "end": "647", |
| | | "type": "1", |
| | | "priority": 3, |
| | | "taskTime": "2023-08-18 10:05:00" |
| | | }, |
| | | { |
| | | "taskId": "TASK_003", |
| | | "agvId": null, |
| | | "start": "733", |
| | | "end": "456", |
| | | "type": "3", |
| | | "priority": 4, |
| | | "taskTime": "2023-08-18 10:10:00" |
| | | }, |
| | | { |
| | | "taskId": "TASK_004", |
| | | "agvId": null, |
| | | "start": "686", |
| | | "end": "482", |
| | | "type": "2", |
| | | "priority": 6, |
| | | "taskTime": "2023-08-18 10:15:00" |
| | | }, |
| | | { |
| | | "taskId": "TASK_005", |
| | | "agvId": null, |
| | | "start": "679", |
| | | "end": "537", |
| | | "type": "1", |
| | | "priority": 2, |
| | | "taskTime": "2023-08-18 10:20:00" |
| | | }, |
| | | { |
| | | "taskId": "CHARGE_001", |
| | | "agvId": "CTU_004", |
| | | "start": "686", |
| | | "end": "647", |
| | | "type": "5", |
| | | "priority": 8, |
| | | "taskTime": "2023-08-18 10:25:00" |
| | | }, |
| | | { |
| | | "taskId": "STANDBY_001", |
| | | "agvId": "CTU_003", |
| | | "start": "733", |
| | | "end": "599", |
| | | "type": "4", |
| | | "priority": 1, |
| | | "taskTime": "2023-08-18 10:30:00" |
| | | }, |
| | | { |
| | | "taskId": "TASK_006", |
| | | "agvId": null, |
| | | "start": "544", |
| | | "end": "654", |
| | | "type": "3", |
| | | "priority": 7, |
| | | "taskTime": "2023-08-18 10:35:00" |
| | | }, |
| | | { |
| | | "taskId": "TASK_007", |
| | | "agvId": null, |
| | | "start": "490", |
| | | "end": "599", |
| | | "type": "1", |
| | | "priority": 4, |
| | | "taskTime": "2023-08-18 10:40:00" |
| | | }, |
| | | { |
| | | "taskId": "TASK_008", |
| | | "agvId": null, |
| | | "start": "456", |
| | | "end": "733", |
| | | "type": "2", |
| | | "priority": 3, |
| | | "taskTime": "2023-08-18 10:45:00" |
| | | } |
| | | ] |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project xmlns="http://maven.apache.org/POM/4.0.0" |
| | | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| | | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
| | | <modelVersion>4.0.0</modelVersion> |
| | | |
| | | <parent> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-parent</artifactId> |
| | | <version>2.5.3</version> |
| | | <relativePath/> <!-- lookup parent from repository --> |
| | | </parent> |
| | | |
| | | |
| | | <groupId>com.zy</groupId> |
| | | <artifactId>algo-zkd</artifactId> |
| | | <version>1.0-SNAPSHOT</version> |
| | | |
| | | |
| | | <properties> |
| | | <maven.compiler.source>8</maven.compiler.source> |
| | | <maven.compiler.target>8</maven.compiler.target> |
| | | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| | | </properties> |
| | | |
| | | |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-web</artifactId> |
| | | </dependency> |
| | | |
| | | |
| | | <dependency> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-test</artifactId> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>junit</groupId> |
| | | <artifactId>junit</artifactId> |
| | | </dependency> |
| | | |
| | | </dependencies> |
| | | |
| | | </project> |
New file |
| | |
| | | package com.algo; |
| | | |
| | | import com.algo.model.AGVStatus; |
| | | import com.algo.model.TaskAssignment; |
| | | import com.algo.model.TaskData; |
| | | import com.algo.service.PathPlanningService; |
| | | import com.algo.service.TaskAllocationService; |
| | | import com.algo.util.AgvTaskUtils; |
| | | import com.algo.util.JsonUtils; |
| | | import org.junit.Test; |
| | | import org.junit.runner.RunWith; |
| | | import org.springframework.boot.test.context.SpringBootTest; |
| | | import org.springframework.test.context.junit4.SpringRunner; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @SpringBootTest |
| | | @RunWith(SpringRunner.class) |
| | | public class AlgorithmMain { |
| | | |
| | | |
| | | /** |
| | | * 任务分配测试 |
| | | */ |
| | | @Test |
| | | public void taskAllocationService() { |
| | | Map<String, Map<String, Integer>> pathMapping = JsonUtils.loadPathMapping("path_mapping.json"); |
| | | System.out.println(pathMapping); |
| | | Map<String, Object> environment = JsonUtils.loadEnvironment("environment.json"); |
| | | System.out.println(environment); |
| | | |
| | | TaskAllocationService taskAllocationService = new TaskAllocationService(pathMapping, environment); |
| | | |
| | | List<AGVStatus> agvStatusList = AgvTaskUtils.loadAgvStatus("ctu_agv_status.json"); |
| | | System.out.println(agvStatusList); |
| | | List<TaskData> taskList = AgvTaskUtils.loadTaskList("ctu_task_data.json"); |
| | | System.out.println(taskList); |
| | | |
| | | System.out.println("执行任务分配方法:"); |
| | | List<TaskAssignment> taskAssignments = taskAllocationService.allocateTasks(agvStatusList, taskList); |
| | | System.out.println(taskAssignments); |
| | | System.out.println("验证任务分配结果:"); |
| | | boolean validateAssignments = taskAllocationService.validateAssignments(taskAssignments); |
| | | System.out.println(validateAssignments); |
| | | } |
| | | |
| | | /** |
| | | * 路径优化测试 |
| | | */ |
| | | @Test |
| | | public void pathPlanningService() { |
| | | Map<String, Map<String, Integer>> pathMapping = JsonUtils.loadPathMapping("path_mapping.json"); |
| | | System.out.println(pathMapping); |
| | | Map<String, Object> environment = JsonUtils.loadEnvironment("environment.json"); |
| | | System.out.println(environment); |
| | | List<TaskData> taskList = AgvTaskUtils.loadTaskList("ctu_task_data.json"); |
| | | System.out.println(taskList); |
| | | |
| | | PathPlanningService pathPlanningService = new PathPlanningService(pathMapping, environment, taskList); |
| | | List<AGVStatus> agvStatusList = AgvTaskUtils.loadAgvStatus("ctu_agv_status.json"); |
| | | System.out.println(agvStatusList); |
| | | PathPlanningService.PathPlanningResult planningResult = pathPlanningService.planAllAgvPaths(agvStatusList, true, null); |
| | | System.out.println(planningResult); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.algo; |
| | | |
| | | import org.springframework.boot.SpringApplication; |
| | | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| | | |
| | | |
| | | @SpringBootApplication |
| | | public class SpringBootAplication { |
| | | |
| | | |
| | | public static void main(String[] args) { |
| | | SpringApplication.run(SpringBootAplication.class, args); |
| | | } |
| | | } |
| | | |
New file |
| | |
| | | package com.algo.expose; |
| | | |
| | | import com.algo.model.AGVStatus; |
| | | import com.algo.model.TaskAssignment; |
| | | import com.algo.model.TaskData; |
| | | import com.algo.service.PathPlanningService; |
| | | |
| | | import java.util.List; |
| | | |
| | | public interface AlgoSupport { |
| | | /** |
| | | * 分配任务算法 |
| | | * |
| | | * @return |
| | | */ |
| | | List<TaskAssignment> allocateTasks(List<AGVStatus> agvStatusList, List<TaskData> taskList); |
| | | |
| | | /** |
| | | * 找路径算法 |
| | | * |
| | | * @return |
| | | */ |
| | | PathPlanningService.PathPlanningResult planAllAgvPaths(List<AGVStatus> agvStatusList, boolean flag, List<double[]> constraints); |
| | | |
| | | } |
New file |
| | |
| | | package com.algo.expose.impl; |
| | | |
| | | import com.algo.expose.AlgoSupport; |
| | | import com.algo.model.AGVStatus; |
| | | import com.algo.model.TaskAssignment; |
| | | import com.algo.model.TaskData; |
| | | import com.algo.service.PathPlanningService; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | |
| | | @Service |
| | | public class AlgoSupportImpl implements AlgoSupport { |
| | | |
| | | |
| | | @Override |
| | | public List<TaskAssignment> allocateTasks(List<AGVStatus> agvStatusList, List<TaskData> taskList) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | @Override |
| | | public PathPlanningService.PathPlanningResult planAllAgvPaths(List<AGVStatus> agvStatusList, boolean flag, List<double[]> constraints) { |
| | | return null; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.model; |
| | | |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * AGV状态信息 |
| | | * 包含AGV的完整状态数据,包括剩余路径和CTU物理参数 |
| | | */ |
| | | public class AGVStatus { |
| | | |
| | | /** |
| | | * AGV编号 |
| | | */ |
| | | private String agvId; |
| | | |
| | | /** |
| | | * AGV状态 |
| | | */ |
| | | private int status; |
| | | |
| | | /** |
| | | * AGV当前位置 |
| | | */ |
| | | private String position; |
| | | |
| | | /** |
| | | * 空背篓数量 |
| | | */ |
| | | private String empty; |
| | | |
| | | /** |
| | | * AGV方向角度 |
| | | */ |
| | | private String direction; |
| | | |
| | | /** |
| | | * 电压电量值 |
| | | */ |
| | | private int vol; |
| | | |
| | | /** |
| | | * 异常码,0表示正常 |
| | | */ |
| | | private int error; |
| | | |
| | | /** |
| | | * 背篓数据列表 |
| | | */ |
| | | private List<BackpackData> backpack; |
| | | |
| | | /** |
| | | * 低电量设定阈值,低于该值可以去自动充电也可以继续做任务 |
| | | */ |
| | | private int autoCharge; |
| | | |
| | | /** |
| | | * 最低电量,电量低于该值必须去充电 |
| | | */ |
| | | private int lowVol; |
| | | |
| | | /** |
| | | * CTU剩余未完成的路径,格式与输出路径相同 |
| | | */ |
| | | private PlannedPath remainingPath; |
| | | |
| | | /** |
| | | * CTU物理参数配置 |
| | | */ |
| | | private CTUPhysicalConfig physicalConfig; |
| | | |
| | | /** |
| | | * 当前执行路径中的位置索引(从剩余路径开始计算) |
| | | */ |
| | | private int currentPathIndex; |
| | | |
| | | /** |
| | | * 预计到达下一个路径点的时间戳(毫秒) |
| | | */ |
| | | private long nextPointArrivalTime; |
| | | |
| | | // 构造函数 |
| | | public AGVStatus() { |
| | | this.physicalConfig = new CTUPhysicalConfig(); // 使用默认配置 |
| | | this.currentPathIndex = 0; |
| | | this.nextPointArrivalTime = System.currentTimeMillis(); |
| | | } |
| | | |
| | | public AGVStatus(String agvId, int status, String position, String empty, |
| | | String direction, int vol, int error, List<BackpackData> backpack, |
| | | int autoCharge, int lowVol) { |
| | | this.agvId = agvId; |
| | | this.status = status; |
| | | this.position = position; |
| | | this.empty = empty; |
| | | this.direction = direction; |
| | | this.vol = vol; |
| | | this.error = error; |
| | | this.backpack = backpack; |
| | | this.autoCharge = autoCharge; |
| | | this.lowVol = lowVol; |
| | | this.physicalConfig = new CTUPhysicalConfig(); // 使用默认配置 |
| | | this.currentPathIndex = 0; |
| | | this.nextPointArrivalTime = System.currentTimeMillis(); |
| | | } |
| | | |
| | | // 原有的Getter和Setter方法 |
| | | public String getAgvId() { |
| | | return agvId; |
| | | } |
| | | |
| | | public void setAgvId(String agvId) { |
| | | this.agvId = agvId; |
| | | } |
| | | |
| | | public int getStatus() { |
| | | return status; |
| | | } |
| | | |
| | | public void setStatus(int status) { |
| | | this.status = status; |
| | | } |
| | | |
| | | public String getPosition() { |
| | | return position; |
| | | } |
| | | |
| | | public void setPosition(String position) { |
| | | this.position = position; |
| | | } |
| | | |
| | | public String getEmpty() { |
| | | return empty; |
| | | } |
| | | |
| | | public void setEmpty(String empty) { |
| | | this.empty = empty; |
| | | } |
| | | |
| | | public String getDirection() { |
| | | return direction; |
| | | } |
| | | |
| | | public void setDirection(String direction) { |
| | | this.direction = direction; |
| | | } |
| | | |
| | | public int getVol() { |
| | | return vol; |
| | | } |
| | | |
| | | public void setVol(int vol) { |
| | | this.vol = vol; |
| | | } |
| | | |
| | | public int getError() { |
| | | return error; |
| | | } |
| | | |
| | | public void setError(int error) { |
| | | this.error = error; |
| | | } |
| | | |
| | | public List<BackpackData> getBackpack() { |
| | | return backpack; |
| | | } |
| | | |
| | | public void setBackpack(List<BackpackData> backpack) { |
| | | this.backpack = backpack; |
| | | } |
| | | |
| | | public int getAutoCharge() { |
| | | return autoCharge; |
| | | } |
| | | |
| | | public void setAutoCharge(int autoCharge) { |
| | | this.autoCharge = autoCharge; |
| | | } |
| | | |
| | | public int getLowVol() { |
| | | return lowVol; |
| | | } |
| | | |
| | | public void setLowVol(int lowVol) { |
| | | this.lowVol = lowVol; |
| | | } |
| | | |
| | | // 新增的剩余路径和物理参数相关的Getter和Setter方法 |
| | | public PlannedPath getRemainingPath() { |
| | | return remainingPath; |
| | | } |
| | | |
| | | public void setRemainingPath(PlannedPath remainingPath) { |
| | | this.remainingPath = remainingPath; |
| | | } |
| | | |
| | | public CTUPhysicalConfig getPhysicalConfig() { |
| | | return physicalConfig; |
| | | } |
| | | |
| | | public void setPhysicalConfig(CTUPhysicalConfig physicalConfig) { |
| | | this.physicalConfig = physicalConfig; |
| | | } |
| | | |
| | | public int getCurrentPathIndex() { |
| | | return currentPathIndex; |
| | | } |
| | | |
| | | public void setCurrentPathIndex(int currentPathIndex) { |
| | | this.currentPathIndex = currentPathIndex; |
| | | } |
| | | |
| | | public long getNextPointArrivalTime() { |
| | | return nextPointArrivalTime; |
| | | } |
| | | |
| | | public void setNextPointArrivalTime(long nextPointArrivalTime) { |
| | | this.nextPointArrivalTime = nextPointArrivalTime; |
| | | } |
| | | |
| | | /** |
| | | * 检查CTU是否有剩余未完成的路径 |
| | | * |
| | | * @return true如果有剩余路径 |
| | | */ |
| | | public boolean hasRemainingPath() { |
| | | return remainingPath != null && |
| | | remainingPath.getCodeList() != null && |
| | | !remainingPath.getCodeList().isEmpty() && |
| | | currentPathIndex < remainingPath.getCodeList().size(); |
| | | } |
| | | |
| | | /** |
| | | * 获取剩余路径的长度(从当前位置开始) |
| | | * |
| | | * @return 剩余路径点数量 |
| | | */ |
| | | public int getRemainingPathLength() { |
| | | if (!hasRemainingPath()) { |
| | | return 0; |
| | | } |
| | | return remainingPath.getCodeList().size() - currentPathIndex; |
| | | } |
| | | |
| | | /** |
| | | * 获取当前正在执行的路径点 |
| | | * |
| | | * @return 当前路径点,如果没有则返回null |
| | | */ |
| | | public PathCode getCurrentPathCode() { |
| | | if (!hasRemainingPath()) { |
| | | return null; |
| | | } |
| | | return remainingPath.getCodeList().get(currentPathIndex); |
| | | } |
| | | |
| | | /** |
| | | * 获取下一个要到达的路径点 |
| | | * |
| | | * @return 下一个路径点,如果没有则返回null |
| | | */ |
| | | public PathCode getNextPathCode() { |
| | | if (!hasRemainingPath() || currentPathIndex + 1 >= remainingPath.getCodeList().size()) { |
| | | return null; |
| | | } |
| | | return remainingPath.getCodeList().get(currentPathIndex + 1); |
| | | } |
| | | |
| | | /** |
| | | * 检查AGV是否可用(空闲且非故障状态) |
| | | * |
| | | * @return true如果AGV可用 |
| | | */ |
| | | public boolean isAvailable() { |
| | | return error == 0 && status == 0 && vol > lowVol; |
| | | } |
| | | |
| | | /** |
| | | * 检查CTU是否可以接受新任务(考虑剩余路径) |
| | | * |
| | | * @return true如果可以接受新任务 |
| | | */ |
| | | public boolean canAcceptNewTask() { |
| | | // 如果有剩余路径,需要等待完成后才能接受新任务 |
| | | // 除非剩余路径很短(比如只有1个点) |
| | | if (hasRemainingPath()) { |
| | | int remainingLength = getRemainingPathLength(); |
| | | return remainingLength <= 1; // 允许在即将完成当前路径时接受新任务 |
| | | } |
| | | return isAvailable(); |
| | | } |
| | | |
| | | /** |
| | | * 获取可用的背篓数量 |
| | | * |
| | | * @return 可用背篓数量 |
| | | */ |
| | | public int getAvailableBackpackCount() { |
| | | if (backpack == null || backpack.isEmpty()) { |
| | | return 0; |
| | | } |
| | | |
| | | int count = 0; |
| | | for (BackpackData bp : backpack) { |
| | | if (!bp.isLoaded() && bp.getTaskId() == null) { |
| | | count++; |
| | | } |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | /** |
| | | * 查找可用的背篓位置 |
| | | * |
| | | * @return 可用背篓的索引,如果没有可用则返回-1 |
| | | */ |
| | | public int findAvailableBackpackIndex() { |
| | | if (backpack == null || backpack.isEmpty()) { |
| | | return -1; |
| | | } |
| | | |
| | | for (BackpackData bp : backpack) { |
| | | if (!bp.isLoaded() && bp.getTaskId() == null) { |
| | | return bp.getIndex(); |
| | | } |
| | | } |
| | | return -1; |
| | | } |
| | | |
| | | /** |
| | | * 检查AGV是否需要充电 |
| | | * |
| | | * @return true如果需要充电 |
| | | */ |
| | | public boolean needsCharging() { |
| | | return vol <= autoCharge; |
| | | } |
| | | |
| | | /** |
| | | * 检查AGV是否必须充电 |
| | | * |
| | | * @return true如果必须充电 |
| | | */ |
| | | public boolean mustCharge() { |
| | | return vol <= lowVol; |
| | | } |
| | | |
| | | @Override |
| | | public boolean equals(Object o) { |
| | | if (this == o) return true; |
| | | if (o == null || getClass() != o.getClass()) return false; |
| | | AGVStatus agvStatus = (AGVStatus) o; |
| | | return Objects.equals(agvId, agvStatus.agvId); |
| | | } |
| | | |
| | | @Override |
| | | public int hashCode() { |
| | | return Objects.hash(agvId); |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "AGVStatus{" + |
| | | "agvId='" + agvId + '\'' + |
| | | ", status=" + status + |
| | | ", position='" + position + '\'' + |
| | | ", empty='" + empty + '\'' + |
| | | ", direction='" + direction + '\'' + |
| | | ", vol=" + vol + |
| | | ", error=" + error + |
| | | ", backpack=" + (backpack != null ? backpack.size() : 0) + |
| | | ", autoCharge=" + autoCharge + |
| | | ", lowVol=" + lowVol + |
| | | ", hasRemainingPath=" + hasRemainingPath() + |
| | | ", remainingPathLength=" + getRemainingPathLength() + |
| | | ", physicalConfig=" + physicalConfig + |
| | | '}'; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.model; |
| | | |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * 背篓数据模型 |
| | | * 包含背篓的状态信息 |
| | | */ |
| | | public class BackpackData { |
| | | |
| | | /** |
| | | * 背篓编号 |
| | | */ |
| | | private int index; |
| | | |
| | | /** |
| | | * 是否载货 |
| | | */ |
| | | private boolean loaded; |
| | | |
| | | /** |
| | | * 对应的任务编号 |
| | | */ |
| | | private String taskId; |
| | | |
| | | private boolean execute; |
| | | |
| | | // 构造函数 |
| | | public BackpackData() { |
| | | } |
| | | |
| | | public BackpackData(int index, boolean loaded, String taskId) { |
| | | this.index = index; |
| | | this.loaded = loaded; |
| | | this.taskId = taskId; |
| | | } |
| | | |
| | | // Getter和Setter方法 |
| | | public int getIndex() { |
| | | return index; |
| | | } |
| | | |
| | | public void setIndex(int index) { |
| | | this.index = index; |
| | | } |
| | | |
| | | public boolean isLoaded() { |
| | | return loaded; |
| | | } |
| | | |
| | | public void setLoaded(boolean loaded) { |
| | | this.loaded = loaded; |
| | | } |
| | | |
| | | public String getTaskId() { |
| | | return taskId; |
| | | } |
| | | |
| | | public void setTaskId(String taskId) { |
| | | this.taskId = taskId; |
| | | } |
| | | |
| | | public boolean isExecute() { |
| | | return execute; |
| | | } |
| | | |
| | | public void setExecute(boolean execute) { |
| | | this.execute = execute; |
| | | } |
| | | |
| | | /** |
| | | * 检查背篓是否可用(未载货且无任务) |
| | | * |
| | | * @return true如果背篓可用 |
| | | */ |
| | | public boolean isAvailable() { |
| | | return !loaded && (taskId == null || taskId.trim().isEmpty()); |
| | | } |
| | | |
| | | /** |
| | | * 分配任务到背篓 |
| | | * |
| | | * @param taskId 任务ID |
| | | * @return true如果分配成功 |
| | | */ |
| | | public boolean assignTask(String taskId) { |
| | | if (isAvailable()) { |
| | | this.taskId = taskId; |
| | | return true; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * 清空背篓任务 |
| | | */ |
| | | public void clearTask() { |
| | | this.taskId = null; |
| | | this.loaded = false; |
| | | } |
| | | |
| | | @Override |
| | | public boolean equals(Object o) { |
| | | if (this == o) return true; |
| | | if (o == null || getClass() != o.getClass()) return false; |
| | | BackpackData that = (BackpackData) o; |
| | | return index == that.index; |
| | | } |
| | | |
| | | @Override |
| | | public int hashCode() { |
| | | return Objects.hash(index); |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "BackpackData{" + |
| | | "index=" + index + |
| | | ", loaded=" + loaded + |
| | | ", taskId='" + taskId + '\'' + |
| | | '}'; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.model; |
| | | |
| | | /** |
| | | * CTU物理参数配置类 |
| | | * 包含CTU运动的各种物理参数,用于路径规划和碰撞检测 |
| | | */ |
| | | public class CTUPhysicalConfig { |
| | | |
| | | /** |
| | | * 最大移动速度 (米/秒) |
| | | */ |
| | | private double maxSpeed; |
| | | |
| | | /** |
| | | * 正常移动速度 (米/秒) |
| | | */ |
| | | private double normalSpeed; |
| | | |
| | | /** |
| | | * 最大加速度 (米/秒²) |
| | | */ |
| | | private double maxAcceleration; |
| | | |
| | | /** |
| | | * 最大减速度 (米/秒²) |
| | | */ |
| | | private double maxDeceleration; |
| | | |
| | | /** |
| | | * 转向时间 (秒) - 90度转向所需时间 |
| | | */ |
| | | private double turnTime90; |
| | | |
| | | /** |
| | | * 转向时间 (秒) - 180度转向所需时间 |
| | | */ |
| | | private double turnTime180; |
| | | |
| | | /** |
| | | * CTU之间的最小安全距离 (米) |
| | | */ |
| | | private double minSafetyDistance; |
| | | |
| | | /** |
| | | * CTU之间的最小跟随距离 (米) |
| | | */ |
| | | private double minFollowingDistance; |
| | | |
| | | /** |
| | | * CTU的物理长度 (米) |
| | | */ |
| | | private double ctuLength; |
| | | |
| | | /** |
| | | * CTU的物理宽度 (米) |
| | | */ |
| | | private double ctuWidth; |
| | | |
| | | /** |
| | | * 启动时间 (秒) - 从静止到正常速度所需时间 |
| | | */ |
| | | private double startupTime; |
| | | |
| | | /** |
| | | * 停止时间 (秒) - 从正常速度到静止所需时间 |
| | | */ |
| | | private double stopTime; |
| | | |
| | | /** |
| | | * 路径点之间的标准距离 (米) |
| | | */ |
| | | private double standardPointDistance; |
| | | |
| | | // 默认构造函数 - 使用推荐的默认值 |
| | | public CTUPhysicalConfig() { |
| | | this.maxSpeed = 2.0; // 2米/秒 |
| | | this.normalSpeed = 1.5; // 1.5米/秒 |
| | | this.maxAcceleration = 1.0; // 1米/秒² |
| | | this.maxDeceleration = 1.5; // 1.5米/秒² |
| | | this.turnTime90 = 2.0; // 90度转向需要2秒 |
| | | this.turnTime180 = 4.0; // 180度转向需要4秒 |
| | | this.minSafetyDistance = 3.0; // 最小安全距离3米 |
| | | this.minFollowingDistance = 2.0; // 最小跟随距离2米 |
| | | this.ctuLength = 1.5; // CTU长度1.5米 |
| | | this.ctuWidth = 1.0; // CTU宽度1米 |
| | | this.startupTime = 1.0; // 启动时间1秒 |
| | | this.stopTime = 1.5; // 停止时间1.5秒 |
| | | this.standardPointDistance = 1.0; // 标准路径点距离1米 |
| | | } |
| | | |
| | | // 完整参数的构造函数 |
| | | public CTUPhysicalConfig(double maxSpeed, double normalSpeed, double maxAcceleration, |
| | | double maxDeceleration, double turnTime90, double turnTime180, |
| | | double minSafetyDistance, double minFollowingDistance, |
| | | double ctuLength, double ctuWidth, double startupTime, |
| | | double stopTime, double standardPointDistance) { |
| | | this.maxSpeed = maxSpeed; |
| | | this.normalSpeed = normalSpeed; |
| | | this.maxAcceleration = maxAcceleration; |
| | | this.maxDeceleration = maxDeceleration; |
| | | this.turnTime90 = turnTime90; |
| | | this.turnTime180 = turnTime180; |
| | | this.minSafetyDistance = minSafetyDistance; |
| | | this.minFollowingDistance = minFollowingDistance; |
| | | this.ctuLength = ctuLength; |
| | | this.ctuWidth = ctuWidth; |
| | | this.startupTime = startupTime; |
| | | this.stopTime = stopTime; |
| | | this.standardPointDistance = standardPointDistance; |
| | | } |
| | | |
| | | // Getter和Setter方法 |
| | | public double getMaxSpeed() { |
| | | return maxSpeed; |
| | | } |
| | | |
| | | public void setMaxSpeed(double maxSpeed) { |
| | | this.maxSpeed = maxSpeed; |
| | | } |
| | | |
| | | public double getNormalSpeed() { |
| | | return normalSpeed; |
| | | } |
| | | |
| | | public void setNormalSpeed(double normalSpeed) { |
| | | this.normalSpeed = normalSpeed; |
| | | } |
| | | |
| | | public double getMaxAcceleration() { |
| | | return maxAcceleration; |
| | | } |
| | | |
| | | public void setMaxAcceleration(double maxAcceleration) { |
| | | this.maxAcceleration = maxAcceleration; |
| | | } |
| | | |
| | | public double getMaxDeceleration() { |
| | | return maxDeceleration; |
| | | } |
| | | |
| | | public void setMaxDeceleration(double maxDeceleration) { |
| | | this.maxDeceleration = maxDeceleration; |
| | | } |
| | | |
| | | public double getTurnTime90() { |
| | | return turnTime90; |
| | | } |
| | | |
| | | public void setTurnTime90(double turnTime90) { |
| | | this.turnTime90 = turnTime90; |
| | | } |
| | | |
| | | public double getTurnTime180() { |
| | | return turnTime180; |
| | | } |
| | | |
| | | public void setTurnTime180(double turnTime180) { |
| | | this.turnTime180 = turnTime180; |
| | | } |
| | | |
| | | public double getMinSafetyDistance() { |
| | | return minSafetyDistance; |
| | | } |
| | | |
| | | public void setMinSafetyDistance(double minSafetyDistance) { |
| | | this.minSafetyDistance = minSafetyDistance; |
| | | } |
| | | |
| | | public double getMinFollowingDistance() { |
| | | return minFollowingDistance; |
| | | } |
| | | |
| | | public void setMinFollowingDistance(double minFollowingDistance) { |
| | | this.minFollowingDistance = minFollowingDistance; |
| | | } |
| | | |
| | | public double getCtuLength() { |
| | | return ctuLength; |
| | | } |
| | | |
| | | public void setCtuLength(double ctuLength) { |
| | | this.ctuLength = ctuLength; |
| | | } |
| | | |
| | | public double getCtuWidth() { |
| | | return ctuWidth; |
| | | } |
| | | |
| | | public void setCtuWidth(double ctuWidth) { |
| | | this.ctuWidth = ctuWidth; |
| | | } |
| | | |
| | | public double getStartupTime() { |
| | | return startupTime; |
| | | } |
| | | |
| | | public void setStartupTime(double startupTime) { |
| | | this.startupTime = startupTime; |
| | | } |
| | | |
| | | public double getStopTime() { |
| | | return stopTime; |
| | | } |
| | | |
| | | public void setStopTime(double stopTime) { |
| | | this.stopTime = stopTime; |
| | | } |
| | | |
| | | public double getStandardPointDistance() { |
| | | return standardPointDistance; |
| | | } |
| | | |
| | | public void setStandardPointDistance(double standardPointDistance) { |
| | | this.standardPointDistance = standardPointDistance; |
| | | } |
| | | |
| | | /** |
| | | * 计算从当前速度加速到目标速度所需的时间 |
| | | * |
| | | * @param currentSpeed 当前速度 |
| | | * @param targetSpeed 目标速度 |
| | | * @return 所需时间(秒) |
| | | */ |
| | | public double getAccelerationTime(double currentSpeed, double targetSpeed) { |
| | | if (targetSpeed > currentSpeed) { |
| | | return (targetSpeed - currentSpeed) / maxAcceleration; |
| | | } else { |
| | | return (currentSpeed - targetSpeed) / maxDeceleration; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 计算从当前速度加速/减速到目标速度所需的距离 |
| | | * |
| | | * @param currentSpeed 当前速度 |
| | | * @param targetSpeed 目标速度 |
| | | * @return 所需距离(米) |
| | | */ |
| | | public double getAccelerationDistance(double currentSpeed, double targetSpeed) { |
| | | double time = getAccelerationTime(currentSpeed, targetSpeed); |
| | | return (currentSpeed + targetSpeed) * time / 2.0; |
| | | } |
| | | |
| | | /** |
| | | * 根据路径点距离计算移动时间 |
| | | * |
| | | * @param distance 距离(米) |
| | | * @param currentSpeed 当前速度 |
| | | * @return 移动时间(秒) |
| | | */ |
| | | public double getTravelTime(double distance, double currentSpeed) { |
| | | if (currentSpeed <= 0) { |
| | | currentSpeed = normalSpeed; |
| | | } |
| | | return distance / currentSpeed; |
| | | } |
| | | |
| | | /** |
| | | * 根据方向变化计算转向时间 |
| | | * |
| | | * @param fromDirection 起始方向角度 |
| | | * @param toDirection 目标方向角度 |
| | | * @return 转向时间(秒) |
| | | */ |
| | | public double getTurnTime(String fromDirection, String toDirection) { |
| | | try { |
| | | double from = Double.parseDouble(fromDirection); |
| | | double to = Double.parseDouble(toDirection); |
| | | |
| | | // 计算角度差 |
| | | double angleDiff = Math.abs(to - from); |
| | | if (angleDiff > 180) { |
| | | angleDiff = 360 - angleDiff; |
| | | } |
| | | |
| | | // 根据角度差计算转向时间 |
| | | if (angleDiff <= 5) { // 基本无转向 |
| | | return 0.0; |
| | | } else if (angleDiff <= 90) { |
| | | return turnTime90 * (angleDiff / 90.0); |
| | | } else if (angleDiff <= 180) { |
| | | return turnTime90 + (turnTime180 - turnTime90) * ((angleDiff - 90) / 90.0); |
| | | } else { |
| | | return turnTime180; |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | // 如果方向不是数字,返回默认转向时间 |
| | | return turnTime90; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 检查两个CTU之间的距离是否满足安全要求 |
| | | * |
| | | * @param distance 距离(米) |
| | | * @param isFollowing 是否为跟随情况 |
| | | * @return true如果距离安全 |
| | | */ |
| | | public boolean isSafeDistance(double distance, boolean isFollowing) { |
| | | double minDistance = isFollowing ? minFollowingDistance : minSafetyDistance; |
| | | return distance >= minDistance; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "CTUPhysicalConfig{" + |
| | | "maxSpeed=" + maxSpeed + |
| | | ", normalSpeed=" + normalSpeed + |
| | | ", maxAcceleration=" + maxAcceleration + |
| | | ", maxDeceleration=" + maxDeceleration + |
| | | ", turnTime90=" + turnTime90 + |
| | | ", turnTime180=" + turnTime180 + |
| | | ", minSafetyDistance=" + minSafetyDistance + |
| | | ", minFollowingDistance=" + minFollowingDistance + |
| | | ", ctuLength=" + ctuLength + |
| | | ", ctuWidth=" + ctuWidth + |
| | | ", startupTime=" + startupTime + |
| | | ", stopTime=" + stopTime + |
| | | ", standardPointDistance=" + standardPointDistance + |
| | | '}'; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.model; |
| | | |
| | | /** |
| | | * 冲突模型 |
| | | * 表示AGV路径规划中的冲突信息 |
| | | */ |
| | | public class Conflict { |
| | | |
| | | /** |
| | | * 冲突类型:vertex(顶点冲突), edge(边冲突), follow(跟随冲突) |
| | | */ |
| | | private String type; |
| | | |
| | | /** |
| | | * 冲突AGV1 |
| | | */ |
| | | private String agv1; |
| | | |
| | | /** |
| | | * 冲突AGV2 |
| | | */ |
| | | private String agv2; |
| | | |
| | | /** |
| | | * 冲突发生的时间步 |
| | | */ |
| | | private int timeStep; |
| | | |
| | | /** |
| | | * AGV1的冲突位置 |
| | | */ |
| | | private String position1; |
| | | |
| | | /** |
| | | * AGV2的冲突位置 |
| | | */ |
| | | private String position2; |
| | | |
| | | /** |
| | | * 冲突描述 |
| | | */ |
| | | private String description; |
| | | |
| | | // 构造函数 |
| | | public Conflict() { |
| | | } |
| | | |
| | | public Conflict(String type, String agv1, String agv2, int timeStep, |
| | | String position1, String position2, String description) { |
| | | this.type = type; |
| | | this.agv1 = agv1; |
| | | this.agv2 = agv2; |
| | | this.timeStep = timeStep; |
| | | this.position1 = position1; |
| | | this.position2 = position2; |
| | | this.description = description; |
| | | } |
| | | |
| | | // Getter和Setter方法 |
| | | public String getType() { |
| | | return type; |
| | | } |
| | | |
| | | public void setType(String type) { |
| | | this.type = type; |
| | | } |
| | | |
| | | public String getAgv1() { |
| | | return agv1; |
| | | } |
| | | |
| | | public void setAgv1(String agv1) { |
| | | this.agv1 = agv1; |
| | | } |
| | | |
| | | public String getAgv2() { |
| | | return agv2; |
| | | } |
| | | |
| | | public void setAgv2(String agv2) { |
| | | this.agv2 = agv2; |
| | | } |
| | | |
| | | public int getTimeStep() { |
| | | return timeStep; |
| | | } |
| | | |
| | | public void setTimeStep(int timeStep) { |
| | | this.timeStep = timeStep; |
| | | } |
| | | |
| | | public String getPosition1() { |
| | | return position1; |
| | | } |
| | | |
| | | public void setPosition1(String position1) { |
| | | this.position1 = position1; |
| | | } |
| | | |
| | | public String getPosition2() { |
| | | return position2; |
| | | } |
| | | |
| | | public void setPosition2(String position2) { |
| | | this.position2 = position2; |
| | | } |
| | | |
| | | public String getDescription() { |
| | | return description; |
| | | } |
| | | |
| | | public void setDescription(String description) { |
| | | this.description = description; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "Conflict{" + |
| | | "type='" + type + '\'' + |
| | | ", agv1='" + agv1 + '\'' + |
| | | ", agv2='" + agv2 + '\'' + |
| | | ", timeStep=" + timeStep + |
| | | ", position1='" + position1 + '\'' + |
| | | ", position2='" + position2 + '\'' + |
| | | ", description='" + description + '\'' + |
| | | '}'; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.model; |
| | | |
| | | /** |
| | | * 执行中任务模型 |
| | | * 表示AGV当前正在执行的任务信息 |
| | | */ |
| | | public class ExecutingTask { |
| | | |
| | | /** |
| | | * AGV编号 |
| | | */ |
| | | private String agvId; |
| | | |
| | | /** |
| | | * 任务ID |
| | | */ |
| | | private String taskId; |
| | | |
| | | /** |
| | | * 当前位置 |
| | | */ |
| | | private String currentPosition; |
| | | |
| | | /** |
| | | * 目标位置 |
| | | */ |
| | | private String targetPosition; |
| | | |
| | | /** |
| | | * 任务类型 |
| | | */ |
| | | private String taskType; |
| | | |
| | | /** |
| | | * 是否已装载 |
| | | */ |
| | | private boolean loaded; |
| | | |
| | | /** |
| | | * 背篓索引 |
| | | */ |
| | | private int backpackIndex; |
| | | |
| | | /** |
| | | * 任务优先级 |
| | | */ |
| | | private int priority; |
| | | |
| | | /** |
| | | * 距离起点的距离 |
| | | */ |
| | | private double distanceToStart; |
| | | |
| | | /** |
| | | * 距离终点的距离 |
| | | */ |
| | | private double distanceToEnd; |
| | | |
| | | // 构造函数 |
| | | public ExecutingTask() { |
| | | } |
| | | |
| | | public ExecutingTask(String agvId, String taskId, String currentPosition, |
| | | String targetPosition, String taskType, boolean loaded, |
| | | int backpackIndex, int priority) { |
| | | this.agvId = agvId; |
| | | this.taskId = taskId; |
| | | this.currentPosition = currentPosition; |
| | | this.targetPosition = targetPosition; |
| | | this.taskType = taskType; |
| | | this.loaded = loaded; |
| | | this.backpackIndex = backpackIndex; |
| | | this.priority = priority; |
| | | } |
| | | |
| | | // Getter和Setter方法 |
| | | public String getAgvId() { |
| | | return agvId; |
| | | } |
| | | |
| | | public void setAgvId(String agvId) { |
| | | this.agvId = agvId; |
| | | } |
| | | |
| | | public String getTaskId() { |
| | | return taskId; |
| | | } |
| | | |
| | | public void setTaskId(String taskId) { |
| | | this.taskId = taskId; |
| | | } |
| | | |
| | | public String getCurrentPosition() { |
| | | return currentPosition; |
| | | } |
| | | |
| | | public void setCurrentPosition(String currentPosition) { |
| | | this.currentPosition = currentPosition; |
| | | } |
| | | |
| | | public String getTargetPosition() { |
| | | return targetPosition; |
| | | } |
| | | |
| | | public void setTargetPosition(String targetPosition) { |
| | | this.targetPosition = targetPosition; |
| | | } |
| | | |
| | | public String getTaskType() { |
| | | return taskType; |
| | | } |
| | | |
| | | public void setTaskType(String taskType) { |
| | | this.taskType = taskType; |
| | | } |
| | | |
| | | public boolean isLoaded() { |
| | | return loaded; |
| | | } |
| | | |
| | | public void setLoaded(boolean loaded) { |
| | | this.loaded = loaded; |
| | | } |
| | | |
| | | public int getBackpackIndex() { |
| | | return backpackIndex; |
| | | } |
| | | |
| | | public void setBackpackIndex(int backpackIndex) { |
| | | this.backpackIndex = backpackIndex; |
| | | } |
| | | |
| | | public int getPriority() { |
| | | return priority; |
| | | } |
| | | |
| | | public void setPriority(int priority) { |
| | | this.priority = priority; |
| | | } |
| | | |
| | | public double getDistanceToStart() { |
| | | return distanceToStart; |
| | | } |
| | | |
| | | public void setDistanceToStart(double distanceToStart) { |
| | | this.distanceToStart = distanceToStart; |
| | | } |
| | | |
| | | public double getDistanceToEnd() { |
| | | return distanceToEnd; |
| | | } |
| | | |
| | | public void setDistanceToEnd(double distanceToEnd) { |
| | | this.distanceToEnd = distanceToEnd; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "ExecutingTask{" + |
| | | "agvId='" + agvId + '\'' + |
| | | ", taskId='" + taskId + '\'' + |
| | | ", currentPosition='" + currentPosition + '\'' + |
| | | ", targetPosition='" + targetPosition + '\'' + |
| | | ", taskType='" + taskType + '\'' + |
| | | ", loaded=" + loaded + |
| | | ", backpackIndex=" + backpackIndex + |
| | | ", priority=" + priority + |
| | | ", distanceToStart=" + distanceToStart + |
| | | ", distanceToEnd=" + distanceToEnd + |
| | | '}'; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.model; |
| | | |
| | | import com.fasterxml.jackson.annotation.JsonProperty; |
| | | |
| | | /** |
| | | * 路径代码模型 |
| | | * 表示路径中的单个节点信息 |
| | | */ |
| | | public class PathCode { |
| | | |
| | | /** |
| | | * 路径点编号 |
| | | */ |
| | | private String code; |
| | | |
| | | /** |
| | | * 方向角度 |
| | | */ |
| | | private String direction; |
| | | |
| | | /** |
| | | * 动作类型 |
| | | */ |
| | | private String actionType; |
| | | |
| | | /** |
| | | * 任务ID |
| | | */ |
| | | private String taskId; |
| | | |
| | | /** |
| | | * 位置类型 |
| | | */ |
| | | private String posType; |
| | | |
| | | /** |
| | | * 背篓层级 |
| | | */ |
| | | private int lev; |
| | | |
| | | /** |
| | | * 是否为目标点 |
| | | */ |
| | | @JsonProperty("isTargetPoint") |
| | | private boolean isTargetPoint; |
| | | |
| | | // 构造函数 |
| | | public PathCode() { |
| | | } |
| | | |
| | | public PathCode(String code, String direction) { |
| | | this.code = code; |
| | | this.direction = direction; |
| | | } |
| | | |
| | | public PathCode(String code, String direction, String actionType, String taskId, |
| | | String posType, int lev, boolean isTargetPoint) { |
| | | this.code = code; |
| | | this.direction = direction; |
| | | this.actionType = actionType; |
| | | this.taskId = taskId; |
| | | this.posType = posType; |
| | | this.lev = lev; |
| | | this.isTargetPoint = isTargetPoint; |
| | | } |
| | | |
| | | // Getter和Setter方法 |
| | | public String getCode() { |
| | | return code; |
| | | } |
| | | |
| | | public void setCode(String code) { |
| | | this.code = code; |
| | | } |
| | | |
| | | public String getDirection() { |
| | | return direction; |
| | | } |
| | | |
| | | public void setDirection(String direction) { |
| | | this.direction = direction; |
| | | } |
| | | |
| | | public String getActionType() { |
| | | return actionType; |
| | | } |
| | | |
| | | public void setActionType(String actionType) { |
| | | this.actionType = actionType; |
| | | } |
| | | |
| | | public String getTaskId() { |
| | | return taskId; |
| | | } |
| | | |
| | | public void setTaskId(String taskId) { |
| | | this.taskId = taskId; |
| | | } |
| | | |
| | | public String getPosType() { |
| | | return posType; |
| | | } |
| | | |
| | | public void setPosType(String posType) { |
| | | this.posType = posType; |
| | | } |
| | | |
| | | public int getLev() { |
| | | return lev; |
| | | } |
| | | |
| | | public void setLev(int lev) { |
| | | this.lev = lev; |
| | | } |
| | | |
| | | public boolean isTargetPoint() { |
| | | return isTargetPoint; |
| | | } |
| | | |
| | | public void setTargetPoint(boolean targetPoint) { |
| | | isTargetPoint = targetPoint; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "PathCode{" + |
| | | "code='" + code + '\'' + |
| | | ", direction='" + direction + '\'' + |
| | | ", actionType='" + actionType + '\'' + |
| | | ", taskId='" + taskId + '\'' + |
| | | ", posType='" + posType + '\'' + |
| | | ", lev=" + lev + |
| | | ", isTargetPoint=" + isTargetPoint + |
| | | '}'; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.model; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 规划路径模型 |
| | | * 表示AGV的完整路径规划结果 |
| | | */ |
| | | public class PlannedPath { |
| | | |
| | | /** |
| | | * AGV编号 |
| | | */ |
| | | private String agvId; |
| | | |
| | | /** |
| | | * 段落ID |
| | | */ |
| | | private String segId; |
| | | |
| | | /** |
| | | * 路径代码列表 |
| | | */ |
| | | private List<PathCode> codeList; |
| | | |
| | | // 构造函数 |
| | | public PlannedPath() { |
| | | } |
| | | |
| | | public PlannedPath(String agvId, String segId, List<PathCode> codeList) { |
| | | this.agvId = agvId; |
| | | this.segId = segId; |
| | | this.codeList = codeList; |
| | | } |
| | | |
| | | // Getter和Setter方法 |
| | | public String getAgvId() { |
| | | return agvId; |
| | | } |
| | | |
| | | public void setAgvId(String agvId) { |
| | | this.agvId = agvId; |
| | | } |
| | | |
| | | public String getSegId() { |
| | | return segId; |
| | | } |
| | | |
| | | public void setSegId(String segId) { |
| | | this.segId = segId; |
| | | } |
| | | |
| | | public List<PathCode> getCodeList() { |
| | | return codeList; |
| | | } |
| | | |
| | | public void setCodeList(List<PathCode> codeList) { |
| | | this.codeList = codeList; |
| | | } |
| | | |
| | | /** |
| | | * 获取路径长度 |
| | | * |
| | | * @return 路径长度 |
| | | */ |
| | | public int getPathLength() { |
| | | return codeList != null ? codeList.size() : 0; |
| | | } |
| | | |
| | | /** |
| | | * 获取起始位置 |
| | | * |
| | | * @return 起始位置编号 |
| | | */ |
| | | public String getStartPosition() { |
| | | return (codeList != null && !codeList.isEmpty()) ? codeList.get(0).getCode() : null; |
| | | } |
| | | |
| | | /** |
| | | * 获取终点位置 |
| | | * |
| | | * @return 终点位置编号 |
| | | */ |
| | | public String getEndPosition() { |
| | | return (codeList != null && !codeList.isEmpty()) ? |
| | | codeList.get(codeList.size() - 1).getCode() : null; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "PlannedPath{" + |
| | | "agvId='" + agvId + '\'' + |
| | | ", segId='" + segId + '\'' + |
| | | ", pathLength=" + getPathLength() + |
| | | ", startPosition='" + getStartPosition() + '\'' + |
| | | ", endPosition='" + getEndPosition() + '\'' + |
| | | '}'; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.model; |
| | | |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * 任务分配结果数据模型 |
| | | * 包含任务分配的完整结果信息 |
| | | */ |
| | | public class TaskAssignment { |
| | | |
| | | /** |
| | | * 任务ID |
| | | */ |
| | | private String taskId; |
| | | |
| | | /** |
| | | * 分配的AGV编号 |
| | | */ |
| | | private String agvId; |
| | | |
| | | /** |
| | | * 片段ID,格式:任务编号_AGV编号_序号 |
| | | */ |
| | | private String segId; |
| | | |
| | | /** |
| | | * 使用的背篓编号 |
| | | */ |
| | | private String lev; |
| | | |
| | | /** |
| | | * 动作类型:take取货 put放货 |
| | | */ |
| | | private String type; |
| | | |
| | | // 动作类型常量 |
| | | public static final String TYPE_TAKE = "take"; // 取货 |
| | | public static final String TYPE_PUT = "put"; // 放货 |
| | | |
| | | // 构造函数 |
| | | public TaskAssignment() { |
| | | } |
| | | |
| | | public TaskAssignment(String taskId, String agvId, String segId, String lev, String type) { |
| | | this.taskId = taskId; |
| | | this.agvId = agvId; |
| | | this.segId = segId; |
| | | this.lev = lev; |
| | | this.type = type; |
| | | } |
| | | |
| | | // Getter和Setter方法 |
| | | public String getTaskId() { |
| | | return taskId; |
| | | } |
| | | |
| | | public void setTaskId(String taskId) { |
| | | this.taskId = taskId; |
| | | } |
| | | |
| | | public String getAgvId() { |
| | | return agvId; |
| | | } |
| | | |
| | | public void setAgvId(String agvId) { |
| | | this.agvId = agvId; |
| | | } |
| | | |
| | | public String getSegId() { |
| | | return segId; |
| | | } |
| | | |
| | | public void setSegId(String segId) { |
| | | this.segId = segId; |
| | | } |
| | | |
| | | public String getLev() { |
| | | return lev; |
| | | } |
| | | |
| | | public void setLev(String lev) { |
| | | this.lev = lev; |
| | | } |
| | | |
| | | public String getType() { |
| | | return type; |
| | | } |
| | | |
| | | public void setType(String type) { |
| | | this.type = type; |
| | | } |
| | | |
| | | /** |
| | | * 生成片段ID |
| | | * |
| | | * @param taskId 任务ID |
| | | * @param agvId AGV编号 |
| | | * @param sequence 序号(1表示取货,2表示放货) |
| | | * @return 片段ID |
| | | */ |
| | | public static String generateSegId(String taskId, String agvId, int sequence) { |
| | | return taskId + "_" + agvId + "_" + sequence; |
| | | } |
| | | |
| | | /** |
| | | * 检查是否为取货任务 |
| | | * |
| | | * @return true如果是取货任务 |
| | | */ |
| | | public boolean isTakeTask() { |
| | | return TYPE_TAKE.equals(type); |
| | | } |
| | | |
| | | /** |
| | | * 检查是否为放货任务 |
| | | * |
| | | * @return true如果是放货任务 |
| | | */ |
| | | public boolean isPutTask() { |
| | | return TYPE_PUT.equals(type); |
| | | } |
| | | |
| | | /** |
| | | * 解析片段ID获取序号 |
| | | * |
| | | * @return 序号,如果解析失败返回0 |
| | | */ |
| | | public int parseSequenceFromSegId() { |
| | | if (segId == null || segId.trim().isEmpty()) { |
| | | return 0; |
| | | } |
| | | |
| | | try { |
| | | String[] parts = segId.split("_"); |
| | | if (parts.length >= 3) { |
| | | return Integer.parseInt(parts[2]); |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | // 忽略解析错误 |
| | | } |
| | | |
| | | return 0; |
| | | } |
| | | |
| | | /** |
| | | * 验证任务分配结果是否有效 |
| | | * |
| | | * @return true如果有效 |
| | | */ |
| | | public boolean isValid() { |
| | | return taskId != null && !taskId.trim().isEmpty() && |
| | | agvId != null && !agvId.trim().isEmpty() && |
| | | segId != null && !segId.trim().isEmpty() && |
| | | lev != null && !lev.trim().isEmpty() && |
| | | type != null && (TYPE_TAKE.equals(type) || TYPE_PUT.equals(type)); |
| | | } |
| | | |
| | | @Override |
| | | public boolean equals(Object o) { |
| | | if (this == o) return true; |
| | | if (o == null || getClass() != o.getClass()) return false; |
| | | TaskAssignment that = (TaskAssignment) o; |
| | | return Objects.equals(taskId, that.taskId) && |
| | | Objects.equals(agvId, that.agvId) && |
| | | Objects.equals(segId, that.segId); |
| | | } |
| | | |
| | | @Override |
| | | public int hashCode() { |
| | | return Objects.hash(taskId, agvId, segId); |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "TaskAssignment{" + |
| | | "taskId='" + taskId + '\'' + |
| | | ", agvId='" + agvId + '\'' + |
| | | ", segId='" + segId + '\'' + |
| | | ", lev='" + lev + '\'' + |
| | | ", type='" + type + '\'' + |
| | | '}'; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.model; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * 任务数据模型 |
| | | * 包含任务的完整信息 |
| | | */ |
| | | public class TaskData { |
| | | |
| | | /** |
| | | * 任务ID |
| | | */ |
| | | private String taskId; |
| | | |
| | | /** |
| | | * AGV编号(仅充电任务和去待机位任务使用) |
| | | */ |
| | | private String agvId; |
| | | |
| | | /** |
| | | * 任务起点 |
| | | */ |
| | | private String start; |
| | | |
| | | /** |
| | | * 任务终点 |
| | | */ |
| | | private String end; |
| | | |
| | | /** |
| | | * 任务类型:1入库 2出库 3移库 4去待机位 5充电 |
| | | */ |
| | | private String type; |
| | | |
| | | /** |
| | | * 任务优先级,越大优先级越高 |
| | | */ |
| | | private int priority; |
| | | |
| | | /** |
| | | * 任务创建时间 |
| | | */ |
| | | private String taskTime; |
| | | |
| | | // 任务类型常量 |
| | | public static final String TYPE_INBOUND = "1"; // 入库 |
| | | public static final String TYPE_OUTBOUND = "2"; // 出库 |
| | | public static final String TYPE_MOVE = "3"; // 移库 |
| | | public static final String TYPE_STANDBY = "4"; // 去待机位 |
| | | public static final String TYPE_CHARGE = "5"; // 充电 |
| | | |
| | | // 构造函数 |
| | | public TaskData() { |
| | | } |
| | | |
| | | public TaskData(String taskId, String agvId, String start, String end, |
| | | String type, int priority, String taskTime) { |
| | | this.taskId = taskId; |
| | | this.agvId = agvId; |
| | | this.start = start; |
| | | this.end = end; |
| | | this.type = type; |
| | | this.priority = priority; |
| | | this.taskTime = taskTime; |
| | | } |
| | | |
| | | // Getter和Setter方法 |
| | | public String getTaskId() { |
| | | return taskId; |
| | | } |
| | | |
| | | public void setTaskId(String taskId) { |
| | | this.taskId = taskId; |
| | | } |
| | | |
| | | public String getAgvId() { |
| | | return agvId; |
| | | } |
| | | |
| | | public void setAgvId(String agvId) { |
| | | this.agvId = agvId; |
| | | } |
| | | |
| | | public String getStart() { |
| | | return start; |
| | | } |
| | | |
| | | public void setStart(String start) { |
| | | this.start = start; |
| | | } |
| | | |
| | | public String getEnd() { |
| | | return end; |
| | | } |
| | | |
| | | public void setEnd(String end) { |
| | | this.end = end; |
| | | } |
| | | |
| | | public String getType() { |
| | | return type; |
| | | } |
| | | |
| | | public void setType(String type) { |
| | | this.type = type; |
| | | } |
| | | |
| | | public int getPriority() { |
| | | return priority; |
| | | } |
| | | |
| | | public void setPriority(int priority) { |
| | | this.priority = priority; |
| | | } |
| | | |
| | | public String getTaskTime() { |
| | | return taskTime; |
| | | } |
| | | |
| | | public void setTaskTime(String taskTime) { |
| | | this.taskTime = taskTime; |
| | | } |
| | | |
| | | /** |
| | | * 检查是否为运输任务(需要取货和送货) |
| | | * |
| | | * @return true如果是运输任务 |
| | | */ |
| | | public boolean isTransportTask() { |
| | | return TYPE_INBOUND.equals(type) || TYPE_OUTBOUND.equals(type) || TYPE_MOVE.equals(type); |
| | | } |
| | | |
| | | /** |
| | | * 检查是否为充电任务 |
| | | * |
| | | * @return true如果是充电任务 |
| | | */ |
| | | public boolean isChargeTask() { |
| | | return TYPE_CHARGE.equals(type); |
| | | } |
| | | |
| | | /** |
| | | * 检查是否为去待机位任务 |
| | | * |
| | | * @return true如果是去待机位任务 |
| | | */ |
| | | public boolean isStandbyTask() { |
| | | return TYPE_STANDBY.equals(type); |
| | | } |
| | | |
| | | /** |
| | | * 检查是否为指定AGV的任务 |
| | | * |
| | | * @return true如果是指定AGV的任务 |
| | | */ |
| | | public boolean isAssignedToAgv() { |
| | | return agvId != null && !agvId.trim().isEmpty(); |
| | | } |
| | | |
| | | /** |
| | | * 解析任务创建时间 |
| | | * |
| | | * @return LocalDateTime对象 |
| | | */ |
| | | public LocalDateTime parseTaskTime() { |
| | | if (taskTime == null || taskTime.trim().isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | try { |
| | | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); |
| | | return LocalDateTime.parse(taskTime, formatter); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取任务类型描述 |
| | | * |
| | | * @return 任务类型描述 |
| | | */ |
| | | public String getTypeDescription() { |
| | | switch (type) { |
| | | case TYPE_INBOUND: |
| | | return "入库"; |
| | | case TYPE_OUTBOUND: |
| | | return "出库"; |
| | | case TYPE_MOVE: |
| | | return "移库"; |
| | | case TYPE_STANDBY: |
| | | return "去待机位"; |
| | | case TYPE_CHARGE: |
| | | return "充电"; |
| | | default: |
| | | return "未知任务类型"; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public boolean equals(Object o) { |
| | | if (this == o) return true; |
| | | if (o == null || getClass() != o.getClass()) return false; |
| | | TaskData taskData = (TaskData) o; |
| | | return Objects.equals(taskId, taskData.taskId); |
| | | } |
| | | |
| | | @Override |
| | | public int hashCode() { |
| | | return Objects.hash(taskId); |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "TaskData{" + |
| | | "taskId='" + taskId + '\'' + |
| | | ", agvId='" + agvId + '\'' + |
| | | ", start='" + start + '\'' + |
| | | ", end='" + end + '\'' + |
| | | ", type='" + type + '\'' + |
| | | ", priority=" + priority + |
| | | ", taskTime='" + taskTime + '\'' + |
| | | '}'; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.service; |
| | | |
| | | import com.algo.model.CTUPhysicalConfig; |
| | | import com.algo.model.PathCode; |
| | | import com.algo.model.PlannedPath; |
| | | import com.algo.util.JsonUtils; |
| | | |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * A*路径规划器实现 |
| | | * 使用3D A*算法进行CTU时空路径规划,支持物理约束 |
| | | */ |
| | | public class AStarPathPlanner implements PathPlanner { |
| | | |
| | | /** |
| | | * 路径映射表 |
| | | */ |
| | | private final Map<String, Map<String, Integer>> pathMapping; |
| | | |
| | | /** |
| | | * 邻接表 |
| | | */ |
| | | private final Map<String, List<Map<String, String>>> adjacencyList; |
| | | |
| | | /** |
| | | * 时间精度(毫秒) |
| | | */ |
| | | private final long timeResolution = 1000; |
| | | |
| | | /** |
| | | * 最大搜索时间(毫秒) |
| | | */ |
| | | private final long maxSearchTime = 30000; // 30秒 |
| | | |
| | | /** |
| | | * 最大搜索深度 |
| | | */ |
| | | private final int maxSearchDepth = 15000; |
| | | |
| | | /** |
| | | * 距离缓存 - 优化:缓存距离计算结果 |
| | | */ |
| | | private final Map<String, Double> distanceCache = new HashMap<>(); |
| | | |
| | | /** |
| | | * 快速路径缓存 |
| | | */ |
| | | private final Map<String, List<String>> fastPathCache = new HashMap<>(); |
| | | |
| | | /** |
| | | * 构造函数 |
| | | * |
| | | * @param pathMapping 路径映射表 |
| | | */ |
| | | public AStarPathPlanner(Map<String, Map<String, Integer>> pathMapping) { |
| | | this.pathMapping = pathMapping; |
| | | this.adjacencyList = new HashMap<>(); |
| | | |
| | | // 直接构建邻接表 |
| | | buildAdjacencyList(); |
| | | |
| | | // 预计算常用距离 |
| | | precomputeCommonDistances(); |
| | | |
| | | System.out.println("A*路径规划器初始化完成,邻接表包含 " + adjacencyList.size() + " 个节点"); |
| | | } |
| | | |
| | | @Override |
| | | public PlannedPath planPath(String startCode, String endCode, List<double[]> constraints) { |
| | | // 先尝试快速路径规划,失败后再使用完整的时空规划 |
| | | PlannedPath fastPath = tryFastPathPlanning(startCode, endCode, constraints); |
| | | if (fastPath != null) { |
| | | return fastPath; |
| | | } |
| | | return planSpaceTimePath(startCode, endCode, constraints, null, null); |
| | | } |
| | | |
| | | @Override |
| | | public PlannedPath planPath(String startCode, String endCode) { |
| | | return planPath(startCode, endCode, null); |
| | | } |
| | | |
| | | /** |
| | | * 规划时空路径 |
| | | * |
| | | * @param startCode 起始路径点ID |
| | | * @param endCode 目标路径点ID |
| | | * @param constraints 静态约束条件 |
| | | * @param spaceTimeOccupancyMap 时空占用表 |
| | | * @param physicalConfig CTU物理配置 |
| | | * @return 规划的路径 |
| | | */ |
| | | public PlannedPath planSpaceTimePath(String startCode, String endCode, |
| | | List<double[]> constraints, |
| | | Map<String, String> spaceTimeOccupancyMap, |
| | | CTUPhysicalConfig physicalConfig) { |
| | | // 验证输入 |
| | | if (!isValidPathPoint(startCode) || !isValidPathPoint(endCode)) { |
| | | System.out.println("无效的路径点: " + startCode + " 或 " + endCode); |
| | | return null; |
| | | } |
| | | |
| | | // 起点和终点相同 |
| | | if (startCode.equals(endCode)) { |
| | | List<PathCode> codeList = new ArrayList<>(); |
| | | codeList.add(new PathCode(startCode, "90")); |
| | | return new PlannedPath("", "", codeList); |
| | | } |
| | | |
| | | // 使用默认物理配置 |
| | | if (physicalConfig == null) { |
| | | physicalConfig = createDefaultPhysicalConfig(); |
| | | } |
| | | |
| | | // 获取起点和终点坐标 |
| | | int[] startCoord = JsonUtils.getCoordinate(startCode, pathMapping); |
| | | int[] endCoord = JsonUtils.getCoordinate(endCode, pathMapping); |
| | | |
| | | if (startCoord == null || endCoord == null) { |
| | | System.out.println("无法获取坐标: " + startCode + " 或 " + endCode); |
| | | return null; |
| | | } |
| | | |
| | | System.out.println("开始时空A*搜索: " + startCode + " -> " + endCode); |
| | | |
| | | // 时空A*算法实现 |
| | | PlannedPath result = spaceTimeAStarSearch( |
| | | startCode, endCode, startCoord, endCoord, |
| | | constraints, spaceTimeOccupancyMap, physicalConfig |
| | | ); |
| | | |
| | | if (result != null) { |
| | | System.out.println("时空路径规划成功,路径长度: " + result.getCodeList().size()); |
| | | } else { |
| | | System.out.println("时空路径规划失败"); |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 时空A*搜索算法实现 |
| | | * |
| | | * @param startCode 起始路径点ID |
| | | * @param endCode 目标路径点ID |
| | | * @param startCoord 起始坐标 |
| | | * @param endCoord 目标坐标 |
| | | * @param constraints 约束条件 |
| | | * @param occupancyMap 时空占用表 |
| | | * @param physicalConfig 物理配置 |
| | | * @return 规划的路径 |
| | | */ |
| | | private PlannedPath spaceTimeAStarSearch(String startCode, String endCode, |
| | | int[] startCoord, int[] endCoord, |
| | | List<double[]> constraints, |
| | | Map<String, String> occupancyMap, |
| | | CTUPhysicalConfig physicalConfig) { |
| | | |
| | | // 使用优先队列实现开放列表 |
| | | PriorityQueue<SpaceTimeAStarNode> openSet = new PriorityQueue<>( |
| | | Comparator.comparingDouble(n -> n.fScore) |
| | | ); |
| | | Set<String> closedSet = new HashSet<>(); |
| | | Map<String, Double> gScores = new HashMap<>(); |
| | | Map<String, SpaceTimeAStarNode> cameFrom = new HashMap<>(); |
| | | |
| | | // 起始时间 |
| | | long startTime = System.currentTimeMillis(); |
| | | |
| | | // 初始化起始节点 |
| | | SpaceTimeAStarNode startNode = new SpaceTimeAStarNode( |
| | | startCode, startCoord, startTime, 0, |
| | | spaceTimeHeuristic(startCoord, endCoord, startTime, physicalConfig) |
| | | ); |
| | | |
| | | openSet.offer(startNode); |
| | | String startKey = createSpaceTimeKey(startCode, startTime); |
| | | gScores.put(startKey, 0.0); |
| | | |
| | | // 构建约束检查器 |
| | | EnhancedConstraintChecker constraintChecker = new EnhancedConstraintChecker( |
| | | constraints, occupancyMap, pathMapping, physicalConfig |
| | | ); |
| | | |
| | | int searchDepth = 0; |
| | | long searchStartTime = System.currentTimeMillis(); |
| | | |
| | | while (!openSet.isEmpty()) { |
| | | // 检查搜索时间和深度限制 |
| | | if (System.currentTimeMillis() - searchStartTime > maxSearchTime || |
| | | searchDepth > maxSearchDepth) { |
| | | System.out.println("搜索超时或达到最大深度,返回null"); |
| | | break; |
| | | } |
| | | |
| | | SpaceTimeAStarNode current = openSet.poll(); |
| | | String currentKey = createSpaceTimeKey(current.code, current.timePoint); |
| | | |
| | | if (closedSet.contains(currentKey)) { |
| | | continue; |
| | | } |
| | | |
| | | closedSet.add(currentKey); |
| | | searchDepth++; |
| | | |
| | | // 到达目标 |
| | | if (current.code.equals(endCode)) { |
| | | System.out.println("找到目标,重建路径,搜索深度: " + searchDepth); |
| | | return reconstructSpaceTimePath(cameFrom, startNode, current, physicalConfig); |
| | | } |
| | | |
| | | // 扩展邻居节点 |
| | | expandNeighbors(current, endCoord, openSet, closedSet, gScores, cameFrom, |
| | | constraintChecker, physicalConfig); |
| | | } |
| | | |
| | | System.out.println("未找到有效路径,搜索深度: " + searchDepth); |
| | | return null; // 无法找到路径 |
| | | } |
| | | |
| | | /** |
| | | * 扩展邻居节点 |
| | | */ |
| | | private void expandNeighbors(SpaceTimeAStarNode current, int[] endCoord, |
| | | PriorityQueue<SpaceTimeAStarNode> openSet, |
| | | Set<String> closedSet, Map<String, Double> gScores, |
| | | Map<String, SpaceTimeAStarNode> cameFrom, |
| | | EnhancedConstraintChecker constraintChecker, |
| | | CTUPhysicalConfig physicalConfig) { |
| | | |
| | | // 获取空间邻居 |
| | | List<Map<String, String>> neighbors = getNeighbors(current.code); |
| | | |
| | | for (Map<String, String> neighbor : neighbors) { |
| | | String neighborCode = neighbor.get("code"); |
| | | String direction = neighbor.get("direction"); |
| | | |
| | | // 计算移动到邻居的时间成本 |
| | | long travelTime = calculateTravelTime(current.code, neighborCode, direction, physicalConfig); |
| | | long arrivalTime = current.timePoint + travelTime; |
| | | |
| | | // 时间对齐到分辨率 |
| | | arrivalTime = alignToTimeResolution(arrivalTime); |
| | | |
| | | String neighborKey = createSpaceTimeKey(neighborCode, arrivalTime); |
| | | |
| | | if (closedSet.contains(neighborKey)) { |
| | | continue; |
| | | } |
| | | |
| | | // 约束检查 |
| | | if (!constraintChecker.isValidMove(current.code, neighborCode, |
| | | current.timePoint, arrivalTime)) { |
| | | continue; |
| | | } |
| | | |
| | | // 计算G值 |
| | | double gScore = gScores.get(createSpaceTimeKey(current.code, current.timePoint)) + |
| | | travelTime / 1000.0; // 转换为秒 |
| | | |
| | | // 更新最佳路径 |
| | | if (!gScores.containsKey(neighborKey) || gScore < gScores.get(neighborKey)) { |
| | | gScores.put(neighborKey, gScore); |
| | | |
| | | // 获取邻居坐标 |
| | | int[] neighborCoord = JsonUtils.getCoordinate(neighborCode, pathMapping); |
| | | if (neighborCoord != null) { |
| | | // 创建邻居节点 |
| | | SpaceTimeAStarNode neighborNode = new SpaceTimeAStarNode( |
| | | neighborCode, neighborCoord, arrivalTime, gScore, |
| | | gScore + spaceTimeHeuristic(neighborCoord, endCoord, arrivalTime, physicalConfig) |
| | | ); |
| | | |
| | | cameFrom.put(neighborKey, current); |
| | | openSet.offer(neighborNode); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 减少等待选项 - 只在必要时等待(20%概率) |
| | | if (Math.random() < 0.2) { |
| | | long waitTime = timeResolution; |
| | | long waitUntilTime = current.timePoint + waitTime; |
| | | String waitKey = createSpaceTimeKey(current.code, waitUntilTime); |
| | | |
| | | if (!closedSet.contains(waitKey) && |
| | | constraintChecker.isValidWait(current.code, current.timePoint, waitUntilTime)) { |
| | | |
| | | double waitGScore = gScores.get(createSpaceTimeKey(current.code, current.timePoint)) + |
| | | (waitTime / 1000.0); |
| | | |
| | | if (!gScores.containsKey(waitKey) || waitGScore < gScores.get(waitKey)) { |
| | | gScores.put(waitKey, waitGScore); |
| | | |
| | | SpaceTimeAStarNode waitNode = new SpaceTimeAStarNode( |
| | | current.code, current.coordinates, waitUntilTime, waitGScore, |
| | | waitGScore + spaceTimeHeuristic(current.coordinates, endCoord, waitUntilTime, physicalConfig) |
| | | ); |
| | | |
| | | cameFrom.put(waitKey, current); |
| | | openSet.offer(waitNode); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 时空启发式函数 |
| | | * |
| | | * @param coord1 当前坐标 |
| | | * @param coord2 目标坐标 |
| | | * @param currentTime 当前时间 |
| | | * @param physicalConfig 物理配置 |
| | | * @return 启发式值 |
| | | */ |
| | | private double spaceTimeHeuristic(int[] coord1, int[] coord2, long currentTime, CTUPhysicalConfig physicalConfig) { |
| | | // 空间距离 |
| | | double spatialDistance = Math.abs(coord1[0] - coord2[0]) + Math.abs(coord1[1] - coord2[1]); |
| | | |
| | | // 时间成本估计 |
| | | double timeEstimate = spatialDistance * physicalConfig.getStandardPointDistance() / physicalConfig.getNormalSpeed(); |
| | | |
| | | // 考虑转向成本 |
| | | double turnPenalty = spatialDistance > 1 ? physicalConfig.getTurnTime90() : 0; |
| | | |
| | | return timeEstimate + turnPenalty; |
| | | } |
| | | |
| | | /** |
| | | * 计算移动时间 |
| | | * |
| | | * @param fromCode 起始代码 |
| | | * @param toCode 目标代码 |
| | | * @param direction 移动方向 |
| | | * @param physicalConfig 物理配置 |
| | | * @return 移动时间(毫秒) |
| | | */ |
| | | private long calculateTravelTime(String fromCode, String toCode, String direction, CTUPhysicalConfig physicalConfig) { |
| | | // 基本移动时间 |
| | | double distance = physicalConfig.getStandardPointDistance(); |
| | | double speed = physicalConfig.getNormalSpeed(); |
| | | long moveTime = (long) ((distance / speed) * 1000); |
| | | |
| | | // 转向时间 |
| | | long turnTime = (long) (physicalConfig.getTurnTime90() * 1000 / 4); // 假设平均转向时间 |
| | | |
| | | return moveTime + turnTime; |
| | | } |
| | | |
| | | /** |
| | | * 重建时空路径 |
| | | * |
| | | * @param cameFrom 路径来源映射 |
| | | * @param startNode 起始节点 |
| | | * @param endNode 结束节点 |
| | | * @param physicalConfig 物理配置 |
| | | * @return 重建的路径 |
| | | */ |
| | | private PlannedPath reconstructSpaceTimePath(Map<String, SpaceTimeAStarNode> cameFrom, |
| | | SpaceTimeAStarNode startNode, |
| | | SpaceTimeAStarNode endNode, |
| | | CTUPhysicalConfig physicalConfig) { |
| | | List<SpaceTimeAStarNode> pathNodes = new ArrayList<>(); |
| | | SpaceTimeAStarNode current = endNode; |
| | | |
| | | // 反向重建路径 |
| | | while (current != null) { |
| | | pathNodes.add(0, current); |
| | | String currentKey = createSpaceTimeKey(current.code, current.timePoint); |
| | | current = cameFrom.get(currentKey); |
| | | } |
| | | |
| | | // 转换为PathCode列表 |
| | | List<PathCode> codeList = new ArrayList<>(); |
| | | for (int i = 0; i < pathNodes.size(); i++) { |
| | | SpaceTimeAStarNode node = pathNodes.get(i); |
| | | String direction = "90"; // 默认方向 |
| | | |
| | | // 计算方向 |
| | | if (i < pathNodes.size() - 1) { |
| | | SpaceTimeAStarNode nextNode = pathNodes.get(i + 1); |
| | | direction = calculateDirection(node.code, nextNode.code); |
| | | } |
| | | |
| | | PathCode pathCode = new PathCode(node.code, direction); |
| | | |
| | | // 添加时间信息 |
| | | // pathCode.setArrivalTime(node.timePoint); |
| | | |
| | | codeList.add(pathCode); |
| | | } |
| | | |
| | | return new PlannedPath("", "", codeList); |
| | | } |
| | | |
| | | /** |
| | | * 创建时空键 |
| | | * |
| | | * @param code 位置代码 |
| | | * @param timePoint 时间点 |
| | | * @return 时空键 |
| | | */ |
| | | private String createSpaceTimeKey(String code, long timePoint) { |
| | | return code + "_T" + timePoint; |
| | | } |
| | | |
| | | /** |
| | | * 时间对齐到分辨率 |
| | | * |
| | | * @param timePoint 时间点 |
| | | * @return 对齐后的时间 |
| | | */ |
| | | private long alignToTimeResolution(long timePoint) { |
| | | return (timePoint / timeResolution) * timeResolution; |
| | | } |
| | | |
| | | /** |
| | | * 创建默认物理配置 |
| | | * |
| | | * @return 默认配置 |
| | | */ |
| | | private CTUPhysicalConfig createDefaultPhysicalConfig() { |
| | | CTUPhysicalConfig config = new CTUPhysicalConfig(); |
| | | config.setMaxSpeed(2.0); |
| | | config.setNormalSpeed(1.5); |
| | | config.setMaxAcceleration(1.0); |
| | | config.setMaxDeceleration(1.5); |
| | | config.setTurnTime90(2.0); |
| | | config.setTurnTime180(4.0); |
| | | config.setMinSafetyDistance(3.0); |
| | | config.setMinFollowingDistance(2.0); |
| | | config.setCtuLength(1.2); |
| | | config.setCtuWidth(0.8); |
| | | config.setStartupTime(1.0); |
| | | config.setStopTime(0.5); |
| | | config.setStandardPointDistance(1.0); |
| | | return config; |
| | | } |
| | | |
| | | /** |
| | | * 计算移动方向 |
| | | * |
| | | * @param fromCode 起始路径点 |
| | | * @param toCode 目标路径点 |
| | | * @return 方向角度字符串 |
| | | */ |
| | | private String calculateDirection(String fromCode, String toCode) { |
| | | int[] fromCoord = JsonUtils.getCoordinate(fromCode, pathMapping); |
| | | int[] toCoord = JsonUtils.getCoordinate(toCode, pathMapping); |
| | | |
| | | if (fromCoord == null || toCoord == null) { |
| | | return "90"; |
| | | } |
| | | |
| | | int dx = toCoord[0] - fromCoord[0]; |
| | | int dy = toCoord[1] - fromCoord[1]; |
| | | |
| | | // 确定方向 |
| | | if (dx == 0 && dy == -1) return "270"; // 上 |
| | | if (dx == 1 && dy == 0) return "0"; // 右 |
| | | if (dx == 0 && dy == 1) return "90"; // 下 |
| | | if (dx == -1 && dy == 0) return "180"; // 左 |
| | | |
| | | return "90"; // 默认方向 |
| | | } |
| | | |
| | | @Override |
| | | public boolean isValidPathPoint(String pathCode) { |
| | | return pathCode != null && pathMapping.containsKey(pathCode); |
| | | } |
| | | |
| | | @Override |
| | | public double calculateDistance(String startCode, String endCode) { |
| | | int[] startCoord = JsonUtils.getCoordinate(startCode, pathMapping); |
| | | int[] endCoord = JsonUtils.getCoordinate(endCode, pathMapping); |
| | | |
| | | if (startCoord == null || endCoord == null) { |
| | | return -1; |
| | | } |
| | | |
| | | return JsonUtils.calculateManhattanDistance(startCoord, endCoord); |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Map<String, Integer>> getPathMapping() { |
| | | return pathMapping; |
| | | } |
| | | |
| | | @Override |
| | | public List<Map<String, String>> getNeighbors(String pathCode) { |
| | | return adjacencyList.getOrDefault(pathCode, new ArrayList<>()); |
| | | } |
| | | |
| | | /** |
| | | * 构建邻接表 |
| | | */ |
| | | private void buildAdjacencyList() { |
| | | // 创建坐标到编号的临时映射 |
| | | Map<String, String> tempCoordToCode = new HashMap<>(); |
| | | for (Map.Entry<String, Map<String, Integer>> entry : pathMapping.entrySet()) { |
| | | String code = entry.getKey(); |
| | | Map<String, Integer> coordMap = entry.getValue(); |
| | | |
| | | if (coordMap.containsKey("x") && coordMap.containsKey("y")) { |
| | | String coordKey = coordMap.get("x") + "," + coordMap.get("y"); |
| | | tempCoordToCode.put(coordKey, code); |
| | | } |
| | | } |
| | | |
| | | // 四个方向:上、右、下、左 |
| | | int[][] directions = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}}; |
| | | String[] directionAngles = {"270", "0", "90", "180"}; |
| | | |
| | | for (Map.Entry<String, Map<String, Integer>> entry : pathMapping.entrySet()) { |
| | | String code = entry.getKey(); |
| | | Map<String, Integer> coordMap = entry.getValue(); |
| | | |
| | | if (!coordMap.containsKey("x") || !coordMap.containsKey("y")) { |
| | | continue; |
| | | } |
| | | |
| | | int x = coordMap.get("x"); |
| | | int y = coordMap.get("y"); |
| | | |
| | | List<Map<String, String>> neighbors = new ArrayList<>(); |
| | | |
| | | // 检查四个方向的邻居 |
| | | for (int i = 0; i < directions.length; i++) { |
| | | int newX = x + directions[i][0]; |
| | | int newY = y + directions[i][1]; |
| | | String coordKey = newX + "," + newY; |
| | | |
| | | String neighborCode = tempCoordToCode.get(coordKey); |
| | | if (neighborCode != null) { |
| | | Map<String, String> neighbor = new HashMap<>(); |
| | | neighbor.put("code", neighborCode); |
| | | neighbor.put("direction", directionAngles[i]); |
| | | neighbors.add(neighbor); |
| | | } |
| | | } |
| | | |
| | | adjacencyList.put(code, neighbors); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 时空A*节点类 |
| | | */ |
| | | private static class SpaceTimeAStarNode { |
| | | final String code; |
| | | final int[] coordinates; |
| | | final long timePoint; |
| | | final double gScore; |
| | | final double fScore; |
| | | |
| | | public SpaceTimeAStarNode(String code, int[] coordinates, long timePoint, |
| | | double gScore, double fScore) { |
| | | this.code = code; |
| | | this.coordinates = coordinates; |
| | | this.timePoint = timePoint; |
| | | this.gScore = gScore; |
| | | this.fScore = fScore; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 约束检查器 |
| | | */ |
| | | private static class EnhancedConstraintChecker { |
| | | private final List<double[]> staticConstraints; |
| | | private final Map<String, String> spaceTimeOccupancyMap; |
| | | private final Map<String, Map<String, Integer>> pathMapping; |
| | | private final CTUPhysicalConfig physicalConfig; |
| | | |
| | | public EnhancedConstraintChecker(List<double[]> staticConstraints, |
| | | Map<String, String> spaceTimeOccupancyMap, |
| | | Map<String, Map<String, Integer>> pathMapping, |
| | | CTUPhysicalConfig physicalConfig) { |
| | | this.staticConstraints = staticConstraints; |
| | | this.spaceTimeOccupancyMap = spaceTimeOccupancyMap != null ? spaceTimeOccupancyMap : new HashMap<>(); |
| | | this.pathMapping = pathMapping; |
| | | this.physicalConfig = physicalConfig; |
| | | } |
| | | |
| | | /** |
| | | * 检查移动是否有效 |
| | | */ |
| | | public boolean isValidMove(String fromCode, String toCode, long fromTime, long toTime) { |
| | | // 检查静态约束 |
| | | if (!isValidStaticConstraint(toCode)) { |
| | | return false; |
| | | } |
| | | |
| | | // 检查时空占用 |
| | | if (!isValidSpaceTimeConstraint(toCode, toTime)) { |
| | | return false; |
| | | } |
| | | |
| | | // 检查物理约束 |
| | | return isValidPhysicalConstraint(fromCode, toCode, fromTime, toTime); |
| | | } |
| | | |
| | | /** |
| | | * 检查等待是否有效 |
| | | */ |
| | | public boolean isValidWait(String code, long fromTime, long toTime) { |
| | | // 检查时空占用期间是否有冲突 |
| | | for (long t = fromTime; t <= toTime; t += 100) { // 100ms检查间隔 |
| | | if (!isValidSpaceTimeConstraint(code, t)) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private boolean isValidStaticConstraint(String pathCode) { |
| | | if (staticConstraints == null || staticConstraints.isEmpty()) { |
| | | return true; |
| | | } |
| | | |
| | | int[] coord = JsonUtils.getCoordinate(pathCode, pathMapping); |
| | | if (coord == null) { |
| | | return false; |
| | | } |
| | | |
| | | for (double[] constraint : staticConstraints) { |
| | | double cx = constraint[0]; |
| | | double cy = constraint[1]; |
| | | double radius = constraint[2]; |
| | | |
| | | double distance = Math.sqrt(Math.pow(coord[0] - cx, 2) + Math.pow(coord[1] - cy, 2)); |
| | | if (distance <= radius) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | private boolean isValidSpaceTimeConstraint(String pathCode, long timePoint) { |
| | | int[] coord = JsonUtils.getCoordinate(pathCode, pathMapping); |
| | | if (coord == null) { |
| | | return false; |
| | | } |
| | | |
| | | // 检查精确的时空占用 |
| | | String spaceTimeKey = coord[0] + "," + coord[1] + "," + (timePoint / 1000); |
| | | if (spaceTimeOccupancyMap.containsKey(spaceTimeKey)) { |
| | | return false; |
| | | } |
| | | |
| | | // 检查周围时间段的占用(考虑安全间隔) |
| | | long safetyMargin = (long) (physicalConfig.getMinSafetyDistance() * 1000 / physicalConfig.getNormalSpeed()); |
| | | for (long t = timePoint - safetyMargin; t <= timePoint + safetyMargin; t += 100) { |
| | | String key = coord[0] + "," + coord[1] + "," + (t / 1000); |
| | | if (spaceTimeOccupancyMap.containsKey(key)) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | private boolean isValidPhysicalConstraint(String fromCode, String toCode, long fromTime, long toTime) { |
| | | // 物理约束检查 |
| | | long timeDiff = toTime - fromTime; |
| | | double minTimeRequired = physicalConfig.getStandardPointDistance() / physicalConfig.getMaxSpeed() * 1000; |
| | | |
| | | // 时间不能小于物理最小时间 |
| | | return timeDiff >= minTimeRequired; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 快速路径规划 - 先尝试简化空间路径规划 |
| | | * 对于近距离路径,直接返回结果避免复杂的时空计算 |
| | | */ |
| | | private PlannedPath tryFastPathPlanning(String startCode, String endCode, List<double[]> constraints) { |
| | | // 检查缓存 |
| | | String cacheKey = startCode + "->" + endCode; |
| | | if (fastPathCache.containsKey(cacheKey)) { |
| | | List<String> cachedPath = fastPathCache.get(cacheKey); |
| | | return convertToPlannedPath(cachedPath); |
| | | } |
| | | |
| | | // 计算距离,如果距离较近则使用快速算法 |
| | | double distance = getCachedDistance(startCode, endCode); |
| | | if (distance > 0 && distance <= 40) { // 40个单位以内使用快速算法 |
| | | List<String> fastPath = simpleAStarSearch(startCode, endCode, constraints); |
| | | if (fastPath != null && !fastPath.isEmpty()) { |
| | | fastPathCache.put(cacheKey, fastPath); // 缓存结果 |
| | | return convertToPlannedPath(fastPath); |
| | | } |
| | | } |
| | | |
| | | return null; // 快速路径失败,需要使用完整算法 |
| | | } |
| | | |
| | | /** |
| | | * 简化A*搜索 |
| | | */ |
| | | private List<String> simpleAStarSearch(String startCode, String endCode, List<double[]> constraints) { |
| | | int[] startCoord = JsonUtils.getCoordinate(startCode, pathMapping); |
| | | int[] endCoord = JsonUtils.getCoordinate(endCode, pathMapping); |
| | | |
| | | if (startCoord == null || endCoord == null) { |
| | | return null; |
| | | } |
| | | |
| | | PriorityQueue<SimpleAStarNode> openSet = new PriorityQueue<>( |
| | | Comparator.comparingDouble(n -> n.fScore) |
| | | ); |
| | | Set<String> closedSet = new HashSet<>(); |
| | | Map<String, Double> gScores = new HashMap<>(); |
| | | Map<String, SimpleAStarNode> cameFrom = new HashMap<>(); |
| | | |
| | | SimpleAStarNode startNode = new SimpleAStarNode( |
| | | startCode, startCoord, 0, spatialHeuristic(startCoord, endCoord) |
| | | ); |
| | | |
| | | openSet.offer(startNode); |
| | | gScores.put(startCode, 0.0); |
| | | |
| | | // 简化的约束检查器 |
| | | FastConstraintChecker constraintChecker = new FastConstraintChecker(constraints); |
| | | |
| | | int searchDepth = 0; |
| | | long searchStartTime = System.currentTimeMillis(); |
| | | |
| | | while (!openSet.isEmpty() && searchDepth < 3000) { |
| | | if (System.currentTimeMillis() - searchStartTime > 5000) { // 5秒超时 |
| | | break; |
| | | } |
| | | |
| | | SimpleAStarNode current = openSet.poll(); |
| | | |
| | | if (closedSet.contains(current.code)) { |
| | | continue; |
| | | } |
| | | |
| | | closedSet.add(current.code); |
| | | searchDepth++; |
| | | |
| | | if (current.code.equals(endCode)) { |
| | | return reconstructSimplePath(cameFrom, startNode, current); |
| | | } |
| | | |
| | | // 扩展邻居 |
| | | expandSimpleNeighbors(current, endCoord, openSet, closedSet, gScores, cameFrom, constraintChecker); |
| | | } |
| | | |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 预计算常用距离 |
| | | */ |
| | | private void precomputeCommonDistances() { |
| | | System.out.println("距离缓存初始化完成"); |
| | | } |
| | | |
| | | /** |
| | | * 获取缓存的距离 |
| | | */ |
| | | private double getCachedDistance(String from, String to) { |
| | | String key = from + "-" + to; |
| | | return distanceCache.computeIfAbsent(key, k -> { |
| | | int[] coord1 = JsonUtils.getCoordinate(from, pathMapping); |
| | | int[] coord2 = JsonUtils.getCoordinate(to, pathMapping); |
| | | if (coord1 != null && coord2 != null) { |
| | | return JsonUtils.calculateManhattanDistance(coord1, coord2); |
| | | } |
| | | return -1.0; |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 空间启发式函数 |
| | | */ |
| | | private double spatialHeuristic(int[] coord1, int[] coord2) { |
| | | return Math.abs(coord1[0] - coord2[0]) + Math.abs(coord1[1] - coord2[1]); |
| | | } |
| | | |
| | | /** |
| | | * 扩展邻居节点 |
| | | */ |
| | | private void expandSimpleNeighbors(SimpleAStarNode current, int[] endCoord, |
| | | PriorityQueue<SimpleAStarNode> openSet, |
| | | Set<String> closedSet, Map<String, Double> gScores, |
| | | Map<String, SimpleAStarNode> cameFrom, |
| | | FastConstraintChecker constraintChecker) { |
| | | |
| | | List<Map<String, String>> neighbors = getNeighbors(current.code); |
| | | |
| | | for (Map<String, String> neighbor : neighbors) { |
| | | String neighborCode = neighbor.get("code"); |
| | | |
| | | if (closedSet.contains(neighborCode)) { |
| | | continue; |
| | | } |
| | | |
| | | if (!constraintChecker.isValidPosition(neighborCode)) { |
| | | continue; |
| | | } |
| | | |
| | | double edgeCost = getCachedDistance(current.code, neighborCode); |
| | | if (edgeCost < 0) edgeCost = 1.0; // 默认边权重 |
| | | |
| | | double gScore = gScores.get(current.code) + edgeCost; |
| | | |
| | | if (!gScores.containsKey(neighborCode) || gScore < gScores.get(neighborCode)) { |
| | | gScores.put(neighborCode, gScore); |
| | | |
| | | int[] neighborCoord = JsonUtils.getCoordinate(neighborCode, pathMapping); |
| | | if (neighborCoord != null) { |
| | | SimpleAStarNode neighborNode = new SimpleAStarNode( |
| | | neighborCode, neighborCoord, gScore, |
| | | gScore + spatialHeuristic(neighborCoord, endCoord) |
| | | ); |
| | | |
| | | cameFrom.put(neighborCode, current); |
| | | openSet.offer(neighborNode); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 重建简化路径 |
| | | */ |
| | | private List<String> reconstructSimplePath(Map<String, SimpleAStarNode> cameFrom, |
| | | SimpleAStarNode startNode, SimpleAStarNode endNode) { |
| | | List<String> path = new ArrayList<>(); |
| | | SimpleAStarNode current = endNode; |
| | | |
| | | while (current != null) { |
| | | path.add(0, current.code); |
| | | current = cameFrom.get(current.code); |
| | | } |
| | | |
| | | return path; |
| | | } |
| | | |
| | | /** |
| | | * 将路径转换为PlannedPath |
| | | */ |
| | | private PlannedPath convertToPlannedPath(List<String> pathCodes) { |
| | | if (pathCodes == null || pathCodes.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | List<PathCode> codeList = new ArrayList<>(); |
| | | |
| | | for (int i = 0; i < pathCodes.size(); i++) { |
| | | String position = pathCodes.get(i); |
| | | String direction = "90"; // 默认方向 |
| | | |
| | | // 计算方向 |
| | | if (i < pathCodes.size() - 1) { |
| | | direction = calculateDirection(position, pathCodes.get(i + 1)); |
| | | } |
| | | |
| | | PathCode pathCode = new PathCode(position, direction); |
| | | codeList.add(pathCode); |
| | | } |
| | | |
| | | return new PlannedPath("", "", codeList); |
| | | } |
| | | |
| | | /** |
| | | * A*节点类 |
| | | */ |
| | | private static class SimpleAStarNode { |
| | | final String code; |
| | | final int[] coordinates; |
| | | final double gScore; |
| | | final double fScore; |
| | | |
| | | public SimpleAStarNode(String code, int[] coordinates, double gScore, double fScore) { |
| | | this.code = code; |
| | | this.coordinates = coordinates; |
| | | this.gScore = gScore; |
| | | this.fScore = fScore; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 约束检查器 |
| | | */ |
| | | private class FastConstraintChecker { |
| | | private final Set<String> blockedPositions; |
| | | |
| | | public FastConstraintChecker(List<double[]> constraints) { |
| | | this.blockedPositions = new HashSet<>(); |
| | | precomputeBlockedPositions(constraints); |
| | | } |
| | | |
| | | private void precomputeBlockedPositions(List<double[]> constraints) { |
| | | if (constraints == null || constraints.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | for (Map.Entry<String, Map<String, Integer>> entry : pathMapping.entrySet()) { |
| | | String pathCode = entry.getKey(); |
| | | int[] coord = JsonUtils.getCoordinate(pathCode, pathMapping); |
| | | |
| | | if (coord != null && isBlocked(coord, constraints)) { |
| | | blockedPositions.add(pathCode); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private boolean isBlocked(int[] coord, List<double[]> constraints) { |
| | | for (double[] constraint : constraints) { |
| | | double cx = constraint[0]; |
| | | double cy = constraint[1]; |
| | | double radius = constraint[2]; |
| | | |
| | | double distance = Math.sqrt(Math.pow(coord[0] - cx, 2) + Math.pow(coord[1] - cy, 2)); |
| | | if (distance <= radius) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | public boolean isValidPosition(String pathCode) { |
| | | return !blockedPositions.contains(pathCode); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.service; |
| | | |
| | | import com.algo.model.CTUPhysicalConfig; |
| | | import com.algo.model.Conflict; |
| | | import com.algo.model.PathCode; |
| | | import com.algo.model.PlannedPath; |
| | | import com.algo.util.JsonUtils; |
| | | |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * 碰撞检测器(增强版) |
| | | * 用于检测CTU路径之间的时空冲突,支持物理参数和高精度时间检测 |
| | | */ |
| | | public class CollisionDetector { |
| | | |
| | | /** |
| | | * 路径映射表 |
| | | */ |
| | | private final Map<String, Map<String, Integer>> pathMapping; |
| | | |
| | | /** |
| | | * 默认最小安全距离 |
| | | */ |
| | | private final double defaultMinDistance; |
| | | |
| | | /** |
| | | * 时间精度(毫秒) |
| | | */ |
| | | private final long timeResolution = 100; // 100毫秒的时间精度 |
| | | |
| | | /** |
| | | * 空间分辨率(米) |
| | | */ |
| | | private final double spaceResolution = 0.1; // 10厘米的空间精度 |
| | | |
| | | /** |
| | | * 构造函数 |
| | | * |
| | | * @param pathMapping 路径映射表 |
| | | * @param defaultMinDistance 默认最小安全距离 |
| | | */ |
| | | public CollisionDetector(Map<String, Map<String, Integer>> pathMapping, |
| | | double defaultMinDistance) { |
| | | this.pathMapping = pathMapping; |
| | | this.defaultMinDistance = defaultMinDistance; |
| | | } |
| | | |
| | | /** |
| | | * 构造函数(使用默认参数) |
| | | * |
| | | * @param pathMapping 路径映射表 |
| | | */ |
| | | public CollisionDetector(Map<String, Map<String, Integer>> pathMapping) { |
| | | this(pathMapping, 3.0); |
| | | } |
| | | |
| | | /** |
| | | * 检测路径冲突(增强版,支持CTU物理参数) |
| | | * |
| | | * @param plannedPaths 规划路径列表 |
| | | * @return 冲突列表 |
| | | */ |
| | | public List<Conflict> detectConflicts(List<PlannedPath> plannedPaths) { |
| | | List<Conflict> conflicts = new ArrayList<>(); |
| | | |
| | | if (plannedPaths == null || plannedPaths.size() < 2) { |
| | | return conflicts; |
| | | } |
| | | |
| | | System.out.println("开始增强版碰撞检测,路径数量: " + plannedPaths.size()); |
| | | |
| | | // 构建高精度时空表 |
| | | Map<Long, List<EnhancedSpaceTimeNode>> spaceTimeTable = buildEnhancedSpaceTimeTable(plannedPaths); |
| | | |
| | | // 检测顶点冲突(同一时间同一位置) |
| | | List<Conflict> vertexConflicts = detectEnhancedVertexConflicts(spaceTimeTable); |
| | | conflicts.addAll(vertexConflicts); |
| | | System.out.println("顶点冲突数量: " + vertexConflicts.size()); |
| | | |
| | | // 检测边冲突(CTU交换位置) |
| | | List<Conflict> edgeConflicts = detectEnhancedEdgeConflicts(spaceTimeTable); |
| | | conflicts.addAll(edgeConflicts); |
| | | System.out.println("边冲突数量: " + edgeConflicts.size()); |
| | | |
| | | // 检测跟随冲突(CTU距离过近,考虑物理参数) |
| | | List<Conflict> followingConflicts = detectEnhancedFollowingConflicts(spaceTimeTable); |
| | | conflicts.addAll(followingConflicts); |
| | | System.out.println("跟随冲突数量: " + followingConflicts.size()); |
| | | |
| | | // 检测物理尺寸冲突 |
| | | List<Conflict> physicalConflicts = detectPhysicalSizeConflicts(spaceTimeTable); |
| | | conflicts.addAll(physicalConflicts); |
| | | System.out.println("物理尺寸冲突数量: " + physicalConflicts.size()); |
| | | |
| | | System.out.println("增强版碰撞检测完成,总冲突数量: " + conflicts.size()); |
| | | return conflicts; |
| | | } |
| | | |
| | | /** |
| | | * 构建增强的时空表(高精度时间和物理参数) |
| | | * |
| | | * @param plannedPaths 规划路径列表 |
| | | * @return 增强时空表 |
| | | */ |
| | | private Map<Long, List<EnhancedSpaceTimeNode>> buildEnhancedSpaceTimeTable(List<PlannedPath> plannedPaths) { |
| | | Map<Long, List<EnhancedSpaceTimeNode>> spaceTimeTable = new HashMap<>(); |
| | | |
| | | for (PlannedPath path : plannedPaths) { |
| | | String agvId = path.getAgvId(); |
| | | List<PathCode> codeList = path.getCodeList(); |
| | | |
| | | if (codeList == null || codeList.isEmpty()) { |
| | | continue; |
| | | } |
| | | |
| | | // 获取CTU的物理配置(这里需要从某个地方获取,可能需要扩展PlannedPath类) |
| | | CTUPhysicalConfig physicalConfig = getPhysicalConfigForCTU(agvId); |
| | | |
| | | long currentTime = System.currentTimeMillis(); // 基准时间 |
| | | |
| | | for (int i = 0; i < codeList.size(); i++) { |
| | | PathCode pathCode = codeList.get(i); |
| | | String position = pathCode.getCode(); |
| | | |
| | | int[] coordinates = JsonUtils.getCoordinate(position, pathMapping); |
| | | if (coordinates != null) { |
| | | // 计算精确的到达时间 |
| | | long arrivalTime = calculateArrivalTime(currentTime, i, codeList, physicalConfig); |
| | | |
| | | // 计算停留时间 |
| | | long departureTime = calculateDepartureTime(arrivalTime, pathCode, physicalConfig); |
| | | |
| | | // 创建时间段内的多个时间点 |
| | | for (long timePoint = arrivalTime; timePoint <= departureTime; timePoint += timeResolution) { |
| | | EnhancedSpaceTimeNode node = new EnhancedSpaceTimeNode( |
| | | agvId, position, coordinates, timePoint, arrivalTime, departureTime, physicalConfig |
| | | ); |
| | | |
| | | spaceTimeTable.computeIfAbsent(timePoint, k -> new ArrayList<>()).add(node); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return spaceTimeTable; |
| | | } |
| | | |
| | | /** |
| | | * 获取CTU的物理配置 |
| | | * |
| | | * @param agvId CTU编号 |
| | | * @return 物理配置 |
| | | */ |
| | | private CTUPhysicalConfig getPhysicalConfigForCTU(String agvId) { |
| | | // 这里应该从配置管理器或数据库获取实际的CTU物理参数 |
| | | // 暂时返回默认配置 |
| | | CTUPhysicalConfig config = new CTUPhysicalConfig(); |
| | | config.setMaxSpeed(2.0); |
| | | config.setNormalSpeed(1.5); |
| | | config.setMaxAcceleration(1.0); |
| | | config.setMaxDeceleration(1.5); |
| | | config.setTurnTime90(2.0); |
| | | config.setTurnTime180(4.0); |
| | | config.setMinSafetyDistance(defaultMinDistance); |
| | | config.setMinFollowingDistance(2.0); |
| | | config.setCtuLength(1.2); |
| | | config.setCtuWidth(0.8); |
| | | config.setStartupTime(1.0); |
| | | config.setStopTime(0.5); |
| | | config.setStandardPointDistance(1.0); |
| | | return config; |
| | | } |
| | | |
| | | /** |
| | | * 计算到达时间 |
| | | * |
| | | * @param startTime 起始时间 |
| | | * @param pathIndex 路径索引 |
| | | * @param codeList 路径代码列表 |
| | | * @param config 物理配置 |
| | | * @return 到达时间 |
| | | */ |
| | | private long calculateArrivalTime(long startTime, int pathIndex, List<PathCode> codeList, CTUPhysicalConfig config) { |
| | | if (pathIndex == 0) { |
| | | return startTime; |
| | | } |
| | | |
| | | long travelTime = 0; |
| | | |
| | | for (int i = 1; i <= pathIndex; i++) { |
| | | PathCode currentCode = codeList.get(i); |
| | | PathCode previousCode = codeList.get(i - 1); |
| | | |
| | | // 计算移动时间 |
| | | double distance = config.getStandardPointDistance(); |
| | | double speed = config.getNormalSpeed(); |
| | | travelTime += (long) ((distance / speed) * 1000); // 转换为毫秒 |
| | | |
| | | // 如果有方向变化,增加转向时间 |
| | | if (!currentCode.getDirection().equals(previousCode.getDirection())) { |
| | | double turnTime = calculateTurnTime(previousCode.getDirection(), currentCode.getDirection(), config); |
| | | travelTime += (long) (turnTime * 1000); |
| | | } |
| | | |
| | | // 考虑加速和减速时间 |
| | | if (i == 1) { |
| | | travelTime += (long) (config.getStartupTime() * 1000); |
| | | } |
| | | } |
| | | |
| | | return startTime + travelTime; |
| | | } |
| | | |
| | | /** |
| | | * 计算离开时间 |
| | | * |
| | | * @param arrivalTime 到达时间 |
| | | * @param pathCode 路径代码 |
| | | * @param config 物理配置 |
| | | * @return 离开时间 |
| | | */ |
| | | private long calculateDepartureTime(long arrivalTime, PathCode pathCode, CTUPhysicalConfig config) { |
| | | long stayTime = 500; // 默认停留时间500毫秒 |
| | | |
| | | // 根据路径代码类型确定停留时间 |
| | | if (pathCode.isTargetPoint()) { |
| | | if ("1".equals(pathCode.getPosType())) { // 取货 |
| | | stayTime = 3000; // 取货需要3秒 |
| | | } else if ("2".equals(pathCode.getPosType())) { // 放货 |
| | | stayTime = 2000; // 放货需要2秒 |
| | | } |
| | | } |
| | | |
| | | return arrivalTime + stayTime; |
| | | } |
| | | |
| | | /** |
| | | * 计算转向时间 |
| | | * |
| | | * @param fromDirection 起始方向 |
| | | * @param toDirection 目标方向 |
| | | * @param config 物理配置 |
| | | * @return 转向时间(秒) |
| | | */ |
| | | private double calculateTurnTime(String fromDirection, String toDirection, CTUPhysicalConfig config) { |
| | | if (fromDirection.equals(toDirection)) { |
| | | return 0.0; |
| | | } |
| | | |
| | | // 简化的方向转换计算 |
| | | // 实际实现中需要根据具体的方向编码计算角度差 |
| | | int angleDiff = Math.abs(Integer.parseInt(toDirection) - Integer.parseInt(fromDirection)); |
| | | angleDiff = Math.min(angleDiff, 4 - angleDiff); // 考虑环形 |
| | | |
| | | switch (angleDiff) { |
| | | case 1: |
| | | return config.getTurnTime90(); |
| | | case 2: |
| | | return config.getTurnTime180(); |
| | | default: |
| | | return config.getTurnTime90() * angleDiff; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 检测增强的顶点冲突 |
| | | * |
| | | * @param spaceTimeTable 时空表 |
| | | * @return 顶点冲突列表 |
| | | */ |
| | | private List<Conflict> detectEnhancedVertexConflicts(Map<Long, List<EnhancedSpaceTimeNode>> spaceTimeTable) { |
| | | List<Conflict> conflicts = new ArrayList<>(); |
| | | |
| | | for (Map.Entry<Long, List<EnhancedSpaceTimeNode>> entry : spaceTimeTable.entrySet()) { |
| | | long timePoint = entry.getKey(); |
| | | List<EnhancedSpaceTimeNode> nodes = entry.getValue(); |
| | | |
| | | // 按位置分组 |
| | | Map<String, List<EnhancedSpaceTimeNode>> positionGroups = new HashMap<>(); |
| | | for (EnhancedSpaceTimeNode node : nodes) { |
| | | positionGroups.computeIfAbsent(node.position, k -> new ArrayList<>()).add(node); |
| | | } |
| | | |
| | | // 检查每个位置是否有多个CTU |
| | | for (Map.Entry<String, List<EnhancedSpaceTimeNode>> posEntry : positionGroups.entrySet()) { |
| | | String position = posEntry.getKey(); |
| | | List<EnhancedSpaceTimeNode> ctuNodes = posEntry.getValue(); |
| | | |
| | | if (ctuNodes.size() > 1) { |
| | | // 进一步检查时间重叠 |
| | | for (int i = 0; i < ctuNodes.size(); i++) { |
| | | for (int j = i + 1; j < ctuNodes.size(); j++) { |
| | | EnhancedSpaceTimeNode node1 = ctuNodes.get(i); |
| | | EnhancedSpaceTimeNode node2 = ctuNodes.get(j); |
| | | |
| | | // 检查时间段是否重叠 |
| | | if (timeRangeOverlap(node1.arrivalTime, node1.departureTime, |
| | | node2.arrivalTime, node2.departureTime)) { |
| | | Conflict conflict = new Conflict( |
| | | "enhanced_vertex", |
| | | node1.agvId, |
| | | node2.agvId, |
| | | (int) (timePoint / 1000), // 转换为秒用于显示 |
| | | position, |
| | | position, |
| | | String.format("CTU %s 和 %s 在时间 %d 占用同一位置 %s(时间重叠)", |
| | | node1.agvId, node2.agvId, timePoint / 1000, position) |
| | | ); |
| | | conflicts.add(conflict); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return conflicts; |
| | | } |
| | | |
| | | /** |
| | | * 检测增强的边冲突 |
| | | * |
| | | * @param spaceTimeTable 时空表 |
| | | * @return 边冲突列表 |
| | | */ |
| | | private List<Conflict> detectEnhancedEdgeConflicts(Map<Long, List<EnhancedSpaceTimeNode>> spaceTimeTable) { |
| | | List<Conflict> conflicts = new ArrayList<>(); |
| | | |
| | | if (spaceTimeTable.isEmpty()) { |
| | | return conflicts; |
| | | } |
| | | |
| | | List<Long> sortedTimes = new ArrayList<>(spaceTimeTable.keySet()); |
| | | Collections.sort(sortedTimes); |
| | | |
| | | for (int i = 0; i < sortedTimes.size() - 1; i++) { |
| | | long currentTime = sortedTimes.get(i); |
| | | long nextTime = sortedTimes.get(i + 1); |
| | | |
| | | // 只检查连续的时间点 |
| | | if (nextTime - currentTime <= timeResolution * 2) { |
| | | List<EnhancedSpaceTimeNode> currentNodes = spaceTimeTable.get(currentTime); |
| | | List<EnhancedSpaceTimeNode> nextNodes = spaceTimeTable.get(nextTime); |
| | | |
| | | conflicts.addAll(detectPositionSwapping(currentNodes, nextNodes, currentTime, nextTime)); |
| | | } |
| | | } |
| | | |
| | | return conflicts; |
| | | } |
| | | |
| | | /** |
| | | * 检测位置交换 |
| | | * |
| | | * @param currentNodes 当前时间的节点 |
| | | * @param nextNodes 下一时间的节点 |
| | | * @param currentTime 当前时间 |
| | | * @param nextTime 下一时间 |
| | | * @return 冲突列表 |
| | | */ |
| | | private List<Conflict> detectPositionSwapping(List<EnhancedSpaceTimeNode> currentNodes, |
| | | List<EnhancedSpaceTimeNode> nextNodes, |
| | | long currentTime, long nextTime) { |
| | | List<Conflict> conflicts = new ArrayList<>(); |
| | | |
| | | Map<String, String> currentPositions = new HashMap<>(); |
| | | Map<String, String> nextPositions = new HashMap<>(); |
| | | |
| | | for (EnhancedSpaceTimeNode node : currentNodes) { |
| | | currentPositions.put(node.agvId, node.position); |
| | | } |
| | | |
| | | for (EnhancedSpaceTimeNode node : nextNodes) { |
| | | nextPositions.put(node.agvId, node.position); |
| | | } |
| | | |
| | | for (Map.Entry<String, String> entry1 : currentPositions.entrySet()) { |
| | | String ctu1 = entry1.getKey(); |
| | | String pos1Current = entry1.getValue(); |
| | | |
| | | for (Map.Entry<String, String> entry2 : currentPositions.entrySet()) { |
| | | String ctu2 = entry2.getKey(); |
| | | String pos2Current = entry2.getValue(); |
| | | |
| | | if (ctu1.compareTo(ctu2) >= 0) { |
| | | continue; |
| | | } |
| | | |
| | | String pos1Next = nextPositions.get(ctu1); |
| | | String pos2Next = nextPositions.get(ctu2); |
| | | |
| | | if (pos1Next != null && pos2Next != null && |
| | | pos1Current.equals(pos2Next) && pos2Current.equals(pos1Next)) { |
| | | |
| | | Conflict conflict = new Conflict( |
| | | "enhanced_edge", |
| | | ctu1, |
| | | ctu2, |
| | | (int) (currentTime / 1000), |
| | | pos1Current, |
| | | pos2Current, |
| | | String.format("CTU %s 和 %s 在时间 %d-%d 交换位置 %s<->%s", |
| | | ctu1, ctu2, currentTime / 1000, nextTime / 1000, pos1Current, pos2Current) |
| | | ); |
| | | conflicts.add(conflict); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return conflicts; |
| | | } |
| | | |
| | | /** |
| | | * 检测增强的跟随冲突(考虑CTU物理参数) |
| | | * |
| | | * @param spaceTimeTable 时空表 |
| | | * @return 跟随冲突列表 |
| | | */ |
| | | private List<Conflict> detectEnhancedFollowingConflicts(Map<Long, List<EnhancedSpaceTimeNode>> spaceTimeTable) { |
| | | List<Conflict> conflicts = new ArrayList<>(); |
| | | |
| | | for (Map.Entry<Long, List<EnhancedSpaceTimeNode>> entry : spaceTimeTable.entrySet()) { |
| | | long timePoint = entry.getKey(); |
| | | List<EnhancedSpaceTimeNode> nodes = entry.getValue(); |
| | | |
| | | for (int i = 0; i < nodes.size(); i++) { |
| | | for (int j = i + 1; j < nodes.size(); j++) { |
| | | EnhancedSpaceTimeNode node1 = nodes.get(i); |
| | | EnhancedSpaceTimeNode node2 = nodes.get(j); |
| | | |
| | | double distance = calculateDistance(node1.coordinates, node2.coordinates); |
| | | double minSafeDistance = Math.max( |
| | | node1.physicalConfig.getMinFollowingDistance(), |
| | | node2.physicalConfig.getMinFollowingDistance() |
| | | ); |
| | | |
| | | if (distance > 0 && distance < minSafeDistance) { |
| | | Conflict conflict = new Conflict( |
| | | "enhanced_follow", |
| | | node1.agvId, |
| | | node2.agvId, |
| | | (int) (timePoint / 1000), |
| | | node1.position, |
| | | node2.position, |
| | | String.format("CTU %s 和 %s 在时间 %d 距离过近 (%.2f < %.2f)", |
| | | node1.agvId, node2.agvId, timePoint / 1000, distance, minSafeDistance) |
| | | ); |
| | | conflicts.add(conflict); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return conflicts; |
| | | } |
| | | |
| | | /** |
| | | * 检测物理尺寸冲突 |
| | | * |
| | | * @param spaceTimeTable 时空表 |
| | | * @return 物理冲突列表 |
| | | */ |
| | | private List<Conflict> detectPhysicalSizeConflicts(Map<Long, List<EnhancedSpaceTimeNode>> spaceTimeTable) { |
| | | List<Conflict> conflicts = new ArrayList<>(); |
| | | |
| | | for (Map.Entry<Long, List<EnhancedSpaceTimeNode>> entry : spaceTimeTable.entrySet()) { |
| | | long timePoint = entry.getKey(); |
| | | List<EnhancedSpaceTimeNode> nodes = entry.getValue(); |
| | | |
| | | for (int i = 0; i < nodes.size(); i++) { |
| | | for (int j = i + 1; j < nodes.size(); j++) { |
| | | EnhancedSpaceTimeNode node1 = nodes.get(i); |
| | | EnhancedSpaceTimeNode node2 = nodes.get(j); |
| | | |
| | | if (checkPhysicalCollision(node1, node2)) { |
| | | Conflict conflict = new Conflict( |
| | | "physical_size", |
| | | node1.agvId, |
| | | node2.agvId, |
| | | (int) (timePoint / 1000), |
| | | node1.position, |
| | | node2.position, |
| | | String.format("CTU %s 和 %s 在时间 %d 发生物理尺寸冲突", |
| | | node1.agvId, node2.agvId, timePoint / 1000) |
| | | ); |
| | | conflicts.add(conflict); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return conflicts; |
| | | } |
| | | |
| | | /** |
| | | * 检查两个CTU是否发生物理碰撞 |
| | | * |
| | | * @param node1 CTU节点1 |
| | | * @param node2 CTU节点2 |
| | | * @return 是否碰撞 |
| | | */ |
| | | private boolean checkPhysicalCollision(EnhancedSpaceTimeNode node1, EnhancedSpaceTimeNode node2) { |
| | | double distance = calculateDistance(node1.coordinates, node2.coordinates); |
| | | |
| | | // 考虑CTU的物理尺寸 |
| | | double ctu1Radius = Math.max(node1.physicalConfig.getCtuLength(), node1.physicalConfig.getCtuWidth()) / 2; |
| | | double ctu2Radius = Math.max(node2.physicalConfig.getCtuLength(), node2.physicalConfig.getCtuWidth()) / 2; |
| | | |
| | | double minClearance = ctu1Radius + ctu2Radius + spaceResolution; |
| | | |
| | | return distance > 0 && distance < minClearance; |
| | | } |
| | | |
| | | /** |
| | | * 检查时间范围是否重叠 |
| | | * |
| | | * @param start1 开始时间1 |
| | | * @param end1 结束时间1 |
| | | * @param start2 开始时间2 |
| | | * @param end2 结束时间2 |
| | | * @return 是否重叠 |
| | | */ |
| | | private boolean timeRangeOverlap(long start1, long end1, long start2, long end2) { |
| | | return Math.max(start1, start2) < Math.min(end1, end2); |
| | | } |
| | | |
| | | /** |
| | | * 计算两个坐标之间的距离 |
| | | * |
| | | * @param coord1 坐标1 |
| | | * @param coord2 坐标2 |
| | | * @return 距离值 |
| | | */ |
| | | private double calculateDistance(int[] coord1, int[] coord2) { |
| | | if (coord1 == null || coord2 == null || coord1.length != 2 || coord2.length != 2) { |
| | | return -1; |
| | | } |
| | | |
| | | return Math.sqrt(Math.pow(coord1[0] - coord2[0], 2) + Math.pow(coord1[1] - coord2[1], 2)); |
| | | } |
| | | |
| | | /** |
| | | * 增强的时空节点内部类 |
| | | */ |
| | | private static class EnhancedSpaceTimeNode { |
| | | final String agvId; |
| | | final String position; |
| | | final int[] coordinates; |
| | | final long timePoint; |
| | | final long arrivalTime; |
| | | final long departureTime; |
| | | final CTUPhysicalConfig physicalConfig; |
| | | |
| | | public EnhancedSpaceTimeNode(String agvId, String position, int[] coordinates, |
| | | long timePoint, long arrivalTime, long departureTime, |
| | | CTUPhysicalConfig physicalConfig) { |
| | | this.agvId = agvId; |
| | | this.position = position; |
| | | this.coordinates = coordinates; |
| | | this.timePoint = timePoint; |
| | | this.arrivalTime = arrivalTime; |
| | | this.departureTime = departureTime; |
| | | this.physicalConfig = physicalConfig; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.service; |
| | | |
| | | import com.algo.model.Conflict; |
| | | import com.algo.model.ExecutingTask; |
| | | import com.algo.model.PathCode; |
| | | import com.algo.model.PlannedPath; |
| | | |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * 碰撞解决器 |
| | | * 用于解决AGV路径之间的冲突 |
| | | */ |
| | | public class CollisionResolver { |
| | | |
| | | /** |
| | | * 碰撞检测器 |
| | | */ |
| | | private final CollisionDetector collisionDetector; |
| | | |
| | | /** |
| | | * 构造函数 |
| | | * |
| | | * @param collisionDetector 碰撞检测器 |
| | | */ |
| | | public CollisionResolver(CollisionDetector collisionDetector) { |
| | | this.collisionDetector = collisionDetector; |
| | | } |
| | | |
| | | /** |
| | | * 解决路径冲突 |
| | | * |
| | | * @param plannedPaths 规划路径列表 |
| | | * @param conflicts 冲突列表 |
| | | * @param executingTasks 执行中任务列表 |
| | | * @return 解决冲突后的路径列表 |
| | | */ |
| | | public List<PlannedPath> resolveConflicts(List<PlannedPath> plannedPaths, |
| | | List<Conflict> conflicts, |
| | | List<ExecutingTask> executingTasks) { |
| | | if (conflicts == null || conflicts.isEmpty()) { |
| | | return plannedPaths; |
| | | } |
| | | |
| | | System.out.println("开始解决 " + conflicts.size() + " 个路径冲突"); |
| | | |
| | | // 构建路径字典便于快速访问 |
| | | Map<String, PlannedPath> pathsMap = new HashMap<>(); |
| | | for (PlannedPath path : plannedPaths) { |
| | | pathsMap.put(path.getAgvId(), path); |
| | | } |
| | | |
| | | // 按时间步排序处理冲突 |
| | | List<Conflict> sortedConflicts = new ArrayList<>(conflicts); |
| | | sortedConflicts.sort(Comparator.comparingInt(Conflict::getTimeStep)); |
| | | |
| | | // 逐个解决冲突 |
| | | for (Conflict conflict : sortedConflicts) { |
| | | resolveSingleConflict(pathsMap, conflict, executingTasks); |
| | | } |
| | | |
| | | List<PlannedPath> resolvedPaths = new ArrayList<>(pathsMap.values()); |
| | | |
| | | // 验证解决结果 |
| | | List<Conflict> remainingConflicts = collisionDetector.detectConflicts(resolvedPaths); |
| | | System.out.println("冲突解决完成,剩余冲突数量: " + remainingConflicts.size()); |
| | | |
| | | return resolvedPaths; |
| | | } |
| | | |
| | | /** |
| | | * 解决单个冲突 |
| | | * |
| | | * @param pathsMap 路径映射 |
| | | * @param conflict 冲突 |
| | | * @param executingTasks 执行中任务列表 |
| | | */ |
| | | private void resolveSingleConflict(Map<String, PlannedPath> pathsMap, |
| | | Conflict conflict, |
| | | List<ExecutingTask> executingTasks) { |
| | | String conflictType = conflict.getType(); |
| | | |
| | | switch (conflictType) { |
| | | case "vertex": |
| | | resolveVertexConflict(pathsMap, conflict, executingTasks); |
| | | break; |
| | | case "edge": |
| | | resolveEdgeConflict(pathsMap, conflict, executingTasks); |
| | | break; |
| | | case "follow": |
| | | resolveFollowingConflict(pathsMap, conflict, executingTasks); |
| | | break; |
| | | default: |
| | | System.out.println("未知冲突类型: " + conflictType); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 解决顶点冲突 |
| | | * |
| | | * @param pathsMap 路径映射 |
| | | * @param conflict 冲突 |
| | | * @param executingTasks 执行中任务列表 |
| | | */ |
| | | private void resolveVertexConflict(Map<String, PlannedPath> pathsMap, |
| | | Conflict conflict, |
| | | List<ExecutingTask> executingTasks) { |
| | | String agv1 = conflict.getAgv1(); |
| | | String agv2 = conflict.getAgv2(); |
| | | int timeStep = conflict.getTimeStep(); |
| | | |
| | | // 评估AGV优先级 |
| | | AGVPriority priority1 = evaluateAgvPriority(agv1, pathsMap.get(agv1), executingTasks); |
| | | AGVPriority priority2 = evaluateAgvPriority(agv2, pathsMap.get(agv2), executingTasks); |
| | | |
| | | // 优先级低的AGV进行延迟 |
| | | if (priority1.priorityScore >= priority2.priorityScore) { |
| | | // agv2优先级低,延迟agv2 |
| | | addDelayToPath(pathsMap.get(agv2), timeStep, 1); |
| | | System.out.println("为解决顶点冲突,AGV " + agv2 + " 在时间步 " + timeStep + " 延迟1步"); |
| | | } else { |
| | | // agv1优先级低,延迟agv1 |
| | | addDelayToPath(pathsMap.get(agv1), timeStep, 1); |
| | | System.out.println("为解决顶点冲突,AGV " + agv1 + " 在时间步 " + timeStep + " 延迟1步"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 解决边冲突 |
| | | * |
| | | * @param pathsMap 路径映射 |
| | | * @param conflict 冲突 |
| | | * @param executingTasks 执行中任务列表 |
| | | */ |
| | | private void resolveEdgeConflict(Map<String, PlannedPath> pathsMap, |
| | | Conflict conflict, |
| | | List<ExecutingTask> executingTasks) { |
| | | String agv1 = conflict.getAgv1(); |
| | | String agv2 = conflict.getAgv2(); |
| | | int timeStep = conflict.getTimeStep(); |
| | | |
| | | // 评估AGV优先级 |
| | | AGVPriority priority1 = evaluateAgvPriority(agv1, pathsMap.get(agv1), executingTasks); |
| | | AGVPriority priority2 = evaluateAgvPriority(agv2, pathsMap.get(agv2), executingTasks); |
| | | |
| | | // 优先级低的AGV进行延迟 |
| | | if (priority1.priorityScore >= priority2.priorityScore) { |
| | | // agv2优先级低,延迟agv2 |
| | | addDelayToPath(pathsMap.get(agv2), timeStep, 2); |
| | | System.out.println("为解决边冲突,AGV " + agv2 + " 在时间步 " + timeStep + " 延迟2步"); |
| | | } else { |
| | | // agv1优先级低,延迟agv1 |
| | | addDelayToPath(pathsMap.get(agv1), timeStep, 2); |
| | | System.out.println("为解决边冲突,AGV " + agv1 + " 在时间步 " + timeStep + " 延迟2步"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 解决跟随冲突 |
| | | * |
| | | * @param pathsMap 路径映射 |
| | | * @param conflict 冲突 |
| | | * @param executingTasks 执行中任务列表 |
| | | */ |
| | | private void resolveFollowingConflict(Map<String, PlannedPath> pathsMap, |
| | | Conflict conflict, |
| | | List<ExecutingTask> executingTasks) { |
| | | String agv1 = conflict.getAgv1(); |
| | | String agv2 = conflict.getAgv2(); |
| | | int timeStep = conflict.getTimeStep(); |
| | | |
| | | // 评估AGV优先级 |
| | | AGVPriority priority1 = evaluateAgvPriority(agv1, pathsMap.get(agv1), executingTasks); |
| | | AGVPriority priority2 = evaluateAgvPriority(agv2, pathsMap.get(agv2), executingTasks); |
| | | |
| | | // 优先级低的AGV进行延迟 |
| | | if (priority1.priorityScore >= priority2.priorityScore) { |
| | | // agv2优先级低,延迟agv2 |
| | | addDelayToPath(pathsMap.get(agv2), timeStep, 1); |
| | | System.out.println("为解决跟随冲突,AGV " + agv2 + " 在时间步 " + timeStep + " 延迟1步"); |
| | | } else { |
| | | // agv1优先级低,延迟agv1 |
| | | addDelayToPath(pathsMap.get(agv1), timeStep, 1); |
| | | System.out.println("为解决跟随冲突,AGV " + agv1 + " 在时间步 " + timeStep + " 延迟1步"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 评估AGV优先级 - 优化版本,只保留必要字段 |
| | | * |
| | | * @param agvId AGV编号 |
| | | * @param path 路径 |
| | | * @param executingTasks 执行中任务列表 |
| | | * @return AGV优先级信息 |
| | | */ |
| | | private AGVPriority evaluateAgvPriority(String agvId, PlannedPath path, List<ExecutingTask> executingTasks) { |
| | | // 查找对应的执行任务 |
| | | ExecutingTask task = null; |
| | | for (ExecutingTask et : executingTasks) { |
| | | if (et.getAgvId().equals(agvId)) { |
| | | task = et; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | double priorityScore = 0.0; |
| | | String explanation = "默认优先级"; |
| | | |
| | | if (task != null) { |
| | | String taskStatus = task.getTaskType(); |
| | | int taskPriority = task.getPriority(); |
| | | |
| | | // 基于任务类型的优先级评分 |
| | | switch (taskStatus) { |
| | | case "delivery": |
| | | priorityScore += 50; // 送货任务优先级高 |
| | | explanation = "送货任务 - 高优先级"; |
| | | break; |
| | | case "pickup": |
| | | priorityScore += 30; // 取货任务中等优先级 |
| | | explanation = "取货任务 - 中等优先级"; |
| | | break; |
| | | case "charging": |
| | | priorityScore += 20; // 充电任务低优先级 |
| | | explanation = "充电任务 - 低优先级"; |
| | | break; |
| | | default: |
| | | priorityScore += 10; // 其他任务最低优先级 |
| | | explanation = "其他任务 - 最低优先级"; |
| | | } |
| | | |
| | | // 任务优先级加成 |
| | | priorityScore += taskPriority * 10; |
| | | |
| | | // 路径长度因子(路径越短优先级越高) |
| | | if (path != null && path.getCodeList() != null) { |
| | | double pathLengthFactor = Math.max(0, 50 - path.getCodeList().size()); |
| | | priorityScore += pathLengthFactor; |
| | | } |
| | | } |
| | | |
| | | return new AGVPriority(agvId, priorityScore, explanation); |
| | | } |
| | | |
| | | /** |
| | | * 为路径添加延迟 |
| | | * |
| | | * @param path 路径 |
| | | * @param timeStep 延迟开始的时间步 |
| | | * @param delaySteps 延迟步数 |
| | | */ |
| | | private void addDelayToPath(PlannedPath path, int timeStep, int delaySteps) { |
| | | if (path == null || path.getCodeList() == null || path.getCodeList().isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | List<PathCode> codeList = path.getCodeList(); |
| | | |
| | | // 确保时间步在有效范围内 |
| | | if (timeStep < 0 || timeStep >= codeList.size()) { |
| | | return; |
| | | } |
| | | |
| | | // 获取延迟位置的路径代码 |
| | | PathCode delayCode = codeList.get(timeStep); |
| | | |
| | | // 创建延迟步骤 |
| | | List<PathCode> delaySteps_list = new ArrayList<>(); |
| | | for (int i = 0; i < delaySteps; i++) { |
| | | PathCode waitCode = new PathCode(delayCode.getCode(), delayCode.getDirection()); |
| | | waitCode.setActionType(delayCode.getActionType()); |
| | | waitCode.setTaskId(delayCode.getTaskId()); |
| | | waitCode.setPosType(delayCode.getPosType()); |
| | | waitCode.setLev(delayCode.getLev()); |
| | | waitCode.setTargetPoint(false); // 等待步骤不是目标点 |
| | | delaySteps_list.add(waitCode); |
| | | } |
| | | |
| | | // 插入延迟步骤 |
| | | codeList.addAll(timeStep, delaySteps_list); |
| | | |
| | | // 更新路径 |
| | | path.setCodeList(codeList); |
| | | } |
| | | |
| | | /** |
| | | * AGV优先级评估结果内部类 - 优化版本,只保留必要字段 |
| | | */ |
| | | private static class AGVPriority { |
| | | final String agvId; |
| | | final double priorityScore; |
| | | final String explanation; |
| | | |
| | | public AGVPriority(String agvId, double priorityScore, String explanation) { |
| | | this.agvId = agvId; |
| | | this.priorityScore = priorityScore; |
| | | this.explanation = explanation; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return String.format("AGV %s: 优先级=%.1f, 说明=%s", |
| | | agvId, priorityScore, explanation); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.service; |
| | | |
| | | import com.algo.model.AGVStatus; |
| | | import com.algo.model.BackpackData; |
| | | import com.algo.model.ExecutingTask; |
| | | import com.algo.model.TaskData; |
| | | import com.algo.util.JsonUtils; |
| | | |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * 执行中任务提取器 |
| | | * 负责从AGV状态中提取当前正在执行的任务 |
| | | */ |
| | | public class ExecutingTaskExtractor { |
| | | |
| | | /** |
| | | * 路径映射表 |
| | | */ |
| | | private Map<String, Map<String, Integer>> pathMapping; |
| | | |
| | | /** |
| | | * 任务数据映射表 |
| | | */ |
| | | private Map<String, TaskData> taskDataMap; |
| | | |
| | | /** |
| | | * 坐标到路径编号的映射 |
| | | */ |
| | | private Map<String, String> coordToCode; |
| | | |
| | | /** |
| | | * 取货位置列表 |
| | | */ |
| | | private List<String> pickupPositions; |
| | | |
| | | /** |
| | | * 充电位置列表 |
| | | */ |
| | | private List<String> chargingPositions; |
| | | |
| | | /** |
| | | * 送货位置列表 |
| | | */ |
| | | private List<String> deliveryPositions; |
| | | |
| | | /** |
| | | * 待机位置列表 |
| | | */ |
| | | private List<String> standbyPositions; |
| | | |
| | | /** |
| | | * 构造函数 |
| | | * |
| | | * @param pathMapping 路径映射表 |
| | | * @param taskDataList 任务数据列表 |
| | | */ |
| | | public ExecutingTaskExtractor(Map<String, Map<String, Integer>> pathMapping, List<TaskData> taskDataList) { |
| | | this.pathMapping = pathMapping; |
| | | this.coordToCode = new HashMap<>(); |
| | | this.taskDataMap = new HashMap<>(); |
| | | |
| | | // 构建任务数据映射表 |
| | | if (taskDataList != null) { |
| | | for (TaskData taskData : taskDataList) { |
| | | taskDataMap.put(taskData.getTaskId(), taskData); |
| | | } |
| | | } |
| | | |
| | | // 预计算坐标映射 |
| | | buildCoordToCodeMapping(); |
| | | |
| | | // 预分类位置类型 |
| | | classifyPositions(); |
| | | |
| | | System.out.println("任务提取器初始化完成,分类位置:取货=" + pickupPositions.size() + |
| | | ", 充电=" + chargingPositions.size() + |
| | | ", 送货=" + deliveryPositions.size() + |
| | | ", 待机=" + standbyPositions.size() + |
| | | ", 任务数据=" + taskDataMap.size()); |
| | | } |
| | | |
| | | /** |
| | | * 提取执行中的任务 |
| | | * |
| | | * @param agvStatusList AGV状态列表 |
| | | * @return 执行中任务列表 |
| | | */ |
| | | public List<ExecutingTask> extractExecutingTasks(List<AGVStatus> agvStatusList) { |
| | | List<ExecutingTask> executingTasks = new ArrayList<>(); |
| | | |
| | | for (AGVStatus agvStatus : agvStatusList) { |
| | | List<ExecutingTask> agvTasks = extractAgvTasks(agvStatus); |
| | | executingTasks.addAll(agvTasks); |
| | | } |
| | | |
| | | System.out.println("提取到 " + executingTasks.size() + " 个执行中任务"); |
| | | return executingTasks; |
| | | } |
| | | |
| | | /** |
| | | * 提取单个AGV的执行中任务 |
| | | * |
| | | * @param agvStatus AGV状态 |
| | | * @return 执行中任务列表 |
| | | */ |
| | | private List<ExecutingTask> extractAgvTasks(AGVStatus agvStatus) { |
| | | List<ExecutingTask> tasks = new ArrayList<>(); |
| | | |
| | | // 检查AGV是否可用 |
| | | if (!agvStatus.isAvailable()) { |
| | | return tasks; |
| | | } |
| | | |
| | | // 分析背篓状态 |
| | | List<BackpackData> backpack = agvStatus.getBackpack(); |
| | | if (backpack == null || backpack.isEmpty()) { |
| | | return tasks; |
| | | } |
| | | |
| | | // 分别收集已装载和未装载的任务 |
| | | List<BackpackData> loadedTasks = new ArrayList<>(); |
| | | List<BackpackData> unloadedTasks = new ArrayList<>(); |
| | | |
| | | for (BackpackData bp : backpack) { |
| | | if (bp.getTaskId() != null && !bp.getTaskId().trim().isEmpty()) { |
| | | TaskData taskData = getTaskData(bp.getTaskId()); |
| | | if (taskData != null) { |
| | | if (bp.isLoaded()) { |
| | | loadedTasks.add(bp); |
| | | } else { |
| | | unloadedTasks.add(bp); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 确定下一步最优行动 |
| | | ExecutingTask nextTask = determineNextBestAction(agvStatus, loadedTasks, unloadedTasks); |
| | | if (nextTask != null) { |
| | | tasks.add(nextTask); |
| | | } |
| | | |
| | | return tasks; |
| | | } |
| | | |
| | | /** |
| | | * 确定AGV下一步最优行动 |
| | | * 使用加权成本算法综合考虑距离和任务类型 |
| | | * |
| | | * @param agvStatus AGV状态 |
| | | * @param loadedTasks 已装载任务 |
| | | * @param unloadedTasks 未装载任务 |
| | | * @return 下一步执行任务 |
| | | */ |
| | | private ExecutingTask determineNextBestAction(AGVStatus agvStatus, |
| | | List<BackpackData> loadedTasks, |
| | | List<BackpackData> unloadedTasks) { |
| | | String currentPosition = agvStatus.getPosition(); |
| | | List<TaskOption> taskOptions = new ArrayList<>(); |
| | | |
| | | // 1. 收集所有可选的送货任务 |
| | | for (BackpackData task : loadedTasks) { |
| | | TaskData taskData = getTaskData(task.getTaskId()); |
| | | if (taskData != null && taskData.getEnd() != null) { |
| | | double distance = calculateDistance(currentPosition, taskData.getEnd()); |
| | | if (distance >= 0) { |
| | | double cost = calculateTaskCost(distance, "delivery", taskData.getPriority(), agvStatus); |
| | | taskOptions.add(new TaskOption(task, true, distance, cost, "delivery")); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 2. 收集所有可选的取货任务 |
| | | for (BackpackData task : unloadedTasks) { |
| | | TaskData taskData = getTaskData(task.getTaskId()); |
| | | if (taskData != null && taskData.getStart() != null) { |
| | | double distance = calculateDistance(currentPosition, taskData.getStart()); |
| | | if (distance >= 0) { |
| | | double cost = calculateTaskCost(distance, "pickup", taskData.getPriority(), agvStatus); |
| | | taskOptions.add(new TaskOption(task, false, distance, cost, "pickup")); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 3. 如果电量不足,添加充电选项 |
| | | if (agvStatus.needsCharging()) { |
| | | String nearestCharging = findNearestChargingPosition(currentPosition); |
| | | if (nearestCharging != null && !nearestCharging.equals(currentPosition)) { |
| | | double distance = calculateDistance(currentPosition, nearestCharging); |
| | | if (distance >= 0) { |
| | | double cost = calculateChargingCost(distance, agvStatus.getVol(), agvStatus.getLowVol()); |
| | | taskOptions.add(new TaskOption(null, false, distance, cost, "charging")); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 4. 选择成本最低的任务 |
| | | if (taskOptions.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | // 按成本排序,选择最优选项 |
| | | taskOptions.sort(Comparator.comparingDouble(option -> option.cost)); |
| | | TaskOption bestOption = taskOptions.get(0); |
| | | |
| | | // 输出决策日志 |
| | | System.out.println("AGV " + agvStatus.getAgvId() + " 任务决策:"); |
| | | for (int i = 0; i < Math.min(3, taskOptions.size()); i++) { |
| | | TaskOption option = taskOptions.get(i); |
| | | System.out.println(" " + (i == 0 ? "[选中]" : " ") + |
| | | " 类型: " + option.taskType + |
| | | ", 距离: " + String.format("%.1f", option.distance) + |
| | | ", 成本: " + String.format("%.2f", option.cost)); |
| | | } |
| | | |
| | | // 创建执行任务 |
| | | if ("charging".equals(bestOption.taskType)) { |
| | | String nearestCharging = findNearestChargingPosition(currentPosition); |
| | | return createChargingTask(agvStatus, nearestCharging); |
| | | } else { |
| | | return createExecutingTask(agvStatus, bestOption.backpackData, bestOption.isLoaded); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 计算任务成本 |
| | | * |
| | | * @param distance 距离 |
| | | * @param taskType 任务类型 |
| | | * @param taskPriority 任务优先级 |
| | | * @param agvStatus AGV状态 |
| | | * @return 任务成本 |
| | | */ |
| | | private double calculateTaskCost(double distance, String taskType, int taskPriority, AGVStatus agvStatus) { |
| | | // 基础距离成本 |
| | | double distanceCost = distance; |
| | | |
| | | // 任务类型权重 |
| | | double taskTypeWeight; |
| | | switch (taskType) { |
| | | case "delivery": |
| | | taskTypeWeight = 10.0; // 送货任务基础权重较低,优先执行 |
| | | break; |
| | | case "pickup": |
| | | taskTypeWeight = 15.0; // 取货任务基础权重适中 |
| | | break; |
| | | default: |
| | | taskTypeWeight = 20.0; |
| | | } |
| | | |
| | | // 任务优先级影响(优先级越高,权重越低) |
| | | double priorityWeight = Math.max(1.0, 6.0 - taskPriority); |
| | | |
| | | // 电量影响(电量低时偏向近距离任务) |
| | | double batteryFactor = 1.0; |
| | | if (agvStatus.needsCharging()) { |
| | | batteryFactor = 1.0 + (100.0 - agvStatus.getVol()) / 100.0; // 电量越低,距离成本越高 |
| | | } |
| | | |
| | | // 综合成本计算 |
| | | double totalCost = (distanceCost * batteryFactor) + (taskTypeWeight * priorityWeight); |
| | | |
| | | return totalCost; |
| | | } |
| | | |
| | | /** |
| | | * 计算充电任务成本 |
| | | * |
| | | * @param distance 到充电点的距离 |
| | | * @param currentVol 当前电量 |
| | | * @param lowVol 最低电量阈值 |
| | | * @return 充电任务成本 |
| | | */ |
| | | private double calculateChargingCost(double distance, int currentVol, int lowVol) { |
| | | // 基础距离成本 |
| | | double distanceCost = distance; |
| | | |
| | | // 电量紧急程度(电量越低,充电成本越低,即优先级越高) |
| | | double batteryUrgency; |
| | | if (currentVol <= lowVol) { |
| | | batteryUrgency = 5.0; // 必须充电,极高优先级 |
| | | } else { |
| | | // 电量在lowVol到autoCharge之间,线性计算紧急程度 |
| | | batteryUrgency = 30.0 - (currentVol - lowVol) * 0.5; // 电量越低优先级越高 |
| | | } |
| | | |
| | | return distanceCost + batteryUrgency; |
| | | } |
| | | |
| | | /** |
| | | * 任务选项内部类 |
| | | */ |
| | | private static class TaskOption { |
| | | BackpackData backpackData; |
| | | boolean isLoaded; |
| | | double distance; |
| | | double cost; |
| | | String taskType; |
| | | |
| | | public TaskOption(BackpackData backpackData, boolean isLoaded, double distance, double cost, String taskType) { |
| | | this.backpackData = backpackData; |
| | | this.isLoaded = isLoaded; |
| | | this.distance = distance; |
| | | this.cost = cost; |
| | | this.taskType = taskType; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取任务数据 |
| | | * |
| | | * @param taskId 任务ID |
| | | * @return 任务数据 |
| | | */ |
| | | private TaskData getTaskData(String taskId) { |
| | | return taskDataMap.get(taskId); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 查找最近的充电位置 |
| | | * |
| | | * @param currentPosition 当前位置 |
| | | * @return 最近的充电位置 |
| | | */ |
| | | private String findNearestChargingPosition(String currentPosition) { |
| | | String nearest = null; |
| | | double minDistance = Double.MAX_VALUE; |
| | | |
| | | for (String chargingPos : chargingPositions) { |
| | | if (!chargingPos.equals(currentPosition)) { |
| | | double distance = calculateDistance(currentPosition, chargingPos); |
| | | if (distance >= 0 && distance < minDistance) { |
| | | minDistance = distance; |
| | | nearest = chargingPos; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return nearest; |
| | | } |
| | | |
| | | /** |
| | | * 创建执行任务 |
| | | * |
| | | * @param agvStatus AGV状态 |
| | | * @param backpackData 背篓数据 |
| | | * @param isLoaded 是否已装载 |
| | | * @return 执行任务 |
| | | */ |
| | | private ExecutingTask createExecutingTask(AGVStatus agvStatus, BackpackData backpackData, boolean isLoaded) { |
| | | TaskData taskData = getTaskData(backpackData.getTaskId()); |
| | | if (taskData == null) { |
| | | return null; |
| | | } |
| | | |
| | | ExecutingTask task = new ExecutingTask(); |
| | | task.setAgvId(agvStatus.getAgvId()); |
| | | task.setTaskId(backpackData.getTaskId()); |
| | | task.setCurrentPosition(agvStatus.getPosition()); |
| | | task.setLoaded(isLoaded); |
| | | task.setBackpackIndex(backpackData.getIndex()); |
| | | task.setPriority(taskData.getPriority()); |
| | | |
| | | // 设置目标位置 |
| | | if (isLoaded) { |
| | | task.setTargetPosition(taskData.getEnd()); |
| | | task.setTaskType("delivery"); |
| | | } else { |
| | | task.setTargetPosition(taskData.getStart()); |
| | | task.setTaskType("pickup"); |
| | | } |
| | | |
| | | // 计算距离 |
| | | task.setDistanceToStart(calculateDistance(agvStatus.getPosition(), taskData.getStart())); |
| | | task.setDistanceToEnd(calculateDistance(agvStatus.getPosition(), taskData.getEnd())); |
| | | |
| | | return task; |
| | | } |
| | | |
| | | /** |
| | | * 创建充电任务 |
| | | * |
| | | * @param agvStatus AGV状态 |
| | | * @param chargingPosition 充电位置 |
| | | * @return 充电任务 |
| | | */ |
| | | private ExecutingTask createChargingTask(AGVStatus agvStatus, String chargingPosition) { |
| | | ExecutingTask task = new ExecutingTask(); |
| | | task.setAgvId(agvStatus.getAgvId()); |
| | | task.setTaskId("CHARGING_" + agvStatus.getAgvId()); |
| | | task.setCurrentPosition(agvStatus.getPosition()); |
| | | task.setTargetPosition(chargingPosition); |
| | | task.setTaskType("charging"); |
| | | task.setLoaded(false); |
| | | task.setBackpackIndex(-1); |
| | | task.setPriority(1); // 充电任务优先级较高 |
| | | |
| | | return task; |
| | | } |
| | | |
| | | /** |
| | | * 计算两个位置之间的距离 |
| | | * |
| | | * @param pos1 位置1 |
| | | * @param pos2 位置2 |
| | | * @return 距离值 |
| | | */ |
| | | private double calculateDistance(String pos1, String pos2) { |
| | | if (pos1 == null || pos2 == null) { |
| | | return -1; |
| | | } |
| | | |
| | | int[] coord1 = JsonUtils.getCoordinate(pos1, pathMapping); |
| | | int[] coord2 = JsonUtils.getCoordinate(pos2, pathMapping); |
| | | |
| | | if (coord1 == null || coord2 == null) { |
| | | return -1; |
| | | } |
| | | |
| | | return JsonUtils.calculateManhattanDistance(coord1, coord2); |
| | | } |
| | | |
| | | /** |
| | | * 构建坐标到路径编号的映射 |
| | | */ |
| | | private void buildCoordToCodeMapping() { |
| | | for (Map.Entry<String, Map<String, Integer>> entry : pathMapping.entrySet()) { |
| | | String code = entry.getKey(); |
| | | Map<String, Integer> coordMap = entry.getValue(); |
| | | |
| | | if (coordMap.containsKey("x") && coordMap.containsKey("y")) { |
| | | String coordKey = coordMap.get("x") + "," + coordMap.get("y"); |
| | | coordToCode.put(coordKey, code); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 预分类位置类型 |
| | | */ |
| | | private void classifyPositions() { |
| | | pickupPositions = new ArrayList<>(); |
| | | chargingPositions = new ArrayList<>(); |
| | | deliveryPositions = new ArrayList<>(); |
| | | standbyPositions = new ArrayList<>(); |
| | | |
| | | for (String position : pathMapping.keySet()) { |
| | | if (position.startsWith("1")) { |
| | | pickupPositions.add(position); |
| | | } else if (position.startsWith("2")) { |
| | | chargingPositions.add(position); |
| | | } else if (position.startsWith("3")) { |
| | | deliveryPositions.add(position); |
| | | } else if (position.startsWith("4")) { |
| | | standbyPositions.add(position); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Getter方法 |
| | | public List<String> getPickupPositions() { |
| | | return pickupPositions; |
| | | } |
| | | |
| | | public List<String> getChargingPositions() { |
| | | return chargingPositions; |
| | | } |
| | | |
| | | public List<String> getDeliveryPositions() { |
| | | return deliveryPositions; |
| | | } |
| | | |
| | | public List<String> getStandbyPositions() { |
| | | return standbyPositions; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.service; |
| | | |
| | | import com.algo.model.PlannedPath; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 路径规划器接口 |
| | | * 定义AGV路径规划方法 |
| | | */ |
| | | public interface PathPlanner { |
| | | |
| | | /** |
| | | * 规划单个AGV的路径 |
| | | * |
| | | * @param startCode 起始路径点ID |
| | | * @param endCode 目标路径点ID |
| | | * @param constraints 约束条件列表 [x, y, radius] |
| | | * @return 规划的路径,失败返回null |
| | | */ |
| | | PlannedPath planPath(String startCode, String endCode, List<double[]> constraints); |
| | | |
| | | /** |
| | | * 规划单个AGV的路径(无约束条件) |
| | | * |
| | | * @param startCode 起始路径点ID |
| | | * @param endCode 目标路径点ID |
| | | * @return 规划的路径,失败返回null |
| | | */ |
| | | PlannedPath planPath(String startCode, String endCode); |
| | | |
| | | /** |
| | | * 验证路径点是否有效 |
| | | * |
| | | * @param pathCode 路径点ID |
| | | * @return true如果路径点有效 |
| | | */ |
| | | boolean isValidPathPoint(String pathCode); |
| | | |
| | | /** |
| | | * 计算两个路径点之间的距离 |
| | | * |
| | | * @param startCode 起始路径点ID |
| | | * @param endCode 目标路径点ID |
| | | * @return 距离值,无效路径点返回-1 |
| | | */ |
| | | double calculateDistance(String startCode, String endCode); |
| | | |
| | | /** |
| | | * 获取路径映射信息 |
| | | * |
| | | * @return 路径映射表 |
| | | */ |
| | | Map<String, Map<String, Integer>> getPathMapping(); |
| | | |
| | | /** |
| | | * 获取邻接点列表 |
| | | * |
| | | * @param pathCode 路径点ID |
| | | * @return 邻接点列表及其方向 |
| | | */ |
| | | List<Map<String, String>> getNeighbors(String pathCode); |
| | | } |
New file |
| | |
| | | package com.algo.service; |
| | | |
| | | import com.algo.model.*; |
| | | import com.algo.util.JsonUtils; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.*; |
| | | import java.util.concurrent.*; |
| | | |
| | | /** |
| | | * 路径规划服务 |
| | | */ |
| | | public class PathPlanningService { |
| | | |
| | | /** |
| | | * 路径映射表 |
| | | */ |
| | | private Map<String, Map<String, Integer>> pathMapping; |
| | | |
| | | /** |
| | | * 环境配置 |
| | | */ |
| | | private Map<String, Object> environmentConfig; |
| | | |
| | | /** |
| | | * 执行中任务提取 |
| | | */ |
| | | private ExecutingTaskExtractor taskExtractor; |
| | | |
| | | /** |
| | | * 路径规划 |
| | | */ |
| | | private PathPlanner pathPlanner; |
| | | |
| | | /** |
| | | * 碰撞检测 |
| | | */ |
| | | private CollisionDetector collisionDetector; |
| | | |
| | | /** |
| | | * 碰撞解决 |
| | | */ |
| | | private CollisionResolver collisionResolver; |
| | | |
| | | /** |
| | | * 剩余路径处理 |
| | | */ |
| | | private RemainingPathProcessor remainingPathProcessor; |
| | | |
| | | /** |
| | | * 线程池大小 |
| | | */ |
| | | private final int threadPoolSize; |
| | | |
| | | /** |
| | | * 线程池 |
| | | */ |
| | | private final ExecutorService executorService; |
| | | |
| | | /** |
| | | * CTU批处理大小 |
| | | */ |
| | | private final int batchSize = 10; |
| | | |
| | | /** |
| | | * 构造函数 |
| | | * |
| | | * @param pathMapping 路径映射表 |
| | | * @param environmentConfig 环境配置 |
| | | * @param taskDataList 任务数据列表 |
| | | */ |
| | | public PathPlanningService(Map<String, Map<String, Integer>> pathMapping, |
| | | Map<String, Object> environmentConfig, |
| | | List<TaskData> taskDataList) { |
| | | this.pathMapping = pathMapping; |
| | | this.environmentConfig = environmentConfig; |
| | | |
| | | this.threadPoolSize = Math.max(4, Runtime.getRuntime().availableProcessors()); |
| | | this.executorService = Executors.newFixedThreadPool(threadPoolSize); |
| | | |
| | | // 初始化 |
| | | initializeComponents(taskDataList); |
| | | |
| | | System.out.println("路径规划服务初始化完成(线程池大小: " + threadPoolSize + ")"); |
| | | } |
| | | |
| | | /** |
| | | * 初始化各个组件 |
| | | * |
| | | * @param taskDataList 任务数据列表 |
| | | */ |
| | | private void initializeComponents(List<TaskData> taskDataList) { |
| | | // 初始化任务提取器 |
| | | this.taskExtractor = new ExecutingTaskExtractor(pathMapping, taskDataList); |
| | | |
| | | // 初始化路径规划器 |
| | | this.pathPlanner = new AStarPathPlanner(pathMapping); |
| | | |
| | | // 初始化碰撞检测器 |
| | | this.collisionDetector = new CollisionDetector(pathMapping); |
| | | |
| | | // 初始化碰撞解决器 |
| | | this.collisionResolver = new CollisionResolver(collisionDetector); |
| | | |
| | | // 初始化剩余路径处理器 |
| | | this.remainingPathProcessor = new RemainingPathProcessor(pathMapping); |
| | | } |
| | | |
| | | /** |
| | | * 为所有CTU规划路径 |
| | | * |
| | | * @param agvStatusList CTU状态列表 |
| | | * @param includeIdleAgv 是否包含空闲CTU |
| | | * @param constraints 路径约束条件 |
| | | * @return 路径规划结果 |
| | | */ |
| | | public PathPlanningResult planAllAgvPaths(List<AGVStatus> agvStatusList, |
| | | boolean includeIdleAgv, |
| | | List<double[]> constraints) { |
| | | long startTime = System.currentTimeMillis(); |
| | | |
| | | System.out.println("开始为 " + agvStatusList.size() + " 个CTU规划"); |
| | | |
| | | // 1. 构建现有剩余路径的时空占用表 |
| | | System.out.println("步骤1: 构建时空占用表"); |
| | | Map<String, String> spaceTimeOccupancyMap = remainingPathProcessor.buildSpaceTimeOccupancyMap(agvStatusList); |
| | | System.out.println("时空占用表构建完成,包含 " + spaceTimeOccupancyMap.size() + " 个占用点"); |
| | | |
| | | // 2. 分类处理CTU:有剩余路径的和需要新路径的 |
| | | List<AGVStatus> ctuWithRemainingPaths = new ArrayList<>(); |
| | | List<AGVStatus> ctuNeedingNewPaths = new ArrayList<>(); |
| | | |
| | | for (AGVStatus agv : agvStatusList) { |
| | | if (agv.hasRemainingPath()) { |
| | | ctuWithRemainingPaths.add(agv); |
| | | } else if (agv.canAcceptNewTask() || (includeIdleAgv && isAgvIdle(agv))) { |
| | | ctuNeedingNewPaths.add(agv); |
| | | } |
| | | } |
| | | |
| | | System.out.println("CTU分类完成: 有剩余路径=" + ctuWithRemainingPaths.size() + |
| | | ", 需要新路径=" + ctuNeedingNewPaths.size()); |
| | | |
| | | // 3. 处理有剩余路径的CTU |
| | | List<PlannedPath> plannedPaths = new ArrayList<>(); |
| | | List<ExecutingTask> executingTasks = new ArrayList<>(); |
| | | Map<String, String> plannedAgvIds = new HashMap<>(); |
| | | |
| | | System.out.println("步骤2: 处理有剩余路径的CTU"); |
| | | for (AGVStatus agv : ctuWithRemainingPaths) { |
| | | PlannedPath remainingPath = processRemainingPath(agv); |
| | | if (remainingPath != null) { |
| | | plannedPaths.add(remainingPath); |
| | | plannedAgvIds.put(agv.getAgvId(), "REMAINING_PATH"); |
| | | |
| | | // 创建对应的执行任务 |
| | | ExecutingTask task = createExecutingTaskFromRemainingPath(agv); |
| | | if (task != null) { |
| | | executingTasks.add(task); |
| | | } |
| | | |
| | | System.out.println("CTU " + agv.getAgvId() + " 剩余路径处理完成,路径长度: " + |
| | | remainingPath.getCodeList().size()); |
| | | } |
| | | } |
| | | |
| | | // 4. 为需要新路径的CTU提取执行中任务 |
| | | System.out.println("步骤3: 提取需要新路径的CTU任务"); |
| | | List<ExecutingTask> newTasks = taskExtractor.extractExecutingTasks(ctuNeedingNewPaths); |
| | | System.out.println("提取到 " + newTasks.size() + " 个新任务"); |
| | | |
| | | // 5. 为新任务规划路径(考虑时空约束) |
| | | System.out.println("步骤4: 为新任务规划路径"); |
| | | for (ExecutingTask task : newTasks) { |
| | | String agvId = task.getAgvId(); |
| | | String currentPos = task.getCurrentPosition(); |
| | | String targetPos = task.getTargetPosition(); |
| | | |
| | | // 避免重复规划 |
| | | if (plannedAgvIds.containsKey(agvId)) { |
| | | System.out.println("跳过CTU " + agvId + ":已规划路径"); |
| | | continue; |
| | | } |
| | | |
| | | // 验证位置信息 |
| | | if (currentPos == null || targetPos == null) { |
| | | System.out.println("CTU " + agvId + " 位置信息无效,跳过"); |
| | | continue; |
| | | } |
| | | |
| | | // 获取CTU状态信息 |
| | | AGVStatus agvStatus = findAgvStatus(agvId, ctuNeedingNewPaths); |
| | | if (agvStatus == null) { |
| | | System.out.println("CTU " + agvId + " 状态信息未找到,跳过"); |
| | | continue; |
| | | } |
| | | |
| | | // 规划时空安全的路径 |
| | | PlannedPath plannedPath = planPathWithSpaceTimeConstraints( |
| | | currentPos, targetPos, constraints, spaceTimeOccupancyMap, agvStatus |
| | | ); |
| | | |
| | | if (plannedPath != null) { |
| | | // 设置CTU信息 |
| | | plannedPath.setAgvId(agvId); |
| | | plannedPath.setSegId(generateSegId(task.getTaskId(), agvId, task.getTaskType())); |
| | | |
| | | // 增强路径代码信息 |
| | | enhancePathCodes(plannedPath, task); |
| | | |
| | | plannedPaths.add(plannedPath); |
| | | plannedAgvIds.put(agvId, targetPos); |
| | | executingTasks.add(task); |
| | | |
| | | // 更新时空占用表 |
| | | updateSpaceTimeOccupancyMap(plannedPath, spaceTimeOccupancyMap, agvStatus); |
| | | |
| | | System.out.println("CTU " + agvId + " 路径规划成功:" + currentPos + " -> " + targetPos + |
| | | ",路径长度: " + plannedPath.getCodeList().size()); |
| | | } else { |
| | | System.out.println("CTU " + agvId + " 路径规划失败:" + currentPos + " -> " + targetPos); |
| | | } |
| | | } |
| | | |
| | | // 6. 处理空闲CTU |
| | | if (includeIdleAgv) { |
| | | System.out.println("步骤5: 处理空闲CTU"); |
| | | List<PlannedPath> idlePaths = planIdleAgvPathsWithConstraints( |
| | | ctuNeedingNewPaths, plannedAgvIds, constraints, spaceTimeOccupancyMap |
| | | ); |
| | | plannedPaths.addAll(idlePaths); |
| | | System.out.println("空闲CTU路径规划完成,数量: " + idlePaths.size()); |
| | | } |
| | | |
| | | // 7. 最终碰撞检测和解决(针对同时生成的新路径) |
| | | System.out.println("步骤6: 最终碰撞检测"); |
| | | List<Conflict> conflicts = collisionDetector.detectConflicts(plannedPaths); |
| | | |
| | | if (!conflicts.isEmpty()) { |
| | | System.out.println("发现 " + conflicts.size() + " 个碰撞,开始解决"); |
| | | plannedPaths = collisionResolver.resolveConflicts(plannedPaths, conflicts, executingTasks); |
| | | |
| | | // 重新检测冲突 |
| | | List<Conflict> remainingConflicts = collisionDetector.detectConflicts(plannedPaths); |
| | | conflicts = remainingConflicts; |
| | | } |
| | | |
| | | long endTime = System.currentTimeMillis(); |
| | | double totalTime = (endTime - startTime) / 1000.0; |
| | | |
| | | // 构建结果 |
| | | PathPlanningResult result = new PathPlanningResult( |
| | | agvStatusList.size(), |
| | | executingTasks.size(), |
| | | plannedPaths.size(), |
| | | totalTime, |
| | | conflicts.size(), |
| | | conflicts.isEmpty(), |
| | | "A*_WITH_SPACETIME_CONSTRAINTS", |
| | | plannedPaths, |
| | | executingTasks |
| | | ); |
| | | |
| | | System.out.println("路径规划完成 - 总CTU: " + result.getTotalAgvs() + |
| | | ", 执行任务: " + result.getExecutingTasksCount() + |
| | | ", 规划路径: " + result.getPlannedPathsCount() + |
| | | ", 耗时: " + result.getTotalPlanningTime() + "s" + |
| | | ", 无冲突: " + result.isCollisionFree()); |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 处理CTU的剩余路径 |
| | | * |
| | | * @param agv CTU状态 |
| | | * @return 处理后的路径 |
| | | */ |
| | | private PlannedPath processRemainingPath(AGVStatus agv) { |
| | | if (!agv.hasRemainingPath()) { |
| | | return null; |
| | | } |
| | | |
| | | PlannedPath remainingPath = agv.getRemainingPath(); |
| | | List<PathCode> remainingCodes = new ArrayList<>(); |
| | | |
| | | // 从当前位置开始,获取剩余路径 |
| | | List<PathCode> originalCodes = remainingPath.getCodeList(); |
| | | for (int i = agv.getCurrentPathIndex(); i < originalCodes.size(); i++) { |
| | | remainingCodes.add(originalCodes.get(i)); |
| | | } |
| | | |
| | | // 创建新的路径对象 |
| | | PlannedPath processedPath = new PlannedPath(); |
| | | processedPath.setAgvId(agv.getAgvId()); |
| | | processedPath.setCodeList(remainingCodes); |
| | | processedPath.setSegId(agv.getAgvId() + "_REMAINING_" + System.currentTimeMillis()); |
| | | |
| | | return processedPath; |
| | | } |
| | | |
| | | /** |
| | | * 从剩余路径创建执行任务 |
| | | * |
| | | * @param agv CTU状态 |
| | | * @return 执行任务 |
| | | */ |
| | | private ExecutingTask createExecutingTaskFromRemainingPath(AGVStatus agv) { |
| | | if (!agv.hasRemainingPath()) { |
| | | return null; |
| | | } |
| | | |
| | | PlannedPath remainingPath = agv.getRemainingPath(); |
| | | List<PathCode> codeList = remainingPath.getCodeList(); |
| | | |
| | | if (codeList.isEmpty() || agv.getCurrentPathIndex() >= codeList.size()) { |
| | | return null; |
| | | } |
| | | |
| | | // 获取当前位置和目标位置 |
| | | PathCode currentCode = codeList.get(agv.getCurrentPathIndex()); |
| | | PathCode targetCode = codeList.get(codeList.size() - 1); |
| | | |
| | | ExecutingTask task = new ExecutingTask(); |
| | | task.setAgvId(agv.getAgvId()); |
| | | task.setTaskId(remainingPath.getSegId()); |
| | | task.setCurrentPosition(currentCode.getCode()); |
| | | task.setTargetPosition(targetCode.getCode()); |
| | | task.setTaskType("remaining"); |
| | | task.setLoaded(false); // 假设值 |
| | | task.setBackpackIndex(0); |
| | | task.setPriority(1); |
| | | |
| | | return task; |
| | | } |
| | | |
| | | /** |
| | | * 规划带时空约束的路径 |
| | | * |
| | | * @param startPos 起始位置 |
| | | * @param endPos 目标位置 |
| | | * @param constraints 约束条件 |
| | | * @param occupancyMap 时空占用表 |
| | | * @param agvStatus CTU状态 |
| | | * @return 规划的路径 |
| | | */ |
| | | private PlannedPath planPathWithSpaceTimeConstraints(String startPos, String endPos, |
| | | List<double[]> constraints, |
| | | Map<String, String> occupancyMap, |
| | | AGVStatus agvStatus) { |
| | | // 首先尝试基本路径规划 |
| | | PlannedPath basicPath = pathPlanner.planPath(startPos, endPos, constraints); |
| | | if (basicPath == null) { |
| | | return null; |
| | | } |
| | | |
| | | // 找到安全的起始时间 |
| | | long safeStartTime = remainingPathProcessor.findSafeStartTime( |
| | | basicPath, occupancyMap, agvStatus.getPhysicalConfig() |
| | | ); |
| | | |
| | | // 设置路径的时间信息 |
| | | enhancePathWithTimeInfo(basicPath, safeStartTime, agvStatus.getPhysicalConfig()); |
| | | |
| | | return basicPath; |
| | | } |
| | | |
| | | /** |
| | | * 路径的时间信息 |
| | | * |
| | | * @param path 路径 |
| | | * @param startTime 起始时间 |
| | | * @param config 物理配置 |
| | | */ |
| | | private void enhancePathWithTimeInfo(PlannedPath path, long startTime, CTUPhysicalConfig config) { |
| | | List<PathCode> codeList = path.getCodeList(); |
| | | if (codeList == null || codeList.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | long currentTime = startTime; |
| | | |
| | | for (int i = 0; i < codeList.size(); i++) { |
| | | PathCode pathCode = codeList.get(i); |
| | | |
| | | // 设置预计到达时间 |
| | | |
| | | // 计算到下一个点的时间 |
| | | if (i < codeList.size() - 1) { |
| | | double travelTime = config.getStandardPointDistance() / config.getNormalSpeed(); |
| | | currentTime += (long) (travelTime * 1000); |
| | | |
| | | // 如果有方向变化,增加转向时间 |
| | | PathCode nextCode = codeList.get(i + 1); |
| | | if (!pathCode.getDirection().equals(nextCode.getDirection())) { |
| | | double turnTime = config.getTurnTime(pathCode.getDirection(), nextCode.getDirection()); |
| | | currentTime += (long) (turnTime * 1000); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新时空占用表 |
| | | * |
| | | * @param path 新路径 |
| | | * @param occupancyMap 占用表 |
| | | * @param agvStatus CTU状态 |
| | | */ |
| | | private void updateSpaceTimeOccupancyMap(PlannedPath path, Map<String, String> occupancyMap, |
| | | AGVStatus agvStatus) { |
| | | // 将新路径添加到占用表中 |
| | | List<PathCode> codeList = path.getCodeList(); |
| | | CTUPhysicalConfig config = agvStatus.getPhysicalConfig(); |
| | | |
| | | long currentTime = System.currentTimeMillis() / 1000; // 转换为秒 |
| | | |
| | | for (PathCode pathCode : codeList) { |
| | | int[] coord = JsonUtils.getCoordinate(pathCode.getCode(), pathMapping); |
| | | if (coord != null) { |
| | | String spaceTimeKey = coord[0] + "," + coord[1] + "," + currentTime; |
| | | occupancyMap.put(spaceTimeKey, agvStatus.getAgvId()); |
| | | currentTime += 2; // 假设每个点停留2秒 |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 查找CTU状态 |
| | | * |
| | | * @param agvId CTU编号 |
| | | * @param agvList CTU列表 |
| | | * @return CTU状态 |
| | | */ |
| | | private AGVStatus findAgvStatus(String agvId, List<AGVStatus> agvList) { |
| | | for (AGVStatus agv : agvList) { |
| | | if (agv.getAgvId().equals(agvId)) { |
| | | return agv; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 规划带约束的空闲CTU路径 |
| | | * |
| | | * @param agvStatusList CTU状态列表 |
| | | * @param plannedAgvIds 已规划CTU映射 |
| | | * @param constraints 约束条件 |
| | | * @param occupancyMap 时空占用表 |
| | | * @return 空闲CTU路径列表 |
| | | */ |
| | | private List<PlannedPath> planIdleAgvPathsWithConstraints(List<AGVStatus> agvStatusList, |
| | | Map<String, String> plannedAgvIds, |
| | | List<double[]> constraints, |
| | | Map<String, String> occupancyMap) { |
| | | List<PlannedPath> idlePaths = new ArrayList<>(); |
| | | |
| | | for (AGVStatus agvStatus : agvStatusList) { |
| | | String agvId = agvStatus.getAgvId(); |
| | | |
| | | // 跳过已规划的CTU |
| | | if (plannedAgvIds.containsKey(agvId)) { |
| | | continue; |
| | | } |
| | | |
| | | // 检查CTU是否空闲 |
| | | if (isAgvIdle(agvStatus)) { |
| | | String currentPos = agvStatus.getPosition(); |
| | | String targetPos = getIdleAgvTarget(agvId, currentPos); |
| | | |
| | | if (targetPos != null && !targetPos.equals(currentPos)) { |
| | | PlannedPath idlePath = planPathWithSpaceTimeConstraints( |
| | | currentPos, targetPos, constraints, occupancyMap, agvStatus |
| | | ); |
| | | |
| | | if (idlePath != null) { |
| | | idlePath.setAgvId(agvId); |
| | | idlePath.setSegId(generateSegId("IDLE", agvId, "idle")); |
| | | |
| | | // 设置空闲路径的代码信息 |
| | | enhanceIdlePathCodes(idlePath, agvStatus); |
| | | |
| | | idlePaths.add(idlePath); |
| | | plannedAgvIds.put(agvId, targetPos); |
| | | |
| | | // 更新占用表 |
| | | updateSpaceTimeOccupancyMap(idlePath, occupancyMap, agvStatus); |
| | | |
| | | System.out.println("空闲CTU " + agvId + " 路径规划成功:" + currentPos + " -> " + targetPos); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return idlePaths; |
| | | } |
| | | |
| | | /** |
| | | * 路径代码信息 |
| | | * |
| | | * @param plannedPath 规划路径 |
| | | * @param task 执行任务 |
| | | */ |
| | | private void enhancePathCodes(PlannedPath plannedPath, ExecutingTask task) { |
| | | List<PathCode> codeList = plannedPath.getCodeList(); |
| | | if (codeList == null || codeList.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | String actionType = "2"; // 任务类型 |
| | | String taskId = task.getTaskId(); |
| | | int backpackLevel = task.getBackpackIndex(); |
| | | |
| | | for (int i = 0; i < codeList.size(); i++) { |
| | | PathCode pathCode = codeList.get(i); |
| | | |
| | | // 设置基本信息 |
| | | pathCode.setActionType(actionType); |
| | | pathCode.setTaskId(taskId); |
| | | pathCode.setLev(backpackLevel); |
| | | |
| | | // 设置目标点信息 |
| | | if (i == codeList.size() - 1) { // 最后一个点 |
| | | pathCode.setTargetPoint(true); |
| | | if (task.isLoaded()) { |
| | | pathCode.setPosType("2"); // 放货 |
| | | } else { |
| | | pathCode.setPosType("1"); // 取货 |
| | | } |
| | | } else { |
| | | pathCode.setTargetPoint(false); |
| | | pathCode.setPosType(null); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 空闲路径代码信息 |
| | | * |
| | | * @param plannedPath 规划路径 |
| | | * @param agvStatus CTU状态 |
| | | */ |
| | | private void enhanceIdlePathCodes(PlannedPath plannedPath, AGVStatus agvStatus) { |
| | | List<PathCode> codeList = plannedPath.getCodeList(); |
| | | if (codeList == null || codeList.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | String actionType = agvStatus.needsCharging() ? "3" : "4"; // 充电或待机 |
| | | |
| | | for (PathCode pathCode : codeList) { |
| | | pathCode.setActionType(actionType); |
| | | pathCode.setTaskId(null); |
| | | pathCode.setPosType(null); |
| | | pathCode.setLev(0); |
| | | pathCode.setTargetPoint(false); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 判断CTU是否空闲 |
| | | * |
| | | * @param agvStatus CTU状态 |
| | | * @return true如果CTU空闲 |
| | | */ |
| | | private boolean isAgvIdle(AGVStatus agvStatus) { |
| | | if (!agvStatus.isAvailable()) { |
| | | return false; |
| | | } |
| | | |
| | | // 检查背篓是否有执行中的任务 |
| | | List<BackpackData> backpack = agvStatus.getBackpack(); |
| | | if (backpack != null) { |
| | | for (BackpackData bp : backpack) { |
| | | if (bp.getTaskId() != null && !bp.getTaskId().trim().isEmpty()) { |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 获取空闲CTU的目标位置 |
| | | * |
| | | * @param agvId CTU编号 |
| | | * @param currentPosition 当前位置 |
| | | * @return 目标位置 |
| | | */ |
| | | private String getIdleAgvTarget(String agvId, String currentPosition) { |
| | | // 根据CTU ID的哈希值选择位置 |
| | | int hashValue = Math.abs(agvId.hashCode() % 1000); |
| | | |
| | | // 优先选择充电位置 |
| | | List<String> chargingPositions = taskExtractor.getChargingPositions(); |
| | | if (!chargingPositions.isEmpty()) { |
| | | for (String pos : chargingPositions) { |
| | | if (!pos.equals(currentPosition)) { |
| | | return pos; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 其次选择待机位置 |
| | | List<String> standbyPositions = taskExtractor.getStandbyPositions(); |
| | | if (!standbyPositions.isEmpty()) { |
| | | return standbyPositions.get(hashValue % standbyPositions.size()); |
| | | } |
| | | |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 生成段落ID |
| | | * |
| | | * @param taskId 任务ID |
| | | * @param agvId CTU编号 |
| | | * @param taskType 任务类型 |
| | | * @return 段落ID |
| | | */ |
| | | private String generateSegId(String taskId, String agvId, String taskType) { |
| | | return taskId + "_" + agvId + "_" + taskType; |
| | | } |
| | | |
| | | /** |
| | | * 路径规划结果类 |
| | | */ |
| | | public static class PathPlanningResult { |
| | | private int totalAgvs; |
| | | private int executingTasksCount; |
| | | private int plannedPathsCount; |
| | | private double totalPlanningTime; |
| | | private int conflictsDetected; |
| | | private boolean collisionFree; |
| | | private String algorithm; |
| | | private List<PlannedPath> plannedPaths; |
| | | private List<ExecutingTask> executingTasks; |
| | | |
| | | public PathPlanningResult(int totalAgvs, int executingTasksCount, int plannedPathsCount, |
| | | double totalPlanningTime, int conflictsDetected, boolean collisionFree, |
| | | String algorithm, List<PlannedPath> plannedPaths, List<ExecutingTask> executingTasks) { |
| | | this.totalAgvs = totalAgvs; |
| | | this.executingTasksCount = executingTasksCount; |
| | | this.plannedPathsCount = plannedPathsCount; |
| | | this.totalPlanningTime = totalPlanningTime; |
| | | this.conflictsDetected = conflictsDetected; |
| | | this.collisionFree = collisionFree; |
| | | this.algorithm = algorithm; |
| | | this.plannedPaths = plannedPaths; |
| | | this.executingTasks = executingTasks; |
| | | } |
| | | |
| | | // Getter方法 |
| | | public int getTotalAgvs() { |
| | | return totalAgvs; |
| | | } |
| | | |
| | | public int getExecutingTasksCount() { |
| | | return executingTasksCount; |
| | | } |
| | | |
| | | public int getPlannedPathsCount() { |
| | | return plannedPathsCount; |
| | | } |
| | | |
| | | public double getTotalPlanningTime() { |
| | | return totalPlanningTime; |
| | | } |
| | | |
| | | public int getConflictsDetected() { |
| | | return conflictsDetected; |
| | | } |
| | | |
| | | public boolean isCollisionFree() { |
| | | return collisionFree; |
| | | } |
| | | |
| | | public String getAlgorithm() { |
| | | return algorithm; |
| | | } |
| | | |
| | | public List<PlannedPath> getPlannedPaths() { |
| | | return plannedPaths; |
| | | } |
| | | |
| | | public List<ExecutingTask> getExecutingTasks() { |
| | | return executingTasks; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "PathPlanningResult{" + |
| | | "totalAgvs=" + totalAgvs + |
| | | ", executingTasksCount=" + executingTasksCount + |
| | | ", plannedPathsCount=" + plannedPathsCount + |
| | | ", executingTasksCount=" + executingTasksCount + |
| | | ", totalPlanningTime=" + totalPlanningTime + |
| | | ", conflictsDetected=" + conflictsDetected + |
| | | ", collisionFree=" + executingTasksCount + |
| | | ", algorithm=" + algorithm + |
| | | ", plannedPaths=" + plannedPaths + |
| | | ", executingTasks='" + executingTasks + '\'' + |
| | | '}'; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 并行规划任务路径 |
| | | */ |
| | | private List<PlannedPath> planTasksInParallel(List<ExecutingTask> tasks, |
| | | List<double[]> constraints, |
| | | Map<String, String> spaceTimeOccupancyMap, |
| | | List<AGVStatus> agvStatusList) { |
| | | |
| | | List<PlannedPath> allPaths = Collections.synchronizedList(new ArrayList<>()); |
| | | |
| | | // 按优先级排序任务 |
| | | List<ExecutingTask> sortedTasks = new ArrayList<>(tasks); |
| | | sortedTasks.sort((t1, t2) -> Integer.compare(t2.getPriority(), t1.getPriority())); |
| | | |
| | | // 分批处理 |
| | | List<List<ExecutingTask>> batches = createBatches(sortedTasks, batchSize); |
| | | |
| | | for (int batchIndex = 0; batchIndex < batches.size(); batchIndex++) { |
| | | List<ExecutingTask> batch = batches.get(batchIndex); |
| | | |
| | | System.out.println("处理批次 " + (batchIndex + 1) + "/" + batches.size() + |
| | | ", 任务数: " + batch.size()); |
| | | |
| | | List<Future<PlannedPath>> futures = new ArrayList<>(); |
| | | |
| | | for (ExecutingTask task : batch) { |
| | | Future<PlannedPath> future = executorService.submit(() -> { |
| | | return planSingleTaskPath(task, constraints, spaceTimeOccupancyMap, agvStatusList); |
| | | }); |
| | | futures.add(future); |
| | | } |
| | | |
| | | for (Future<PlannedPath> future : futures) { |
| | | try { |
| | | PlannedPath path = future.get(60, TimeUnit.SECONDS); |
| | | if (path != null) { |
| | | allPaths.add(path); |
| | | System.out.println("CTU " + path.getAgvId() + " 路径规划成功"); |
| | | } |
| | | } catch (TimeoutException e) { |
| | | System.out.println("路径规划超时,跳过"); |
| | | future.cancel(true); |
| | | } catch (Exception e) { |
| | | System.out.println("路径规划异常: " + e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return allPaths; |
| | | } |
| | | |
| | | /** |
| | | * 为单个任务规划路径 |
| | | */ |
| | | private PlannedPath planSingleTaskPath(ExecutingTask task, |
| | | List<double[]> constraints, |
| | | Map<String, String> spaceTimeOccupancyMap, |
| | | List<AGVStatus> agvStatusList) { |
| | | |
| | | String agvId = task.getAgvId(); |
| | | String currentPos = task.getCurrentPosition(); |
| | | String targetPos = task.getTargetPosition(); |
| | | |
| | | if (currentPos == null || targetPos == null) { |
| | | return null; |
| | | } |
| | | |
| | | // 获取CTU状态信息 |
| | | AGVStatus agvStatus = findAgvStatus(agvId, agvStatusList); |
| | | if (agvStatus == null) { |
| | | return null; |
| | | } |
| | | |
| | | // 规划时空安全的路径 |
| | | PlannedPath plannedPath = planPathWithSpaceTimeConstraints( |
| | | currentPos, targetPos, constraints, spaceTimeOccupancyMap, agvStatus |
| | | ); |
| | | |
| | | if (plannedPath != null) { |
| | | // 设置CTU信息 |
| | | plannedPath.setAgvId(agvId); |
| | | plannedPath.setSegId(generateSegId(task.getTaskId(), agvId, task.getTaskType())); |
| | | |
| | | enhancePathCodes(plannedPath, task); |
| | | } |
| | | |
| | | return plannedPath; |
| | | } |
| | | |
| | | /** |
| | | * 创建批次 |
| | | */ |
| | | private List<List<ExecutingTask>> createBatches(List<ExecutingTask> tasks, int batchSize) { |
| | | List<List<ExecutingTask>> batches = new ArrayList<>(); |
| | | |
| | | for (int i = 0; i < tasks.size(); i += batchSize) { |
| | | int end = Math.min(i + batchSize, tasks.size()); |
| | | batches.add(new ArrayList<>(tasks.subList(i, end))); |
| | | } |
| | | |
| | | return batches; |
| | | } |
| | | |
| | | /** |
| | | * 关闭服务 |
| | | */ |
| | | public void shutdown() { |
| | | if (executorService != null && !executorService.isShutdown()) { |
| | | executorService.shutdown(); |
| | | try { |
| | | if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) { |
| | | executorService.shutdownNow(); |
| | | } |
| | | } catch (InterruptedException e) { |
| | | executorService.shutdownNow(); |
| | | Thread.currentThread().interrupt(); |
| | | } |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.service; |
| | | |
| | | import com.algo.model.AGVStatus; |
| | | import com.algo.model.CTUPhysicalConfig; |
| | | import com.algo.model.PathCode; |
| | | import com.algo.model.PlannedPath; |
| | | import com.algo.util.JsonUtils; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 剩余路径处理器 |
| | | * 负责处理CTU未完成路径的续接逻辑,确保新路径与剩余路径的平滑连接 |
| | | */ |
| | | public class RemainingPathProcessor { |
| | | |
| | | /** |
| | | * 路径映射表 |
| | | */ |
| | | private final Map<String, Map<String, Integer>> pathMapping; |
| | | |
| | | /** |
| | | * 系统当前时间基准(毫秒) |
| | | */ |
| | | private long systemBaseTime; |
| | | |
| | | public RemainingPathProcessor(Map<String, Map<String, Integer>> pathMapping) { |
| | | this.pathMapping = pathMapping; |
| | | this.systemBaseTime = System.currentTimeMillis(); |
| | | } |
| | | |
| | | /** |
| | | * 处理所有CTU的剩余路径,构建时空占用表 |
| | | * |
| | | * @param agvStatusList CTU状态列表 |
| | | * @return 时空占用表,key为"x,y,timeSlot",value为CTU编号 |
| | | */ |
| | | public Map<String, String> buildSpaceTimeOccupancyMap(List<AGVStatus> agvStatusList) { |
| | | Map<String, String> occupancyMap = new HashMap<>(); |
| | | |
| | | for (AGVStatus agv : agvStatusList) { |
| | | if (agv.hasRemainingPath()) { |
| | | processRemainingPathOccupancy(agv, occupancyMap); |
| | | } |
| | | } |
| | | |
| | | return occupancyMap; |
| | | } |
| | | |
| | | /** |
| | | * 处理单个CTU的剩余路径时空占用 |
| | | * |
| | | * @param agv CTU状态 |
| | | * @param occupancyMap 时空占用表 |
| | | */ |
| | | private void processRemainingPathOccupancy(AGVStatus agv, Map<String, String> occupancyMap) { |
| | | PlannedPath remainingPath = agv.getRemainingPath(); |
| | | List<PathCode> codeList = remainingPath.getCodeList(); |
| | | CTUPhysicalConfig config = agv.getPhysicalConfig(); |
| | | |
| | | // 从当前位置开始计算时空占用 |
| | | long currentTime = agv.getNextPointArrivalTime(); |
| | | int startIndex = agv.getCurrentPathIndex(); |
| | | |
| | | for (int i = startIndex; i < codeList.size(); i++) { |
| | | PathCode pathCode = codeList.get(i); |
| | | String position = pathCode.getCode(); |
| | | |
| | | // 获取位置坐标 |
| | | int[] coord = JsonUtils.getCoordinate(position, pathMapping); |
| | | if (coord == null) continue; |
| | | |
| | | // 计算在此位置的停留时间 |
| | | double stayDuration = calculateStayDuration(agv, i, codeList, config); |
| | | |
| | | // 将停留时间转换为时间片 |
| | | long startTimeSlot = currentTime / 1000; // 转换为秒 |
| | | long endTimeSlot = (long) (currentTime / 1000 + stayDuration); |
| | | |
| | | // 占用时空 |
| | | for (long timeSlot = startTimeSlot; timeSlot <= endTimeSlot; timeSlot++) { |
| | | String spaceTimeKey = coord[0] + "," + coord[1] + "," + timeSlot; |
| | | occupancyMap.put(spaceTimeKey, agv.getAgvId()); |
| | | |
| | | // 考虑CTU的物理尺寸,占用周围的格子 |
| | | occupyAdjacentSpaces(coord, timeSlot, agv.getAgvId(), occupancyMap, config); |
| | | } |
| | | |
| | | // 更新到下一个位置的时间 |
| | | currentTime += (long) (stayDuration * 1000); |
| | | if (i < codeList.size() - 1) { |
| | | PathCode nextPathCode = codeList.get(i + 1); |
| | | double travelTime = calculateTravelTime(pathCode, nextPathCode, config); |
| | | currentTime += (long) (travelTime * 1000); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 占用相邻空间(考虑CTU物理尺寸) |
| | | * |
| | | * @param centerCoord 中心坐标 |
| | | * @param timeSlot 时间片 |
| | | * @param agvId CTU编号 |
| | | * @param occupancyMap 占用表 |
| | | * @param config 物理配置 |
| | | */ |
| | | private void occupyAdjacentSpaces(int[] centerCoord, long timeSlot, String agvId, |
| | | Map<String, String> occupancyMap, CTUPhysicalConfig config) { |
| | | // 根据CTU的长度和宽度计算需要占用的格子数 |
| | | int lengthGrids = (int) Math.ceil(config.getCtuLength() / config.getStandardPointDistance()); |
| | | int widthGrids = (int) Math.ceil(config.getCtuWidth() / config.getStandardPointDistance()); |
| | | |
| | | // 占用周围的格子 |
| | | for (int dx = -lengthGrids / 2; dx <= lengthGrids / 2; dx++) { |
| | | for (int dy = -widthGrids / 2; dy <= widthGrids / 2; dy++) { |
| | | if (dx == 0 && dy == 0) continue; // 中心点已经占用 |
| | | |
| | | int adjX = centerCoord[0] + dx; |
| | | int adjY = centerCoord[1] + dy; |
| | | String spaceTimeKey = adjX + "," + adjY + "," + timeSlot; |
| | | |
| | | // 只有当该位置未被占用时才占用 |
| | | if (!occupancyMap.containsKey(spaceTimeKey)) { |
| | | occupancyMap.put(spaceTimeKey, agvId); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 计算CTU在指定路径点的停留时间 |
| | | * |
| | | * @param agv CTU状态 |
| | | * @param pathIndex 路径点索引 |
| | | * @param codeList 完整路径代码列表 |
| | | * @param config 物理配置 |
| | | * @return 停留时间(秒) |
| | | */ |
| | | private double calculateStayDuration(AGVStatus agv, int pathIndex, |
| | | List<PathCode> codeList, CTUPhysicalConfig config) { |
| | | PathCode currentCode = codeList.get(pathIndex); |
| | | |
| | | // 基础停留时间 |
| | | double stayTime = 0.5; // 默认0.5秒 |
| | | |
| | | // 如果需要转向,增加转向时间 |
| | | if (pathIndex < codeList.size() - 1) { |
| | | PathCode nextCode = codeList.get(pathIndex + 1); |
| | | String currentDirection = currentCode.getDirection(); |
| | | String nextDirection = nextCode.getDirection(); |
| | | |
| | | if (!currentDirection.equals(nextDirection)) { |
| | | stayTime += config.getTurnTime(currentDirection, nextDirection); |
| | | } |
| | | } |
| | | |
| | | // 如果是动作点(取货、放货等),增加操作时间 |
| | | if (currentCode.getActionType() != null) { |
| | | switch (currentCode.getActionType()) { |
| | | case "1": // 取货 |
| | | stayTime += 3.0; // 取货需要3秒 |
| | | break; |
| | | case "2": // 放货 |
| | | stayTime += 2.0; // 放货需要2秒 |
| | | break; |
| | | case "3": // 充电 |
| | | stayTime += 10.0; // 充电停靠需要10秒 |
| | | break; |
| | | default: |
| | | stayTime += 1.0; // 其他动作需要1秒 |
| | | break; |
| | | } |
| | | } |
| | | |
| | | return stayTime; |
| | | } |
| | | |
| | | /** |
| | | * 计算两个路径点之间的移动时间 |
| | | * |
| | | * @param fromCode 起始路径点 |
| | | * @param toCode 目标路径点 |
| | | * @param config 物理配置 |
| | | * @return 移动时间(秒) |
| | | */ |
| | | private double calculateTravelTime(PathCode fromCode, PathCode toCode, CTUPhysicalConfig config) { |
| | | // 获取坐标 |
| | | int[] fromCoord = JsonUtils.getCoordinate(fromCode.getCode(), pathMapping); |
| | | int[] toCoord = JsonUtils.getCoordinate(toCode.getCode(), pathMapping); |
| | | |
| | | if (fromCoord == null || toCoord == null) { |
| | | return config.getStandardPointDistance() / config.getNormalSpeed(); |
| | | } |
| | | |
| | | // 计算距离 |
| | | double distance = Math.sqrt(Math.pow(toCoord[0] - fromCoord[0], 2) + |
| | | Math.pow(toCoord[1] - fromCoord[1], 2)) * |
| | | config.getStandardPointDistance(); |
| | | |
| | | // 计算移动时间 |
| | | return config.getTravelTime(distance, config.getNormalSpeed()); |
| | | } |
| | | |
| | | /** |
| | | * 为新路径查找合适的起始时间,避免与现有路径冲突 |
| | | * |
| | | * @param newPath 新路径 |
| | | * @param occupancyMap 现有时空占用表 |
| | | * @param config 物理配置 |
| | | * @return 建议的起始时间(毫秒) |
| | | */ |
| | | public long findSafeStartTime(PlannedPath newPath, Map<String, String> occupancyMap, |
| | | CTUPhysicalConfig config) { |
| | | if (newPath.getCodeList() == null || newPath.getCodeList().isEmpty()) { |
| | | return systemBaseTime; |
| | | } |
| | | |
| | | // 从当前时间开始,逐步向后搜索安全的起始时间 |
| | | long currentTime = systemBaseTime; |
| | | long timeStep = 1000; // 1秒步长 |
| | | long maxSearchTime = currentTime + 300000; // 最多搜索5分钟 |
| | | |
| | | while (currentTime < maxSearchTime) { |
| | | if (isPathTimeSafe(newPath, currentTime, occupancyMap, config)) { |
| | | return currentTime; |
| | | } |
| | | currentTime += timeStep; |
| | | } |
| | | |
| | | // 如果找不到安全时间,返回较远的未来时间 |
| | | return maxSearchTime; |
| | | } |
| | | |
| | | /** |
| | | * 检查指定时间开始的路径是否安全 |
| | | * |
| | | * @param path 路径 |
| | | * @param startTime 起始时间 |
| | | * @param occupancyMap 占用表 |
| | | * @param config 物理配置 |
| | | * @return true如果安全 |
| | | */ |
| | | private boolean isPathTimeSafe(PlannedPath path, long startTime, |
| | | Map<String, String> occupancyMap, CTUPhysicalConfig config) { |
| | | List<PathCode> codeList = path.getCodeList(); |
| | | long currentTime = startTime; |
| | | |
| | | for (int i = 0; i < codeList.size(); i++) { |
| | | PathCode pathCode = codeList.get(i); |
| | | int[] coord = JsonUtils.getCoordinate(pathCode.getCode(), pathMapping); |
| | | if (coord == null) continue; |
| | | |
| | | // 计算在此位置的停留时间 |
| | | double stayDuration = 2.0; // 简化计算,使用固定停留时间 |
| | | long startTimeSlot = currentTime / 1000; |
| | | long endTimeSlot = (long) (currentTime / 1000 + stayDuration); |
| | | |
| | | // 检查时空冲突 |
| | | for (long timeSlot = startTimeSlot; timeSlot <= endTimeSlot; timeSlot++) { |
| | | String spaceTimeKey = coord[0] + "," + coord[1] + "," + timeSlot; |
| | | if (occupancyMap.containsKey(spaceTimeKey)) { |
| | | return false; // 发现冲突 |
| | | } |
| | | |
| | | // 检查安全距离内的冲突 |
| | | if (!isSafeFromOtherCTUs(coord, timeSlot, occupancyMap, config)) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | // 更新到下一个位置的时间 |
| | | currentTime += (long) (stayDuration * 1000); |
| | | if (i < codeList.size() - 1) { |
| | | currentTime += (long) (config.getStandardPointDistance() / config.getNormalSpeed() * 1000); |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 检查指定位置和时间是否与其他CTU保持安全距离 |
| | | * |
| | | * @param coord 位置坐标 |
| | | * @param timeSlot 时间片 |
| | | * @param occupancyMap 占用表 |
| | | * @param config 物理配置 |
| | | * @return true如果安全 |
| | | */ |
| | | private boolean isSafeFromOtherCTUs(int[] coord, long timeSlot, |
| | | Map<String, String> occupancyMap, CTUPhysicalConfig config) { |
| | | int safetyRadius = (int) Math.ceil(config.getMinSafetyDistance() / config.getStandardPointDistance()); |
| | | |
| | | // 检查安全半径内是否有其他CTU |
| | | for (int dx = -safetyRadius; dx <= safetyRadius; dx++) { |
| | | for (int dy = -safetyRadius; dy <= safetyRadius; dy++) { |
| | | if (dx == 0 && dy == 0) continue; |
| | | |
| | | String checkKey = (coord[0] + dx) + "," + (coord[1] + dy) + "," + timeSlot; |
| | | if (occupancyMap.containsKey(checkKey)) { |
| | | // 计算实际距离 |
| | | double distance = Math.sqrt(dx * dx + dy * dy) * config.getStandardPointDistance(); |
| | | if (distance < config.getMinSafetyDistance()) { |
| | | return false; // 距离太近 |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 连接剩余路径和新路径 |
| | | * |
| | | * @param agv CTU状态 |
| | | * @param newPath 新规划的路径 |
| | | * @return 连接后的完整路径 |
| | | */ |
| | | public PlannedPath connectRemainingAndNewPath(AGVStatus agv, PlannedPath newPath) { |
| | | if (!agv.hasRemainingPath()) { |
| | | // 没有剩余路径,直接返回新路径 |
| | | return newPath; |
| | | } |
| | | |
| | | PlannedPath remainingPath = agv.getRemainingPath(); |
| | | List<PathCode> remainingCodes = new ArrayList<>(); |
| | | |
| | | // 添加剩余路径(从当前位置开始) |
| | | List<PathCode> originalCodes = remainingPath.getCodeList(); |
| | | for (int i = agv.getCurrentPathIndex(); i < originalCodes.size(); i++) { |
| | | remainingCodes.add(originalCodes.get(i)); |
| | | } |
| | | |
| | | // 连接新路径 |
| | | if (newPath != null && newPath.getCodeList() != null) { |
| | | // 检查连接点是否一致,避免重复 |
| | | if (!remainingCodes.isEmpty() && !newPath.getCodeList().isEmpty()) { |
| | | PathCode lastRemaining = remainingCodes.get(remainingCodes.size() - 1); |
| | | PathCode firstNew = newPath.getCodeList().get(0); |
| | | |
| | | if (lastRemaining.getCode().equals(firstNew.getCode())) { |
| | | // 跳过重复的连接点 |
| | | for (int i = 1; i < newPath.getCodeList().size(); i++) { |
| | | remainingCodes.add(newPath.getCodeList().get(i)); |
| | | } |
| | | } else { |
| | | // 直接连接 |
| | | remainingCodes.addAll(newPath.getCodeList()); |
| | | } |
| | | } else { |
| | | remainingCodes.addAll(newPath.getCodeList()); |
| | | } |
| | | } |
| | | |
| | | // 创建连接后的路径 |
| | | PlannedPath connectedPath = new PlannedPath(); |
| | | connectedPath.setAgvId(agv.getAgvId()); |
| | | connectedPath.setCodeList(remainingCodes); |
| | | |
| | | // 生成新的段落ID |
| | | String segId = agv.getAgvId() + "_CONNECTED_" + System.currentTimeMillis(); |
| | | if (newPath != null && newPath.getSegId() != null) { |
| | | segId = newPath.getSegId() + "_EXTENDED"; |
| | | } |
| | | connectedPath.setSegId(segId); |
| | | |
| | | return connectedPath; |
| | | } |
| | | |
| | | /** |
| | | * 更新系统基准时间 |
| | | * |
| | | * @param baseTime 新的基准时间 |
| | | */ |
| | | public void updateSystemBaseTime(long baseTime) { |
| | | this.systemBaseTime = baseTime; |
| | | } |
| | | |
| | | /** |
| | | * 获取CTU预计完成剩余路径的时间 |
| | | * |
| | | * @param agv CTU状态 |
| | | * @return 预计完成时间(毫秒) |
| | | */ |
| | | public long getEstimatedCompletionTime(AGVStatus agv) { |
| | | if (!agv.hasRemainingPath()) { |
| | | return System.currentTimeMillis(); |
| | | } |
| | | |
| | | PlannedPath remainingPath = agv.getRemainingPath(); |
| | | List<PathCode> codeList = remainingPath.getCodeList(); |
| | | CTUPhysicalConfig config = agv.getPhysicalConfig(); |
| | | |
| | | long currentTime = agv.getNextPointArrivalTime(); |
| | | |
| | | // 计算完成剩余路径所需的时间 |
| | | for (int i = agv.getCurrentPathIndex(); i < codeList.size(); i++) { |
| | | double stayDuration = calculateStayDuration(agv, i, codeList, config); |
| | | currentTime += (long) (stayDuration * 1000); |
| | | |
| | | if (i < codeList.size() - 1) { |
| | | PathCode currentCode = codeList.get(i); |
| | | PathCode nextCode = codeList.get(i + 1); |
| | | double travelTime = calculateTravelTime(currentCode, nextCode, config); |
| | | currentTime += (long) (travelTime * 1000); |
| | | } |
| | | } |
| | | |
| | | return currentTime; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.service; |
| | | |
| | | import com.algo.model.AGVStatus; |
| | | import com.algo.model.BackpackData; |
| | | import com.algo.model.TaskAssignment; |
| | | import com.algo.model.TaskData; |
| | | import com.algo.util.JsonUtils; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * 任务分配服务类 |
| | | */ |
| | | public class TaskAllocationService { |
| | | |
| | | private Map<String, Map<String, Integer>> pathMapping; |
| | | private Map<String, Object> environmentConfig; |
| | | |
| | | /** |
| | | * 构造函数 |
| | | * |
| | | * @param pathMapping 路径映射信息 |
| | | * @param environmentConfig 环境配置信息 |
| | | */ |
| | | public TaskAllocationService(Map<String, Map<String, Integer>> pathMapping, |
| | | Map<String, Object> environmentConfig) { |
| | | this.pathMapping = pathMapping; |
| | | this.environmentConfig = environmentConfig; |
| | | } |
| | | |
| | | /** |
| | | * 执行任务分配 |
| | | * 根据AGV状态和任务列表进行智能分配 |
| | | * |
| | | * @param agvStatusList AGV状态列表 |
| | | * @param taskList 任务列表 |
| | | * @return 任务分配结果列表 |
| | | */ |
| | | public List<TaskAssignment> allocateTasks(List<AGVStatus> agvStatusList, List<TaskData> taskList) { |
| | | System.out.println("开始任务分配,AGV数量: " + agvStatusList.size() + ", 任务数量: " + taskList.size()); |
| | | |
| | | // 输出环境信息 |
| | | System.out.println("环境信息:宽度=" + environmentConfig.get("width") + |
| | | ", 高度=" + environmentConfig.get("height") + |
| | | ", 工作站数量=" + environmentConfig.get("stationCount")); |
| | | |
| | | List<TaskAssignment> assignments = new ArrayList<>(); |
| | | |
| | | // 1. 过滤可用的AGV |
| | | List<AGVStatus> availableAgvs = agvStatusList.stream() |
| | | .filter(agv -> agv.isAvailable() && agv.getAvailableBackpackCount() > 0) |
| | | .collect(Collectors.toList()); |
| | | |
| | | System.out.println("可用AGV数量: " + availableAgvs.size()); |
| | | |
| | | if (availableAgvs.isEmpty()) { |
| | | System.out.println("没有可用的AGV,任务分配结束"); |
| | | return assignments; |
| | | } |
| | | |
| | | // 2. 过滤需要分配的任务(排除已指定AGV的任务) |
| | | List<TaskData> unassignedTasks = taskList.stream() |
| | | .filter(task -> !task.isAssignedToAgv() && task.isTransportTask()) |
| | | .collect(Collectors.toList()); |
| | | |
| | | System.out.println("需要分配的运输任务数量: " + unassignedTasks.size()); |
| | | |
| | | // 3. 按优先级排序任务 |
| | | unassignedTasks.sort((t1, t2) -> Integer.compare(t2.getPriority(), t1.getPriority())); |
| | | |
| | | // 4. 使用智能分配策略(考虑工作站信息) |
| | | assignments.addAll(performIntelligentAllocation(availableAgvs, unassignedTasks)); |
| | | |
| | | System.out.println("任务分配完成,生成分配结果数量: " + assignments.size()); |
| | | |
| | | return assignments; |
| | | } |
| | | |
| | | /** |
| | | * 执行智能分配策略 |
| | | * 考虑AGV位置、任务位置、背篓容量、工作站容量等因素 |
| | | * |
| | | * @param availableAgvs 可用AGV列表 |
| | | * @param tasks 待分配任务列表 |
| | | * @return 分配结果列表 |
| | | */ |
| | | private List<TaskAssignment> performIntelligentAllocation(List<AGVStatus> availableAgvs, List<TaskData> tasks) { |
| | | List<TaskAssignment> assignments = new ArrayList<>(); |
| | | |
| | | // 为每个AGV维护一个工作负载记录 |
| | | Map<String, Integer> agvWorkLoad = new HashMap<>(); |
| | | Map<String, Integer> agvUsedBackpacks = new HashMap<>(); |
| | | |
| | | for (AGVStatus agv : availableAgvs) { |
| | | agvWorkLoad.put(agv.getAgvId(), 0); |
| | | agvUsedBackpacks.put(agv.getAgvId(), 0); |
| | | } |
| | | |
| | | // 按地理位置分组任务(考虑工作站信息) |
| | | Map<String, List<TaskData>> tasksByStartLocation = groupTasksByStartLocation(tasks); |
| | | |
| | | // 为每个位置分组分配AGV |
| | | for (Map.Entry<String, List<TaskData>> entry : tasksByStartLocation.entrySet()) { |
| | | String startLocation = entry.getKey(); |
| | | List<TaskData> locationTasks = entry.getValue(); |
| | | |
| | | System.out.println("处理起点 " + startLocation + " 的任务,数量: " + locationTasks.size()); |
| | | |
| | | // 检查是否为工作站 |
| | | if (JsonUtils.isStation(startLocation, environmentConfig)) { |
| | | Map<String, Object> stationInfo = JsonUtils.getStationInfo(startLocation, environmentConfig); |
| | | if (stationInfo != null) { |
| | | Integer capacity = (Integer) stationInfo.get("capacity"); |
| | | System.out.println("工作站 " + startLocation + " 容量: " + capacity); |
| | | |
| | | // 限制同时分配给该工作站的任务数量(不超过容量) |
| | | locationTasks = locationTasks.subList(0, Math.min(locationTasks.size(), capacity)); |
| | | } |
| | | } |
| | | |
| | | // 找到距离该位置最近的AGV |
| | | AGVStatus bestAgv = findNearestAvailableAgv(availableAgvs, startLocation, agvUsedBackpacks); |
| | | |
| | | if (bestAgv != null) { |
| | | // 为该AGV分配尽可能多的任务(考虑背篓容量) |
| | | int availableBackpacks = bestAgv.getAvailableBackpackCount() - agvUsedBackpacks.get(bestAgv.getAgvId()); |
| | | int tasksToAssign = Math.min(locationTasks.size(), availableBackpacks); |
| | | |
| | | for (int i = 0; i < tasksToAssign; i++) { |
| | | TaskData task = locationTasks.get(i); |
| | | |
| | | // 查找可用的背篓位置 |
| | | int backpackIndex = findAvailableBackpackIndex(bestAgv, agvUsedBackpacks.get(bestAgv.getAgvId())); |
| | | |
| | | if (backpackIndex != -1) { |
| | | // 创建取货任务分配 |
| | | TaskAssignment takeAssignment = new TaskAssignment( |
| | | task.getTaskId(), |
| | | bestAgv.getAgvId(), |
| | | TaskAssignment.generateSegId(task.getTaskId(), bestAgv.getAgvId(), 1), |
| | | String.valueOf(backpackIndex), |
| | | TaskAssignment.TYPE_TAKE |
| | | ); |
| | | assignments.add(takeAssignment); |
| | | |
| | | // 创建放货任务分配 |
| | | TaskAssignment putAssignment = new TaskAssignment( |
| | | task.getTaskId(), |
| | | bestAgv.getAgvId(), |
| | | TaskAssignment.generateSegId(task.getTaskId(), bestAgv.getAgvId(), 2), |
| | | String.valueOf(backpackIndex), |
| | | TaskAssignment.TYPE_PUT |
| | | ); |
| | | assignments.add(putAssignment); |
| | | |
| | | // 更新AGV工作负载 |
| | | agvWorkLoad.put(bestAgv.getAgvId(), agvWorkLoad.get(bestAgv.getAgvId()) + 1); |
| | | agvUsedBackpacks.put(bestAgv.getAgvId(), agvUsedBackpacks.get(bestAgv.getAgvId()) + 1); |
| | | |
| | | System.out.println("分配任务 " + task.getTaskId() + " 给AGV " + bestAgv.getAgvId() + |
| | | " 背篓 " + backpackIndex + " (起点: " + startLocation + ")"); |
| | | } else { |
| | | System.out.println("AGV " + bestAgv.getAgvId() + " 没有可用的背篓位置"); |
| | | break; |
| | | } |
| | | } |
| | | } else { |
| | | System.out.println("未找到可用的AGV处理起点 " + startLocation + " 的任务"); |
| | | } |
| | | } |
| | | |
| | | return assignments; |
| | | } |
| | | |
| | | /** |
| | | * 按起点位置分组任务 |
| | | * |
| | | * @param tasks 任务列表 |
| | | * @return 按起点分组的任务Map |
| | | */ |
| | | private Map<String, List<TaskData>> groupTasksByStartLocation(List<TaskData> tasks) { |
| | | Map<String, List<TaskData>> grouped = new HashMap<>(); |
| | | |
| | | for (TaskData task : tasks) { |
| | | String startLocation = task.getStart(); |
| | | grouped.computeIfAbsent(startLocation, k -> new ArrayList<>()).add(task); |
| | | } |
| | | |
| | | return grouped; |
| | | } |
| | | |
| | | /** |
| | | * 查找距离指定位置最近的可用AGV |
| | | * |
| | | * @param availableAgvs 可用AGV列表 |
| | | * @param targetLocation 目标位置 |
| | | * @param usedBackpacks 已使用背篓数量统计 |
| | | * @return 最近的可用AGV |
| | | */ |
| | | private AGVStatus findNearestAvailableAgv(List<AGVStatus> availableAgvs, String targetLocation, Map<String, Integer> usedBackpacks) { |
| | | AGVStatus bestAgv = null; |
| | | double minDistance = Double.MAX_VALUE; |
| | | |
| | | int[] targetCoord = JsonUtils.getCoordinate(targetLocation, pathMapping); |
| | | if (targetCoord == null) { |
| | | System.out.println("无法获取目标位置 " + targetLocation + " 的坐标"); |
| | | return availableAgvs.isEmpty() ? null : availableAgvs.get(0); |
| | | } |
| | | |
| | | for (AGVStatus agv : availableAgvs) { |
| | | // 检查AGV是否还有可用背篓 |
| | | int availableBackpacks = agv.getAvailableBackpackCount() - usedBackpacks.get(agv.getAgvId()); |
| | | if (availableBackpacks <= 0) { |
| | | continue; |
| | | } |
| | | |
| | | int[] agvCoord = JsonUtils.getCoordinate(agv.getPosition(), pathMapping); |
| | | if (agvCoord != null) { |
| | | double distance = JsonUtils.calculateManhattanDistance(agvCoord, targetCoord); |
| | | |
| | | // 考虑AGV的工作负载(距离越近且负载越轻越好) |
| | | double adjustedDistance = distance + usedBackpacks.get(agv.getAgvId()) * 2.0; |
| | | |
| | | if (adjustedDistance < minDistance) { |
| | | minDistance = adjustedDistance; |
| | | bestAgv = agv; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return bestAgv; |
| | | } |
| | | |
| | | /** |
| | | * 查找AGV的可用背篓位置 |
| | | * |
| | | * @param agv AGV状态 |
| | | * @param usedCount 已使用的背篓数量 |
| | | * @return 可用背篓索引,如果没有可用则返回-1 |
| | | */ |
| | | private int findAvailableBackpackIndex(AGVStatus agv, int usedCount) { |
| | | if (agv.getBackpack() == null || agv.getBackpack().isEmpty()) { |
| | | return usedCount == 0 ? 0 : -1; |
| | | } |
| | | |
| | | int availableCount = 0; |
| | | for (BackpackData backpack : agv.getBackpack()) { |
| | | if (backpack.isAvailable()) { |
| | | if (availableCount >= usedCount) { |
| | | return backpack.getIndex(); |
| | | } |
| | | availableCount++; |
| | | } |
| | | } |
| | | |
| | | return -1; |
| | | } |
| | | |
| | | /** |
| | | * 验证任务分配结果 |
| | | * |
| | | * @param assignments 分配结果列表 |
| | | * @return 验证是否通过 |
| | | */ |
| | | public boolean validateAssignments(List<TaskAssignment> assignments) { |
| | | Map<String, Set<String>> agvBackpackUsage = new HashMap<>(); |
| | | |
| | | for (TaskAssignment assignment : assignments) { |
| | | if (!assignment.isValid()) { |
| | | System.out.println("发现无效的任务分配: " + assignment); |
| | | return false; |
| | | } |
| | | |
| | | String agvId = assignment.getAgvId(); |
| | | String backpackId = assignment.getLev(); |
| | | |
| | | agvBackpackUsage.computeIfAbsent(agvId, k -> new HashSet<>()).add(backpackId); |
| | | } |
| | | |
| | | // 检查背篓使用冲突 |
| | | for (Map.Entry<String, Set<String>> entry : agvBackpackUsage.entrySet()) { |
| | | String agvId = entry.getKey(); |
| | | Set<String> usedBackpacks = entry.getValue(); |
| | | |
| | | System.out.println("AGV " + agvId + " 使用背篓: " + usedBackpacks); |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | } |
New file |
| | |
| | | package com.algo.util; |
| | | |
| | | import com.algo.model.AGVStatus; |
| | | import com.algo.model.TaskData; |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | |
| | | import java.io.File; |
| | | import java.io.FileNotFoundException; |
| | | import java.io.IOException; |
| | | import java.util.List; |
| | | |
| | | public class AgvTaskUtils { |
| | | |
| | | /** |
| | | * 加载和解析AGV状态列表 |
| | | * |
| | | * @param filePath 文件地址 |
| | | * @return AGV状态列表 |
| | | */ |
| | | public static List<AGVStatus> loadAgvStatus(String filePath) { |
| | | ObjectMapper objectMapper = new ObjectMapper(); |
| | | try { |
| | | // 直接解析JSON数组为List<AGVStatus> |
| | | return objectMapper.readValue( |
| | | new File(filePath), |
| | | new TypeReference<List<AGVStatus>>() { |
| | | } |
| | | ); |
| | | } catch (FileNotFoundException e) { |
| | | System.err.println("AGV状态列表文件不存在: " + e.getMessage()); |
| | | } catch (IOException e) { |
| | | System.err.println("路径AGV状态列表文件读取错误: " + e.getMessage()); |
| | | } catch (Exception e) { |
| | | System.err.println("加载AGV状态列表文件失败: " + e.getMessage()); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 加载和解析任务列表 |
| | | * |
| | | * @param filePath 文件地址 |
| | | * @return 任务列表 |
| | | */ |
| | | public static List<TaskData> loadTaskList(String filePath) { |
| | | ObjectMapper objectMapper = new ObjectMapper(); |
| | | try { |
| | | // 直接解析JSON数组为List<TaskData>,使用TypeReference指定泛型类型,避免类型擦除导致的转换错误 |
| | | return objectMapper.readValue( |
| | | new File(filePath), |
| | | new TypeReference<List<TaskData>>() { |
| | | } |
| | | ); |
| | | } catch (FileNotFoundException e) { |
| | | System.err.println("任务列表文件不存在: " + e.getMessage()); |
| | | } catch (IOException e) { |
| | | System.err.println("任务列表文件读取错误: " + e.getMessage()); |
| | | } catch (Exception e) { |
| | | System.err.println("加载任务列表文件失败: " + e.getMessage()); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.algo.util; |
| | | |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | |
| | | import java.io.*; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * JSON文件读取工具类 |
| | | * 用于读取环境配置和路径映射文件 |
| | | */ |
| | | public class JsonUtils { |
| | | |
| | | /** |
| | | * 读取JSON文件内容 |
| | | * |
| | | * @param filePath 文件路径 |
| | | * @return JSON字符串内容 |
| | | * @throws IOException 文件读取异常 |
| | | */ |
| | | public static String readJsonFile(String filePath) throws IOException { |
| | | StringBuilder content = new StringBuilder(); |
| | | try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | content.append(line).append("\n"); |
| | | } |
| | | } |
| | | return content.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 解析路径映射JSON内容 |
| | | * 正确解析path_mapping.json的实际格式:{"path_id_to_coordinates": {...}} |
| | | * |
| | | * @param jsonContent JSON内容 |
| | | * @return 路径映射Map,key为路径编号,value为坐标信息 |
| | | */ |
| | | public static Map<String, Map<String, Integer>> parsePathMapping(String jsonContent) { |
| | | Map<String, Map<String, Integer>> pathMapping = new HashMap<>(); |
| | | |
| | | try { |
| | | // 找到path_id_to_coordinates部分 |
| | | String pathIdSection = extractJsonSection(jsonContent, "path_id_to_coordinates"); |
| | | if (pathIdSection == null) { |
| | | System.err.println("未找到path_id_to_coordinates部分"); |
| | | return pathMapping; |
| | | } |
| | | |
| | | String[] lines = pathIdSection.split("\n"); |
| | | String currentKey = null; |
| | | |
| | | for (String line : lines) { |
| | | line = line.trim(); |
| | | |
| | | // 查找路径ID |
| | | if (line.startsWith("\"") && line.contains("\":[")) { |
| | | int endIndex = line.indexOf("\":["); |
| | | currentKey = line.substring(1, endIndex); |
| | | pathMapping.put(currentKey, new HashMap<>()); |
| | | } |
| | | |
| | | // 查找坐标信息 |
| | | if (currentKey != null && line.contains("\"x\":")) { |
| | | String xStr = line.substring(line.indexOf("\"x\":") + 4); |
| | | xStr = xStr.substring(0, xStr.indexOf(",")).trim(); |
| | | try { |
| | | int x = Integer.parseInt(xStr); |
| | | pathMapping.get(currentKey).put("x", x); |
| | | } catch (NumberFormatException e) { |
| | | // 忽略解析错误 |
| | | } |
| | | } |
| | | |
| | | if (currentKey != null && line.contains("\"y\":")) { |
| | | String yStr = line.substring(line.indexOf("\"y\":") + 4); |
| | | if (yStr.contains(",")) { |
| | | yStr = yStr.substring(0, yStr.indexOf(",")).trim(); |
| | | } else if (yStr.contains("}")) { |
| | | yStr = yStr.substring(0, yStr.indexOf("}")).trim(); |
| | | } |
| | | try { |
| | | int y = Integer.parseInt(yStr); |
| | | pathMapping.get(currentKey).put("y", y); |
| | | } catch (NumberFormatException e) { |
| | | // 忽略解析错误 |
| | | } |
| | | } |
| | | } |
| | | |
| | | System.out.println("成功解析路径映射,包含 " + pathMapping.size() + " 个路径点"); |
| | | |
| | | } catch (Exception e) { |
| | | System.err.println("解析路径映射JSON时发生错误: " + e.getMessage()); |
| | | } |
| | | |
| | | return pathMapping; |
| | | } |
| | | |
| | | /** |
| | | * 加载和解析路径映射文件 |
| | | * 正确解析path_mapping.json的实际格式:{"path_id_to_coordinates": {...}} |
| | | * |
| | | * @param filePath 文件地址 |
| | | * @return 路径映射Map,key为路径编号,value为坐标信息 |
| | | */ |
| | | public static Map<String, Map<String, Integer>> loadPathMapping(String filePath) { |
| | | // 转换为目标结构 Map<String, Map<String, Integer>> |
| | | Map<String, Map<String, Integer>> pathMapping = new HashMap<>(); |
| | | ObjectMapper objectMapper = new ObjectMapper(); |
| | | try { |
| | | // 先解析为顶层Map<String, Object> |
| | | Map<String, Object> topLevelMap = objectMapper.readValue( |
| | | new File(filePath), |
| | | Map.class |
| | | ); |
| | | |
| | | if (!topLevelMap.containsKey("path_id_to_coordinates")) { |
| | | System.err.println("未找到path_id_to_coordinates部分"); |
| | | return pathMapping; |
| | | } |
| | | |
| | | // 处理 path_id_to_coordinates(将坐标列表转换为第一个坐标的path_id映射) |
| | | Map<String, Object> pathIdCoords = (Map<String, Object>) topLevelMap.get("path_id_to_coordinates"); |
| | | for (Map.Entry<String, Object> entry : pathIdCoords.entrySet()) { |
| | | // 保存方式: 路径ID:{"x": , "y":} |
| | | String pathId = entry.getKey(); |
| | | Object coordsObj = entry.getValue(); |
| | | if (coordsObj instanceof List) { |
| | | List<?> coordsList = (List<?>) coordsObj; |
| | | if (!coordsList.isEmpty()) { |
| | | Map<?, ?> coordMap = (Map<?, ?>) coordsList.get(0); |
| | | int x = ((Number) coordMap.get("x")).intValue(); |
| | | int y = ((Number) coordMap.get("y")).intValue(); |
| | | Map<String, Integer> pointMap = new HashMap<>(); |
| | | pointMap.put("x", x); |
| | | pointMap.put("y", y); |
| | | pathMapping.put(pathId, pointMap); |
| | | } |
| | | } |
| | | } |
| | | |
| | | System.out.println("成功解析路径映射,包含 " + pathMapping.size() + " 个路径点"); |
| | | } catch (FileNotFoundException e) { |
| | | System.err.println("路径映射文件不存在: " + e.getMessage()); |
| | | } catch (IOException e) { |
| | | System.err.println("路径映射文件读取错误: " + e.getMessage()); |
| | | } catch (Exception e) { |
| | | System.err.println("加载路径映射文件失败: " + e.getMessage()); |
| | | } |
| | | return pathMapping; |
| | | } |
| | | |
| | | /** |
| | | * 解析环境配置JSON内容 |
| | | * 解析environment.json中的stations信息 |
| | | * |
| | | * @param jsonContent JSON内容 |
| | | * @return 环境配置Map |
| | | */ |
| | | public static Map<String, Object> parseEnvironmentConfig(String jsonContent) { |
| | | Map<String, Object> config = new HashMap<>(); |
| | | |
| | | try { |
| | | // 解析width |
| | | if (jsonContent.contains("\"width\":")) { |
| | | String widthStr = jsonContent.substring(jsonContent.indexOf("\"width\":") + 8); |
| | | widthStr = widthStr.substring(0, widthStr.indexOf(",")).trim(); |
| | | try { |
| | | config.put("width", Integer.parseInt(widthStr)); |
| | | } catch (NumberFormatException e) { |
| | | config.put("width", 78); |
| | | } |
| | | } |
| | | |
| | | // 解析height |
| | | if (jsonContent.contains("\"height\":")) { |
| | | String heightStr = jsonContent.substring(jsonContent.indexOf("\"height\":") + 9); |
| | | heightStr = heightStr.substring(0, heightStr.indexOf(",")).trim(); |
| | | try { |
| | | config.put("height", Integer.parseInt(heightStr)); |
| | | } catch (NumberFormatException e) { |
| | | config.put("height", 50); |
| | | } |
| | | } |
| | | |
| | | // 解析stations信息 |
| | | Map<String, Map<String, Object>> stations = parseStations(jsonContent); |
| | | config.put("stations", stations); |
| | | config.put("stationCount", stations.size()); |
| | | |
| | | System.out.println("成功解析环境配置,包含 " + stations.size() + " 个工作站"); |
| | | |
| | | } catch (Exception e) { |
| | | System.err.println("解析环境配置JSON时发生错误: " + e.getMessage()); |
| | | } |
| | | |
| | | return config; |
| | | } |
| | | |
| | | /** |
| | | * 加载和解析环境配置文件 |
| | | * 解析environment.json中的stations信息 |
| | | * |
| | | * @param filePath 文件地址 |
| | | * @return 环境配置Map |
| | | */ |
| | | public static Map<String, Object> loadEnvironment(String filePath) { |
| | | // 转换为目标结构 Map<String, Object> |
| | | Map<String, Object> environmentMap = new HashMap<>(); |
| | | ObjectMapper objectMapper = new ObjectMapper(); |
| | | try { |
| | | // 先解析为顶层Map<String, Object> |
| | | Map<String, Object> topLevelMap = objectMapper.readValue( |
| | | new File(filePath), |
| | | Map.class |
| | | ); |
| | | |
| | | // 解析width |
| | | if (topLevelMap.containsKey("width")) { |
| | | environmentMap.put("width", Integer.parseInt(topLevelMap.get("width").toString())); |
| | | } else { |
| | | environmentMap.put("width", 78); |
| | | } |
| | | |
| | | // 解析height |
| | | if (topLevelMap.containsKey("width")) { |
| | | environmentMap.put("height", Integer.parseInt(topLevelMap.get("height").toString())); |
| | | } else { |
| | | environmentMap.put("height", 50); |
| | | } |
| | | |
| | | // 解析stations信息 |
| | | if (topLevelMap.containsKey("stations")) { |
| | | Map<String, Map<String, Object>> stations = new HashMap<>(); |
| | | Map<String, Object> stationMap = (Map<String, Object>) topLevelMap.get("stations"); |
| | | for (Map.Entry<String, Object> stationEntry : stationMap.entrySet()) { |
| | | // 工作站ID |
| | | String pathId = stationEntry.getKey(); |
| | | Map<String, Object> stationInfo = (Map<String, Object>) stationEntry.getValue(); |
| | | Map<String, Object> map = new HashMap<>(); |
| | | // 解析capacity |
| | | if (stationInfo.containsKey("capacity")) { |
| | | map.put("capacity", Integer.parseInt(stationInfo.get("capacity").toString())); |
| | | } |
| | | // 解析load_position和unload_position |
| | | if (stationInfo.containsKey("load_position")) { |
| | | List<Integer> loadPos = (List<Integer>) stationInfo.get("load_position"); |
| | | map.put("load_position", loadPos); |
| | | } |
| | | if (stationInfo.containsKey("unload_position")) { |
| | | List<Integer> unloadPos = (List<Integer>) stationInfo.get("unload_position"); |
| | | map.put("unload_position", unloadPos); |
| | | } |
| | | stations.put(pathId, map); |
| | | } |
| | | environmentMap.put("stations", stations); |
| | | environmentMap.put("stationCount", stations.size()); |
| | | System.out.println("成功解析环境配置,包含 " + stations.size() + " 个工作站"); |
| | | } |
| | | } catch (FileNotFoundException e) { |
| | | System.err.println("环境配置文件不存在: " + e.getMessage()); |
| | | } catch (IOException e) { |
| | | System.err.println("环境配置文件读取错误: " + e.getMessage()); |
| | | } catch (Exception e) { |
| | | System.err.println("加载环境配置文件失败: " + e.getMessage()); |
| | | } |
| | | return environmentMap; |
| | | } |
| | | |
| | | /** |
| | | * 解析stations信息 |
| | | * |
| | | * @param jsonContent JSON内容 |
| | | * @return stations Map |
| | | */ |
| | | private static Map<String, Map<String, Object>> parseStations(String jsonContent) { |
| | | Map<String, Map<String, Object>> stations = new HashMap<>(); |
| | | |
| | | try { |
| | | String stationsSection = extractJsonSection(jsonContent, "stations"); |
| | | if (stationsSection == null) { |
| | | return stations; |
| | | } |
| | | |
| | | String[] lines = stationsSection.split("\n"); |
| | | String currentStation = null; |
| | | |
| | | for (String line : lines) { |
| | | line = line.trim(); |
| | | |
| | | // 查找工作站ID |
| | | if (line.startsWith("\"") && line.contains("\":{")) { |
| | | int endIndex = line.indexOf("\":{"); |
| | | currentStation = line.substring(1, endIndex); |
| | | stations.put(currentStation, new HashMap<>()); |
| | | } |
| | | |
| | | // 解析capacity |
| | | if (currentStation != null && line.contains("\"capacity\":")) { |
| | | String capacityStr = line.substring(line.indexOf("\"capacity\":") + 11); |
| | | capacityStr = capacityStr.substring(0, capacityStr.indexOf(",")).trim(); |
| | | try { |
| | | int capacity = Integer.parseInt(capacityStr); |
| | | stations.get(currentStation).put("capacity", capacity); |
| | | } catch (NumberFormatException e) { |
| | | // 忽略解析错误 |
| | | } |
| | | } |
| | | |
| | | // 解析load_position和unload_position |
| | | if (currentStation != null && line.contains("\"load_position\":")) { |
| | | List<Integer> loadPos = parsePosition(stationsSection, currentStation, "load_position"); |
| | | if (loadPos != null) { |
| | | stations.get(currentStation).put("load_position", loadPos); |
| | | } |
| | | } |
| | | |
| | | if (currentStation != null && line.contains("\"unload_position\":")) { |
| | | List<Integer> unloadPos = parsePosition(stationsSection, currentStation, "unload_position"); |
| | | if (unloadPos != null) { |
| | | stations.get(currentStation).put("unload_position", unloadPos); |
| | | } |
| | | } |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | System.err.println("解析stations信息时发生错误: " + e.getMessage()); |
| | | } |
| | | |
| | | return stations; |
| | | } |
| | | |
| | | /** |
| | | * 解析位置信息 |
| | | * |
| | | * @param content JSON内容 |
| | | * @param stationId 工作站ID |
| | | * @param positionType 位置类型(load_position或unload_position) |
| | | * @return 位置坐标列表 |
| | | */ |
| | | private static List<Integer> parsePosition(String content, String stationId, String positionType) { |
| | | try { |
| | | String stationSection = content.substring(content.indexOf("\"" + stationId + "\":{")); |
| | | String positionSection = stationSection.substring(stationSection.indexOf("\"" + positionType + "\":")); |
| | | |
| | | String[] lines = positionSection.split("\n"); |
| | | List<Integer> position = new ArrayList<>(); |
| | | |
| | | for (String line : lines) { |
| | | line = line.trim(); |
| | | if (line.matches("\\d+,?")) { |
| | | String numStr = line.replaceAll("[,\\s]", ""); |
| | | try { |
| | | position.add(Integer.parseInt(numStr)); |
| | | } catch (NumberFormatException e) { |
| | | // 忽略解析错误 |
| | | } |
| | | } |
| | | if (line.contains("]")) { |
| | | break; |
| | | } |
| | | } |
| | | |
| | | return position.size() == 2 ? position : null; |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 提取JSON中的特定部分 |
| | | * |
| | | * @param jsonContent JSON内容 |
| | | * @param sectionName 部分名称 |
| | | * @return 提取的部分内容 |
| | | */ |
| | | private static String extractJsonSection(String jsonContent, String sectionName) { |
| | | try { |
| | | String startPattern = "\"" + sectionName + "\": {"; |
| | | int startIndex = jsonContent.indexOf(startPattern); |
| | | if (startIndex == -1) { |
| | | return null; |
| | | } |
| | | |
| | | int braceCount = 0; |
| | | int currentIndex = startIndex + startPattern.length() - 1; |
| | | |
| | | while (currentIndex < jsonContent.length()) { |
| | | char ch = jsonContent.charAt(currentIndex); |
| | | if (ch == '{') { |
| | | braceCount++; |
| | | } else if (ch == '}') { |
| | | braceCount--; |
| | | if (braceCount == 0) { |
| | | break; |
| | | } |
| | | } |
| | | currentIndex++; |
| | | } |
| | | |
| | | return jsonContent.substring(startIndex + startPattern.length() - 1, currentIndex + 1); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取路径点的坐标 |
| | | * |
| | | * @param pathId 路径点ID |
| | | * @param pathMapping 路径映射 |
| | | * @return 坐标数组 [x, y],如果未找到返回null |
| | | */ |
| | | public static int[] getCoordinate(String pathId, Map<String, Map<String, Integer>> pathMapping) { |
| | | Map<String, Integer> coordMap = pathMapping.get(pathId); |
| | | if (coordMap != null && coordMap.containsKey("x") && coordMap.containsKey("y")) { |
| | | return new int[]{coordMap.get("x"), coordMap.get("y")}; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 获取工作站信息 |
| | | * |
| | | * @param stationId 工作站ID |
| | | * @param environmentConfig 环境配置 |
| | | * @return 工作站信息Map |
| | | */ |
| | | @SuppressWarnings("unchecked") |
| | | public static Map<String, Object> getStationInfo(String stationId, Map<String, Object> environmentConfig) { |
| | | Map<String, Map<String, Object>> stations = |
| | | (Map<String, Map<String, Object>>) environmentConfig.get("stations"); |
| | | |
| | | if (stations != null) { |
| | | return stations.get(stationId); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 判断位置是否为工作站 |
| | | * |
| | | * @param position 位置字符串 |
| | | * @param environmentConfig 环境配置 |
| | | * @return 是否为工作站 |
| | | */ |
| | | public static boolean isStation(String position, Map<String, Object> environmentConfig) { |
| | | return getStationInfo(position, environmentConfig) != null; |
| | | } |
| | | |
| | | /** |
| | | * 计算两点之间的曼哈顿距离 |
| | | * |
| | | * @param coord1 坐标1 [x, y] |
| | | * @param coord2 坐标2 [x, y] |
| | | * @return 曼哈顿距离 |
| | | */ |
| | | public static double calculateManhattanDistance(int[] coord1, int[] coord2) { |
| | | if (coord1 == null || coord2 == null || coord1.length != 2 || coord2.length != 2) { |
| | | return Double.MAX_VALUE; |
| | | } |
| | | |
| | | return Math.abs(coord1[0] - coord2[0]) + Math.abs(coord1[1] - coord2[1]); |
| | | } |
| | | |
| | | /** |
| | | * 计算两点之间的欧几里得距离 |
| | | * |
| | | * @param coord1 坐标1 [x, y] |
| | | * @param coord2 坐标2 [x, y] |
| | | * @return 欧几里得距离 |
| | | */ |
| | | public static double calculateEuclideanDistance(int[] coord1, int[] coord2) { |
| | | if (coord1 == null || coord2 == null || coord1.length != 2 || coord2.length != 2) { |
| | | return Double.MAX_VALUE; |
| | | } |
| | | |
| | | int dx = coord1[0] - coord2[0]; |
| | | int dy = coord1[1] - coord2[1]; |
| | | return Math.sqrt(dx * dx + dy * dy); |
| | | } |
| | | } |
New file |
| | |
| | | org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.algo.service.PathPlanningService |
| | | |
New file |
| | |
| | | spring: |
| | | application: |
| | | name: web-server |
| | | |
| | | server: |
| | | port: 8083 |
| | | |
| | | |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project xmlns="http://maven.apache.org/POM/4.0.0" |
| | | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| | | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
| | | <modelVersion>4.0.0</modelVersion> |
| | | |
| | | <groupId>com.zy</groupId> |
| | | <artifactId>api</artifactId> |
| | | <version>1.0-SNAPSHOT</version> |
| | | |
| | | <properties> |
| | | <maven.compiler.source>8</maven.compiler.source> |
| | | <maven.compiler.target>8</maven.compiler.target> |
| | | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| | | <dubbo.version>2.7.8</dubbo.version> |
| | | <curator.version>4.2.0</curator.version> |
| | | <curator-recipes.version>2.8.0</curator-recipes.version> |
| | | <zookeeper.version>3.6.3</zookeeper.version> |
| | | </properties> |
| | | |
| | | |
| | | <dependencies> |
| | | |
| | | <!-- Dubbo Spring Boot Starter --> |
| | | <dependency> |
| | | <groupId>org.apache.dubbo</groupId> |
| | | <artifactId>dubbo-spring-boot-starter</artifactId> |
| | | <version>2.7.8</version> |
| | | </dependency> |
| | | |
| | | |
| | | <!-- Dubbo Spring Boot Starter --> |
| | | <dependency> |
| | | <groupId>org.apache.dubbo</groupId> |
| | | <artifactId>dubbo-spring-boot-starter</artifactId> |
| | | <version>${dubbo.version}</version> |
| | | |
| | | </dependency> |
| | | |
| | | <!-- zookeeper注册中心 需要导入zookeeper依赖 --> |
| | | <dependency> |
| | | <groupId>org.apache.zookeeper</groupId> |
| | | <artifactId>zookeeper</artifactId> |
| | | <version>${zookeeper.version}</version> |
| | | <!-- 提示与zookeeper中的slf4j-log4j12-1.7.29.jar 包冲突--> |
| | | <!--排除这个slf4j-log4j12--> |
| | | <exclusions> |
| | | <exclusion> |
| | | <groupId>org.slf4j</groupId> |
| | | <artifactId>slf4j-log4j12</artifactId> |
| | | </exclusion> |
| | | <exclusion> |
| | | <groupId>log4j</groupId> |
| | | <artifactId>log4j</artifactId> |
| | | </exclusion> |
| | | </exclusions> |
| | | </dependency> |
| | | |
| | | <!-- Zookeeper 客户端 --> |
| | | <dependency> |
| | | <groupId>org.apache.curator</groupId> |
| | | <artifactId>curator-framework</artifactId> |
| | | <version>${curator.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.apache.curator</groupId> |
| | | <artifactId>curator-recipes</artifactId> |
| | | <version>${curator-recipes.version}</version> |
| | | </dependency> |
| | | |
| | | <!-- mybatis-plus --> |
| | | <dependency> |
| | | <groupId>com.baomidou</groupId> |
| | | <artifactId>mybatis-plus</artifactId> |
| | | <version>3.5.12</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.baomidou</groupId> |
| | | <artifactId>mybatis-plus-generator</artifactId> |
| | | <version>3.4.1</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>org.projectlombok</groupId> |
| | | <artifactId>lombok</artifactId> |
| | | <version>1.18.20</version> |
| | | <scope>provided</scope> |
| | | </dependency> |
| | | |
| | | </dependencies> |
| | | |
| | | |
| | | </project> |
New file |
| | |
| | | package com.algo.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.io.Serializable; |
| | | |
| | | @Data |
| | | @TableName("sys_user") |
| | | public class User implements Serializable { |
| | | |
| | | private String username; |
| | | |
| | | /** |
| | | * 密码 |
| | | */ |
| | | private String password; |
| | | |
| | | /** |
| | | * 昵称 |
| | | */ |
| | | private String nickname; |
| | | |
| | | |
| | | } |
New file |
| | |
| | | package com.algo.service; |
| | | |
| | | import com.algo.entity.User; |
| | | |
| | | import java.util.List; |
| | | |
| | | //todo 这里写一个自定义注解,之后通过自定义注解来获取该接口和实现类将其注册到注册中心 |
| | | public interface UserService { |
| | | |
| | | /** |
| | | * 查询user |
| | | */ |
| | | public User queryUser(); |
| | | |
| | | void add(String key, String value); |
| | | |
| | | String get(String key); |
| | | |
| | | List<User> getDataByMySql(); |
| | | } |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project xmlns="http://maven.apache.org/POM/4.0.0" |
| | | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| | | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
| | | <modelVersion>4.0.0</modelVersion> |
| | | |
| | | |
| | | <parent> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-parent</artifactId> |
| | | <version>2.5.3</version> |
| | | <relativePath/> <!-- lookup parent from repository --> |
| | | </parent> |
| | | |
| | | |
| | | <groupId>com.zy</groupId> |
| | | <artifactId>rpc-server</artifactId> |
| | | <version>1.0-SNAPSHOT</version> |
| | | |
| | | <properties> |
| | | <maven.compiler.source>8</maven.compiler.source> |
| | | <maven.compiler.target>8</maven.compiler.target> |
| | | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| | | </properties> |
| | | |
| | | |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-web</artifactId> |
| | | </dependency> |
| | | |
| | | |
| | | <dependency> |
| | | <groupId>com.zy</groupId> |
| | | <artifactId>service</artifactId> |
| | | <version>1.0-SNAPSHOT</version> |
| | | </dependency> |
| | | </dependencies> |
| | | |
| | | |
| | | </project> |
New file |
| | |
| | | package com.algo; |
| | | |
| | | import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo; |
| | | import org.springframework.boot.SpringApplication; |
| | | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| | | |
| | | |
| | | @SpringBootApplication |
| | | @EnableDubbo |
| | | public class SpringBootRpcServerApplication { |
| | | |
| | | |
| | | public static void main(String[] args) { |
| | | SpringApplication.run(SpringBootRpcServerApplication.class, args); |
| | | } |
| | | } |
| | | |
New file |
| | |
| | | spring: |
| | | application: |
| | | name: rpc-server |
| | | mvc: |
| | | static-path-pattern: /** |
| | | |
| | | servlet: |
| | | multipart: |
| | | maxFileSize: 100MB |
| | | maxRequestSize: 100MB |
| | | jmx: |
| | | enabled: false |
| | | datasource: |
| | | driver-class-name: com.mysql.jdbc.Driver |
| | | url: jdbc:mysql://localhost:3306/jbly?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai |
| | | username: root |
| | | password: xltys1995 |
| | | type: com.alibaba.druid.pool.DruidDataSource |
| | | druid: |
| | | initial-size: 5 |
| | | min-idle: 5 |
| | | max-active: 20 |
| | | max-wait: 30000 |
| | | time-between-eviction-runs-millis: 60000 |
| | | min-evictable-idle-time-millis: 300000 |
| | | test-while-idle: true |
| | | test-on-borrow: true |
| | | test-on-return: false |
| | | remove-abandoned: true |
| | | remove-abandoned-timeout: 1800 |
| | | #pool-prepared-statements: false |
| | | #max-pool-prepared-statement-per-connection-size: 20 |
| | | filters: stat, wall |
| | | validation-query: SELECT 'x' |
| | | aop-patterns: com.zy.*.*.service.* |
| | | stat-view-servlet: |
| | | url-pattern: /druid/* |
| | | reset-enable: true |
| | | login-username: admin |
| | | login-password: admin |
| | | enabled: true |
| | | |
| | | redis: |
| | | host: localhost |
| | | password: xltys1995 |
| | | port: 6379 |
| | | max: 30 |
| | | min: 10 |
| | | timeout: 5000 |
| | | index: 3 |
| | | |
| | | |
| | | |
| | | server: |
| | | port: 8082 |
| | | |
| | | dubbo: |
| | | application: |
| | | name: rpc-server |
| | | registry: |
| | | address: zookeeper://127.0.0.1:2181 |
| | | # protocol: zookeeper |
| | | # password: dreamtech |
| | | # username: zk_user |
| | | protocol: |
| | | name: dubbo |
| | | port: 20880 |
| | | # monitor: |
| | | # protocol: registry |
| | | |
| | | |
| | | mybatis-plus: |
| | | mapper-locations: classpath:mapper/*/*.xml |
| | | # global-config: |
| | | # field-strategy: 0 |
| | | configuration: |
| | | # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl |
| | | map-underscore-to-camel-case: true |
| | | cache-enabled: true |
| | | global-config: |
| | | :banner: false |
| | | db-config: |
| | | id-type: auto |
| | | logic-delete-value: 1 |
| | | logic-not-delete-value: 0 |
| | |
| | | login-username: admin |
| | | login-password: admin |
| | | enabled: true |
| | | servlet: |
| | | multipart: |
| | | maxFileSize: 100MB |
| | | maxRequestSize: 100MB |
| | | jmx: |
| | | enabled: false |
| | | |
| | | redis: |
| | | host: localhost |
| | |
| | | timeout: 5000 |
| | | index: 3 |
| | | |
| | | |
| | | |
| | | server: |
| | | port: 8082 |
| | | |
| | | dubbo: |
| | | application: |
| | | name: rpc-server |
| | | registry: |
| | | address: zookeeper://127.0.0.1:2181 |
| | | # protocol: zookeeper |
| | | # password: dreamtech |
| | | # username: zk_user |
| | | protocol: |
| | | name: dubbo |
| | | port: 20880 |
| | | # monitor: |
| | | # protocol: registry |
| | | |
| | | |
| | | mybatis-plus: |
| | | mapper-locations: classpath:mapper/*/*.xml |
| | | # global-config: |
| | | # field-strategy: 0 |
| | | configuration: |
| | | # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl |
| | | map-underscore-to-camel-case: true |
| | | cache-enabled: true |
| | | global-config: |
| | | :banner: false |
| | | db-config: |
| | | id-type: auto |
| | | logic-delete-value: 1 |
| | | logic-not-delete-value: 0 |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project xmlns="http://maven.apache.org/POM/4.0.0" |
| | | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| | | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
| | | <modelVersion>4.0.0</modelVersion> |
| | | |
| | | <groupId>com.zy</groupId> |
| | | <artifactId>service</artifactId> |
| | | <version>1.0-SNAPSHOT</version> |
| | | |
| | | <properties> |
| | | <maven.compiler.source>8</maven.compiler.source> |
| | | <maven.compiler.target>8</maven.compiler.target> |
| | | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| | | |
| | | </properties> |
| | | |
| | | |
| | | <dependencies> |
| | | |
| | | <dependency> |
| | | <groupId>com.zy</groupId> |
| | | <artifactId>api</artifactId> |
| | | <version>1.0-SNAPSHOT</version> |
| | | </dependency> |
| | | |
| | | <!-- 集成redis依赖 --> |
| | | <dependency> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-data-redis</artifactId> |
| | | <version>2.0.3.RELEASE</version> |
| | | </dependency> |
| | | |
| | | <!-- mybatis-plus --> |
| | | <dependency> |
| | | <groupId>com.baomidou</groupId> |
| | | <artifactId>mybatis-plus-boot-starter</artifactId> |
| | | <version>3.4.1</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.baomidou</groupId> |
| | | <artifactId>mybatis-plus-generator</artifactId> |
| | | <version>3.4.1</version> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>mysql</groupId> |
| | | <artifactId>mysql-connector-java</artifactId> |
| | | <version>5.1.47</version> |
| | | </dependency> |
| | | |
| | | <!-- druid --> |
| | | <dependency> |
| | | <groupId>com.alibaba</groupId> |
| | | <artifactId>druid-spring-boot-starter</artifactId> |
| | | <version>1.2.6</version> |
| | | </dependency> |
| | | |
| | | </dependencies> |
| | | </project> |
New file |
| | |
| | | package com.algo.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.algo.entity.User; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.springframework.stereotype.Repository; |
| | | |
| | | @Mapper |
| | | @Repository |
| | | public interface UserMapper extends BaseMapper<User> { |
| | | |
| | | |
| | | } |
New file |
| | |
| | | package com.algo.support; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.algo.entity.User; |
| | | import com.algo.mapper.UserMapper; |
| | | import com.algo.service.UserService; |
| | | import org.apache.dubbo.config.annotation.DubboService; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.data.redis.core.RedisTemplate; |
| | | |
| | | import java.util.List; |
| | | |
| | | |
| | | @DubboService // 该注解用于将当前类对象存入spring容器中,既可以本项目服务使用,父接口注解能力也可以提供给其他项目使用 |
| | | public class UserServiceSupport implements UserService { |
| | | |
| | | @Autowired |
| | | private RedisTemplate<String, String> redisTemplate; |
| | | |
| | | @Autowired |
| | | private UserMapper userMapper; |
| | | |
| | | /** |
| | | * 查询user |
| | | */ |
| | | @Override |
| | | public User queryUser() { |
| | | User user = new User(); |
| | | user.setNickname("1212"); |
| | | user.setUsername("张三"); |
| | | return user; |
| | | } |
| | | |
| | | @Override |
| | | public void add(String key, String value) { |
| | | redisTemplate.opsForValue().set(key, value); |
| | | } |
| | | |
| | | @Override |
| | | public String get(String key) { |
| | | return redisTemplate.opsForValue().get(key); |
| | | } |
| | | |
| | | @Override |
| | | public List<User> getDataByMySql() { |
| | | List<User> users = userMapper.selectList(new QueryWrapper<>()); |
| | | return users; |
| | | } |
| | | } |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project xmlns="http://maven.apache.org/POM/4.0.0" |
| | | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| | | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
| | | <modelVersion>4.0.0</modelVersion> |
| | | |
| | | <parent> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-parent</artifactId> |
| | | <version>2.5.3</version> |
| | | <relativePath/> <!-- lookup parent from repository --> |
| | | </parent> |
| | | |
| | | |
| | | <groupId>com.zy</groupId> |
| | | <artifactId>web-server</artifactId> |
| | | <version>1.0-SNAPSHOT</version> |
| | | |
| | | |
| | | <properties> |
| | | <maven.compiler.source>8</maven.compiler.source> |
| | | <maven.compiler.target>8</maven.compiler.target> |
| | | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| | | </properties> |
| | | |
| | | |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>org.springframework.boot</groupId> |
| | | <artifactId>spring-boot-starter-web</artifactId> |
| | | </dependency> |
| | | |
| | | |
| | | <dependency> |
| | | <groupId>com.zy</groupId> |
| | | <artifactId>api</artifactId> |
| | | <version>1.0-SNAPSHOT</version> |
| | | </dependency> |
| | | </dependencies> |
| | | |
| | | </project> |
New file |
| | |
| | | package com.algo; |
| | | |
| | | import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo; |
| | | import org.springframework.boot.SpringApplication; |
| | | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| | | |
| | | |
| | | @SpringBootApplication |
| | | @EnableDubbo |
| | | public class SpringBootAplication { |
| | | |
| | | |
| | | public static void main(String[] args) { |
| | | SpringApplication.run(SpringBootAplication.class, args); |
| | | } |
| | | } |
| | | |
New file |
| | |
| | | package com.algo.controller; |
| | | |
| | | |
| | | import com.algo.entity.User; |
| | | import com.algo.service.UserService; |
| | | import org.apache.dubbo.config.annotation.DubboReference; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import java.util.List; |
| | | |
| | | @RestController |
| | | public class UserController { |
| | | |
| | | @DubboReference(timeout = 1000, check = false)//远程注入 |
| | | private UserService userService; |
| | | |
| | | |
| | | @GetMapping("/user") |
| | | public String user() { |
| | | User user = userService.queryUser(); |
| | | return user.toString(); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | public String get() { |
| | | return userService.get("user"); |
| | | } |
| | | |
| | | @GetMapping("/add") |
| | | public String add() { |
| | | userService.add("user", "123"); |
| | | return "ok"; |
| | | } |
| | | |
| | | @GetMapping("/user3") |
| | | public List<User> user3() { |
| | | return userService.getDataByMySql(); |
| | | |
| | | } |
| | | } |
New file |
| | |
| | | spring: |
| | | application: |
| | | name: web-server |
| | | |
| | | server: |
| | | port: 8083 |
| | | |
| | | dubbo: |
| | | protocol: |
| | | name: dubbo |
| | | port: 20885 |
| | | application: |
| | | name: web-server |
| | | registry: |
| | | address: zookeeper://127.0.0.1:2181 |
| | | # password: dreamtech |
| | | # username: zk_user |
| | | # monitor: |
| | | # protocol: registry |