From 368b9eca39e66fa173bcbffd43fa91ec51d9060d Mon Sep 17 00:00:00 2001 From: topfive Date: Thu, 15 May 2025 22:02:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=AE=9E=E7=8E=B01.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 + .../__pycache__/__init__.cpython-37.pyc | Bin 0 -> 528 bytes .../communication_manager.cpython-37.pyc | Bin 0 -> 6234 bytes .../database_handler.cpython-37.pyc | Bin 0 -> 6949 bytes .../drone_connection_manager.cpython-37.pyc | Bin 0 -> 5420 bytes .../mavlink_handler.cpython-37.pyc | Bin 0 -> 3177 bytes .../__pycache__/message_queue.cpython-37.pyc | Bin 0 -> 2739 bytes .../__pycache__/database.cpython-312.pyc | Bin 0 -> 13205 bytes Src/command_center/database/database.py | 271 ++++ Src/command_center/database/db_config.json | 6 + Src/command_center/database/table.sql | 17 + Src/command_center/main.py | 140 +- .../__pycache__/astar.cpython-312.pyc | Bin 0 -> 11247 bytes .../__pycache__/astar.cpython-37.pyc | Bin 0 -> 7218 bytes .../genetic_algorithm.cpython-312.pyc | Bin 0 -> 20363 bytes .../genetic_algorithm.cpython-37.pyc | Bin 0 -> 11911 bytes .../__pycache__/hybrid_astar.cpython-37.pyc | Bin 0 -> 6615 bytes .../__pycache__/path_planner.cpython-312.pyc | Bin 5497 -> 9847 bytes .../__pycache__/path_planner.cpython-37.pyc | Bin 0 -> 6725 bytes .../__pycache__/rrt_algorithm.cpython-312.pyc | Bin 0 -> 14020 bytes .../__pycache__/rrt_algorithm.cpython-37.pyc | Bin 0 -> 8350 bytes Src/command_center/path_planning/astar.py | 283 ++++ .../path_planning/genetic_algorithm.py | 475 ++++++ .../path_planning/path_planner.py | 227 ++- .../path_planning/rrt_algorithm.py | 316 ++++ .../ui/__pycache__/__init__.cpython-312.pyc | Bin 149 -> 230 bytes .../ui/__pycache__/__init__.cpython-37.pyc | Bin 0 -> 162 bytes .../algorithm_config_view.cpython-37.pyc | Bin 0 -> 2906 bytes .../__pycache__/base_map_view.cpython-312.pyc | Bin 17687 -> 19749 bytes .../__pycache__/base_map_view.cpython-37.pyc | Bin 0 -> 9240 bytes .../drone_detail_view.cpython-312.pyc | Bin 21035 -> 21116 bytes .../drone_detail_view.cpython-37.pyc | Bin 0 -> 10434 bytes .../drone_list_view.cpython-312.pyc | Bin 28046 -> 29020 bytes .../drone_list_view.cpython-37.pyc | Bin 0 -> 14735 bytes .../drone_management.cpython-312.pyc | Bin 0 -> 14222 bytes .../drone_management.cpython-37.pyc | Bin 0 -> 6976 bytes .../__pycache__/drone_manager.cpython-312.pyc | Bin 0 -> 11045 bytes .../__pycache__/drone_manager.cpython-37.pyc | Bin 0 -> 7690 bytes .../integrated_map_view.cpython-312.pyc | Bin 0 -> 63380 bytes .../integrated_map_view.cpython-37.pyc | Bin 0 -> 28583 bytes .../ui/__pycache__/login_view.cpython-312.pyc | Bin 4374 -> 5151 bytes .../ui/__pycache__/login_view.cpython-37.pyc | Bin 0 -> 2468 bytes .../map_data_model.cpython-312.pyc | Bin 2441 -> 5984 bytes .../__pycache__/map_data_model.cpython-37.pyc | Bin 0 -> 4280 bytes .../path_layer_view.cpython-37.pyc | Bin 0 -> 2764 bytes .../path_planning_view.cpython-37.pyc | Bin 0 -> 7269 bytes .../simple_map_view.cpython-37.pyc | Bin 0 -> 2582 bytes .../status_dashboard.cpython-312.pyc | Bin 5594 -> 5675 bytes .../status_dashboard.cpython-37.pyc | Bin 0 -> 3479 bytes .../threat_layer_view.cpython-37.pyc | Bin 0 -> 4860 bytes Src/command_center/ui/base_map_view.py | 45 + Src/command_center/ui/drone_list_view.py | 161 +- Src/command_center/ui/drone_management.py | 251 ++++ Src/command_center/ui/drone_manager.py | 278 ++++ Src/command_center/ui/integrated_map_view.py | 1299 +++++++++++++++++ Src/command_center/ui/login_view.py | 32 +- Src/command_center/ui/map_data_model.py | 85 +- 57 files changed, 3724 insertions(+), 165 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 Src/command_center/communication/__pycache__/__init__.cpython-37.pyc create mode 100644 Src/command_center/communication/__pycache__/communication_manager.cpython-37.pyc create mode 100644 Src/command_center/communication/__pycache__/database_handler.cpython-37.pyc create mode 100644 Src/command_center/communication/__pycache__/drone_connection_manager.cpython-37.pyc create mode 100644 Src/command_center/communication/__pycache__/mavlink_handler.cpython-37.pyc create mode 100644 Src/command_center/communication/__pycache__/message_queue.cpython-37.pyc create mode 100644 Src/command_center/database/__pycache__/database.cpython-312.pyc create mode 100644 Src/command_center/database/database.py create mode 100644 Src/command_center/database/db_config.json create mode 100644 Src/command_center/database/table.sql create mode 100644 Src/command_center/path_planning/__pycache__/astar.cpython-312.pyc create mode 100644 Src/command_center/path_planning/__pycache__/astar.cpython-37.pyc create mode 100644 Src/command_center/path_planning/__pycache__/genetic_algorithm.cpython-312.pyc create mode 100644 Src/command_center/path_planning/__pycache__/genetic_algorithm.cpython-37.pyc create mode 100644 Src/command_center/path_planning/__pycache__/hybrid_astar.cpython-37.pyc create mode 100644 Src/command_center/path_planning/__pycache__/path_planner.cpython-37.pyc create mode 100644 Src/command_center/path_planning/__pycache__/rrt_algorithm.cpython-312.pyc create mode 100644 Src/command_center/path_planning/__pycache__/rrt_algorithm.cpython-37.pyc create mode 100644 Src/command_center/path_planning/astar.py create mode 100644 Src/command_center/path_planning/genetic_algorithm.py create mode 100644 Src/command_center/path_planning/rrt_algorithm.py create mode 100644 Src/command_center/ui/__pycache__/__init__.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/algorithm_config_view.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/base_map_view.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/drone_detail_view.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/drone_list_view.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/drone_management.cpython-312.pyc create mode 100644 Src/command_center/ui/__pycache__/drone_management.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/drone_manager.cpython-312.pyc create mode 100644 Src/command_center/ui/__pycache__/drone_manager.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/integrated_map_view.cpython-312.pyc create mode 100644 Src/command_center/ui/__pycache__/integrated_map_view.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/login_view.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/map_data_model.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/path_layer_view.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/path_planning_view.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/simple_map_view.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/status_dashboard.cpython-37.pyc create mode 100644 Src/command_center/ui/__pycache__/threat_layer_view.cpython-37.pyc create mode 100644 Src/command_center/ui/drone_management.py create mode 100644 Src/command_center/ui/drone_manager.py create mode 100644 Src/command_center/ui/integrated_map_view.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..9ddf6b28 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.ignoreCMakeListsMissing": true +} \ No newline at end of file diff --git a/Src/command_center/communication/__pycache__/__init__.cpython-37.pyc b/Src/command_center/communication/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a1a5d2249c90184d9285a39f404e8b0ca883fcd3 GIT binary patch literal 528 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 literal 0 HcmV?d00001 diff --git a/Src/command_center/communication/__pycache__/database_handler.cpython-37.pyc b/Src/command_center/communication/__pycache__/database_handler.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..099a6fa31844649bc24cd86c5ed8864648157a50 GIT binary patch literal 6949 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 Q(td|vj-*K4@O?x1Kh;?Lng9R* literal 0 HcmV?d00001 diff --git a/Src/command_center/communication/__pycache__/mavlink_handler.cpython-37.pyc b/Src/command_center/communication/__pycache__/mavlink_handler.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..efeead62a1011eb94aed197d0082209e02b38a45 GIT binary patch literal 3177 zcmZ`*>u(&@6~Fh+bJzC9c7l_Xw7Afs4yvLA6(L!aLh~pR#1*nt!pQ1$J$GWSJ+r&q znKjs2M~!UJl(Zq0pyiQ*T_qw=RSu#G2t@fm=4-RI^Q~X{rJwpcGrODY#_Z~z`+l6? zIlpt}=R-q9hF|H)N9Lme#{Ny6&QC+*8I*VuLNdui7IByJye(XjjcKliT7-IJxJG2U zW@Nb*)@V`>?Wo`uIQs>YhBViiG}nY%lzfUg)*YP5CLJxcPn5&3JR1h7ezM{xsrGDj z`3}dV!_&btK^&Lof)^V>Bk*ZQ=cl9b3`)EN5iplahW8u}G%2ofx_jMl^}vt@dZx72 zb=Q=(EUYuvl0`Xy+LnWI2z5c0k z(c32vL5J_8r8DKFuu@%ou3VL2p#HbNA?p*zAAj=bG5n8D9RJA^PaK09BYv$~4g921 zt9o&wD%Ck(;Mw$}n#u{3_%VnkZ!!-EL-ItdQLRZD)3Y(7LL;VRxL-`3b}UuErnGqX z24&K~P?RI*y+Y8XmTFY1xHmPEc@>mpTJVATY}sFo9g*sB5T45p`k1Lw9 zGKMu;Vm0|1@2=9P;JvH-JU`1`W>s+(Jz#G(_T#eFS6}Y@{nD9dfAs>KVE3C3wl80A zJb>|@k?o6DT5o(fF+P3&RO{NOyZ^jCF@Ac|u~Q2`HIho`j&=~c$EajwMT)wmSIa^Hy*+vtt(rtH*U0%+rD*i`@$CB+7@mi+B z<%da&1tGZU0p{RzQ=$Ip8m&n<5ZGGp;>p3E=i#K=PTknB(($QTT%<_`u zdZ5Vh6}|?TtJNDxTTk@}22vZ!=8O82{XS-YiW1upOxSR<5?}dVzmjL`ycGyTx+QF1PJ#}!sq=KY zME#IN@2k@h70gv)dU`tJK`WbSqtwngL;-rKt3~A{`T(d(MVxUd3(+Tyd#HU>$5xruL6dqPCmcPG zq69>0F20Hg9q&blbdWCToc0`6w|QYA?+AXRlYJiS1O47z-3Ub~h{_2%hCX-*TtLAa zqv-5j{=D_(pWDQL=fl>;zraxB&)*UyN67PgKA5KH+;8!M`xcY8k=HxJ^kGviM}g<1 zMbC?BvJp~y(8Fg~Im~CWK%(fGsXkk)g?SdEq@%`3P}ESwCPnwFM@cv&NKN$^iMvjm zwJ5DC3xZUP>13M^+ob_}z_wAWVZAhv%{v3`NPW3&;G?MNb0agmdXUa|7y>zVxsFhp m-%d1CPMjA!#`wR?QYvApM3Y6JKfr)MV-yuxgY1=d(Q9t&N=&L zYiokQlX`XU*_$RIf8e0;XwW$fuYU*{PB?W*Hu};mg)wtkLCb0dJ*!ik*Ic7uWDQE* zCS2#nIN`<^%f>i8LDI1$ID=%;EY#lcDxrC-6y%GZ?WSpHayt+~z91YFX*>pW4#VqT zfkqI#Fw6K3p>Z}zxyJQzBdZI88!(FT__z)ib&0qzMN21vHql9VVp8K9coHNxh$L?X z$rRrRlC8_5Hr@`S6z|}h#z}S~e~xd4ZyVgP1!TAKZLr!d;%d~%x5KD|w}3G_!{qzI z_w7D$ye!IMX%UbDL*fN{$j=W8hk?rY;1@wW0FIs>8ZV%y@_uG?!n13rMO`@%h= zw%;XkBb@SA4>a8ebG2Ky=jSHo=Puv8PyK^O3>=#auJ0YE% zQ!Es0k6VuL0wL9~?ByLBy_G9OMq5g){UvatW#zqmU|AdB7C#0Jp(&c6!<$#*v%evw zFpAD-!l(uIDU49r5z@

H1Xd;x}m~ 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 qql*5Cs<}t2|0F4=+qWa#}gj3`+M3V^1VV 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 literal 0 HcmV?d00001 diff --git a/Src/command_center/path_planning/__pycache__/genetic_algorithm.cpython-37.pyc b/Src/command_center/path_planning/__pycache__/genetic_algorithm.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1fb802ca1774c9eb108e612974d37ebf0c103f2 GIT binary patch literal 11911 zcmbVSd2Afld7pb{4=yQ+mMDvk=}Xosjv~>jt*DA5+p-h5iR{#doo*>?R{KWOQp;U( zW>#JjvxRHP78UEV zDrtrPs%aI!N!Hbx@p$cULXWzj#Hj($yID$|ZgOgpTkV@!TRO6iNBC+$gT zzV?m0B6X6g8& zRAc6p!J;g7L{A%RyA?sLwqIEzHRY(UX+=?IHfq~TEHM>f9V`htu|}_^oS@P|VU5H( zr*ziERvgjNarRZvZ)dtjuz780@er?1amot{7c`rOZ6UHIgq+Vvv~A0DYq zzFd3r>^z!r_3TDja{+xq9G zo<*018$X`C@*e)@UcJ!Lw`JM3_18|lQM>wF`=c9+iKqVb`ofDpnZ5YK*=yeevBjgU z&u(4oZT+*C&(uyGYbLlSW!N29Z97RxCna5!te~VDNh;!Nj#b$I`Mu6bHnGfa2a!Cy zZ4mD`d-2%p#TN$`u6;CnnGd;wv}}=S)`61E$zdYT)}dD z!?xVAT?`p^B_3_!COznE0S9{0G%>9LX4Pi2W6+4z2ve%&X}Kb~VCG@vu)JH^`>)j~ zda9G6ukIN$dNt;1)4CU%k*jep=Ec`Yo_SES3nE`N(HC{qh-WgD>K~WT-=O}W8j0gc z&qIw>6Gx@}GSirT#PkxorNeTy15cvSu^FYB^g5=aGg7s)@*Owkbsna%@q^N7**4w8 zbcdH@#*8wkRJ*DxP)^L?eTI_nr#4kEg;xT-d<^5T7fPpW7>B5y;}y zh1%QKYv-@kE`QKMslhw-PK$r`Mti4brb4$9KUsY9T7C;|X zMdl|m&Pb_52nU!;t+Z(g?bVd@QnH4Un<*j1ZTBHb$M#u{o3Tq}*GeaNKA3ho)=(>c zGN{Zrxe`=#x)bkGf4@wgRni?oW#o%2pF_LBi;f=|1;`a$C!H)>&onJrI%wEj&kXo_ z?m)i4#FonTZ8SmJJzt|;xf?lW3lgcPOVwme=~lYcn4FMJ*;GwgQCG>`YC<(p-=!wy zq++T`{B^5_oRlYST6U*X`m#%N3m%fw=~1`{{SDmQWH^n?kW|$PU51pZ;psqQgAl3W z3iM@iFE9}ZyssK@)oG0>Oa-Qyo;)O1qm}#6%jE4Fc)QLtkJ@ACOL-0XxEBRgAoPBP zW8EV_Hmczz{=_J+C&bgqmnoA*4J{I3BP^0E*>@mWB=5?mBDC;k%HKr^SH)jK&W{kA z<%_N#DHrqmOZG@A@w?fEb|sx)c^f<{c*d7A>5gJ~B=byme6$2Q4y1vY$(Jj>JT9b= zgqUxpeJ65bxE&M6HZ4>{ZB8%di- z7a1F*O8G|4BI8Qava5LVVHqmXsQk67QXX6PmrzgoKs6;4Ae57)5&W)d2JVia_*5>#7p+zsSHY! ze(Q4sa~NQbXqiN4;iDt7S1$yd@$7T8%dbFpaGtQgfaW3GxPG%!WOkj}W#5l-0L*_I zR7-!qGpM<=GiBN&kiI@)!P2(xp?uT=7m#&s0VV;UIWpaWl4NfI(}tj)i1YkTK;F;@ zFd@S@X!>=J@sV=C$HM!rt6ov+tn4kvIs{YWG&2^{JLHYva;!mCwspPT@f1F_V8W#aaw z+}*A;QVM@B!NjC~=pts0;O0#1A_GQKD#I?>eqdgv1dR4n70m3ZOdbk~^-5K( zsg2lLv>}H!-BVm;8rG$!^#ETyotSDy23sm)tH4wWm`W8)b$$jupV07g&t6%4^+w24 z#62lZ01f92Z3J8Z8-L7pA+TI2(Wd!Ez!?2hP*?@`62y1_ZRc?dLtGJS*(ZNNUJfIc z<-3&bE2Eon4>Z)CSl2qrQ*b9l`JbYIb=CQ*O4EP{U@u(6VRbFAohgTb{FNgv+(V|W z7HIFnZjN{cL?1}c*W2_E9HO;eQIT7kDA4ccN5a@~sp_thEIp7eC4 zV$F3=vo~_cpcV}+8_}Xs%h*Q3e=&!(WaaAR4Vd^s^RAr#c>dgtg&RkkZNd4sj=)W- zZ`C5L0GD=qD{}B(R5DUj(g)EDgZ+~&<}Bftap_%|$vZ&eOtZ1+cLkN9QJIR1O{TD& zji6q$yJRtCqZ4zvU5lR zby6yX5sH@tR~e!o)J_*TX+@bpckaDs=iYrE#{JwIAJtBOP@6j2%to^pf9z}S_^9PW zigS4z?kj$|iw)$GF`g%6#Ik(Z5jA9waAtebkCFue{HLfZWwABkphgbnW2rDc1moBc zw#6d|*{Va9Yd=h+9zgP_t2Axx#g8N$c+Skb4_M?<$Xy*1)MB{0qVTPRkM^scQ zK#TFszAjvML%8ksA5;G}zI+^YzryX1VI#qINhl__W7dH?jUc}@$bnOpi945a=~9tu z;nXxJ3%sgxY63MRxm*wY92pnJ39w0qFWtp}(TE9XL7hsdUODN?qRdU4KLN+8p0P#> zOdSNgcs^Q<5jufa(&R%QRGeXO8;NB}nra0dj!~5Q_}ZW%%sOTMO4O)vo=Z_QLa@U7f65I5L0v27-&9 zT|Ks_V#FhYhrnl~T)3L4+pLW(r$zIPe9_4>%ic}>`l&{#L%XYwl z?K`^{-RE&TeMlrF0iQ#|?gD8@hs&C}N!Dmzm(}J=Sh5*;h+yk}66%EdhoG*qog>c{ z?2ks8Xd3$6(|{i`M-v#7l;cQZP?&O9!Ok1B^J4}Xk62x;Ot#(9)eYT!h%omMo_r*w zHspz8eJau}K{RJSRtNY@r71sde+P~2%l9qRNf2p={0~V z3)jZ`vEtN9y1g;Bd0fyEj73X*9L#;PocQy23Q5qorst`!q^bt*rSo1qMK5R+*aJKb zdcm4t3gU1ZJjD0-ub>MXBcO>K=4xUTyI`ouH@9Enry{% zw_9?vg$&mjncM;HJM>e37?|=x@RdG!tGrcRC$Cew@(zC=_9XavSQ%DbZ4#CyCr!E#-z|Cs}pRY=Xfb_v+} zWJ-m7PKPY=C_(}jVlFxB{~wXFZ^b+~;--R74qs$)M(x{?Y@+ogIY$`o%S%@<42mrm zjDrVpH%9uZQy#H-2*b!7C?V=3Sc)PWi0rJyPh-i`ogqnLf_+Z9OH^14h64{-aR95& zmQR%I>APs6L{fsCNk;&htiax!So=AX#!?zQ1hP5!9cbNgLc~yeUqm6mF8QGB^dgTK zJ}^$Uw~BgE0}R;Y$YK5XYznmDke75qt+@VtEr2M z_H=v%M~1>Q+}vCkNOy&CQqjJ7NzH)$dqk{F`oE020orsPpn?GI;_KEMu~VqmXhE>A5>8Dtc$L_B1dTM}0HMkD7NP&uX_cRq zXwdR~JXR>;ibhjD;u=8o2+$k52(6?6?+y5;gzje0Mt(iDk!K3qjJH8kc+vfQ6oOiP zPfION`(-d29sKu8v>+%J)EK?wN5CtchHpw7(L4?(F~yeNcpLPNMhn^cqI$EW*qg#4n)X5`4ZLlrGaA4d2^rd)H|2e%dsmsp2hX6mT`!o>A^C zi^I>5a~Iz_H8*v6_Qv_SS1t*jg^ffG^K+Ltx8{F^^9nmkTZ>(jgkrk`x%@v-14R+w z`Epjii`qG7bEf9!odoVSsW`hx31@T_rwH6kwGbLf3Px|`owPXU22gQK;h>t1FstBZ zGnIN~JgrlFy&`_b(;W!OXWSA_(?@U&o{j~@EMw`|sBIk*GU3OHkTa2QmxE21;$tKS zgo2W4s=Z1Qx-bDv*oP4N%}S3PQ@W5M1P^W4553rh^2E(cfw6u{0Uh`#Xo-M8iw-2+ z7uvrYQ4kuC?>i)cd(H}3zTAr8Xa@@zHDm#ETbGA9dF%tggZ)~3{pSmxyfgp76|qya zNqh_8G(1dsavd=rt|c0iYQuuZ(5XUez%9~c->vRA)7_ty|1gG%JG%{!*P7(fOLo6#w94IL5ts9OU9)fn0xAR`SoeV{abyFXn_RufU z;AvpK#(6Kq!BNjs^osFbJPmp!00saSfW;Atg$Fr7I}U?Z&_xagEkYoiS_1Wg5^|`= zku$GPQi`W=7{Ng^X2cEMP6X<35X}sJ41ET01qDZNHTG?Qf;4QHC8!;dT~nh29%lU#lYXB#1i$q4jF@Di_}}{ z^(0+A(*3aVA?Lw3-dCrRa6rXJ8C2q@LZAzEhZCkct)6BYQCA1ghHVu1htn1vd+KRv z?CZGKKP^?h;SxIF1d0cJw)5Z)W{jXh2M+)m9zMx{qoG0qc6dl_lbej1@aA zkC_N6=*&0*YJsZ}DUV@s!|wxxlOcpcdm~RPkK+#TGW|lv1g%$oG56}*i*HQUp1s;aD=aMgT}9wqpFRoOqwTf1 z5j_On<`-9K_0q1;T_bf$#^w{a9Xj~uA5e5*-*H?7U7+k&mV88^3YYDx=)G(Jp1iid zBU}i)woOaqyh+TLb=yo~58VwyzMi%1?0A!@beNv|I3*M~sz-2-QAOiG_q$Ylk&@#` z@UaJ-EpkeJyy@u(L^r|%JoX1jEmxXi|81Ce3ejA_$2iV~V3Gh4WL%*4)RTk|3Z>Lg0;HolqbfRUTe2+4XV=vB ztU64RX`F(SkOpWI54E`@foT(FIwdZSLm&Fuw?6IkftgyC{ZjhUmp=9T&q}f+hjcaP z-*Z3z{^!4Z=jc#NOGLrb`t4mqeY+LqpH!$kIx+{5oS%Vkg|mzjV|c3>bwrD4BYI39 zF=EDu88aERYnjl96|-cXWx}zr921E}Z0l)->)be{aO0$UlEs=(HhBo;&`CAc%-Ino zYOP`wr7x=c`g1AA^^KRt@)_IL52uo@Z#|!PQ@Ly+6J>swC)`9blW?3Qji@|TZhwLr zTXGsxUsi0*);a49-fH=+mEU^#ZS;blm|>e-*`lC-2=yUb$G0)t?u+w4HSOkBDS{GB+}k$ntm+oY|618HZfwy<9;L_AIb5t4CR~S@zJqFrrcr- zWO51D-w=;unVg%TJ#*r5(Siwu0peTWFmDTYLbOvIeOC?HNW~Bc$-tq;h-$IQuT52% zY^~dOlGH)!{uyLj*>}!tEv<8P8}=PPFYjWbh3$8gJtpPwBBHySc7ki)?G> zo6y!O+cwzv(fqt)M&<@_PTY~ z_iy3%&KP_LzYl#k)%B%suj|jdXLNo)--(_b4SX1U7kJ#wA3)nZXlsxde-PjF5Z{B= zP9BBsKjJq%BbD-rBhbk~>?ocu4y=-0uhJ%$tNwkUoas1N&M1G0ntphY^w~*G+J5^; zVm$7+wk@FP(8NKwk(O#1nYLBV655?4GG zlzAGc>gk}GXMpOS32N}NF`HsLMu>Zf>>zRwAK<`Nk{oI8$UGGjY_>#O!q3b}a3 z&JMameoLkN^AvZ7L@OprnsUCtPQ%D+NRAIOs1!_BIjIyvF7p^?TOcnE!vQ5lW>aid zg#^_(mFv?+!Sc*m&9mkhQ^qfOrue%H337v*6dI=0LKy9a7p8WMG`%p56nW%BRC`46 zLbDc1VJgX4UO*{A{jfK>XUrLgm1FzP(CF}#GRF!LFXDxUP4WBDi*D0t?NCRh+u8a`Q&<_Ni*eYNmMZ#PVC8?5mb(BDn|qy7!Fl?Vi}%EyGE)d*x3zODF!k zv^ZT_yk5NU#>&*k#q)304Wm74993GF`ah$pd%Umvr*g&U-tL~Bo@3psmu5=uO_$Dm zR9Akc|@-7AY%R&M`t^{vl~pIy0QN2x58e#PsHjs2>FRzAO2y!>vx zR00X&Yl@PSGPf1^l$lc)!09p&Wb&;9|96@qO-zicDF`CKXh49R>>#VaklNdS96Z@Z~VMxm4 z&RUD40Nm4?$t4pRXFoDQXH2CH+Y{BbiAM^TUq^Dbf_TcY_5vA?G6hqbQ(sY_oNC3* zFux_9a^en!=PZJDRQ1)KN5x(YL^hnY$6Z+;cpDjWDboHo+&gHaF)#KXqlIWyU76f< z7k(@K`=ZUh=Gd+b#{dLF_Lu-@OC^0XkKH0Mp_@ zA`cNE4=th~u`qB(Hl6`ju}CtLbI9G>$V-cp;_ySsF+n&jW>prH+X)Qz7)>L6GYQJq zY1?Rihh(aB=*-mAvd>jnn;KC~wT(rzW~4T)3)EsN-tFpSTcb3in%Ivy0-mKaCFe;G zDH-j$#D*^-XRGmua9TYQy|BLPYxN&po%ScdopW;%}KkmV^5R~zeKlbyBVSyQ9r9c4nu zz9N%!okY@1%0sQlBdwLCp(A`WNCS3k4lB7=Jj2z~A+B#zhSwys{Qhgpzx#0cH#dss zuaGCXeY#?Tj4 zmH}hRWlfd=Xwrsk*MmShppoYryB`y*oV{5*_x8%KPLO+^xmH?yb9wgG()=Gvmre!I z1JFWCW!*qF656b}Spq?DzS6RuLB2+S(hWBV;(O>aid3UBZESK!ofy{n3n;5Wf_N-= zJst?8qEK}LA?`e7mRu@>c~MS9PABXztjbR&jsvAwn*U&F{^Q`o0|hBvo-dw%BgzEv zS@|q-{Njfou>g|7jXk!OeY|n6zlNUUNHWAIYJ^4D{WPb@9w}uyN4R z;`y_sg{wgkmibjcKptA79ln~)$IOJlF~Y6^@?;4Cke85NPC|z7Yq$vr`~Yb^f+WJA z<|eX;fe3ubVH~qF2ug@+GImPffd~gYkQ~b-Tw5N)rLz>@#01fn1~=On<|1oxF=kZ` zIl%UGo{5<<^pBU{6S0Ua2ku#hxCVRc#frZ`avlXyLOKOBMhR5yWSy)FH7b54!W+Hm z*9lKeRMAW=9PKQ3T-z_5-CQo1}-NNJ%Q~05Z}d>6E3BpRriUCb>8zCgRtD*_eyyMvjDkr)cN@-ho+jTGj5Z`4g&DX2n7KmxK&37PCt+$W zmtnfp%7WI-$^f00zj8Es-(_^yy3tteE^C)tzjE#R(T!`@kEXQ;bvV1QbT1Sa0vHt> zg^HIiQ*^j`p&ADzgaX==I`AWO7kwbnNR1Ax?F6a)+HoY-G?d5;ln>ysh%5xB@0Y1x zgVnx-x_Kn03q&y@bYZ6LVBJuOO4_YXcCWL}24yV+)AD&i8fzUq4bkLCQa?xu?+(Rq zu5SQd0H=Tx0borV5}=u$0dQ(o!72HjK&nd%XKVd7uo!MUm*!uqhG3HO!uv}LHyfbY z8fdNc?Q4d35KBl0UAcYPPZh5ap)ZrD441t)Hzcx2`;PeR$LRMrqyV28gwHy)cCEny z*1L2%h{+SsdW=Q}SQRXBgIJCDR?x=xkiiXy#iO1IXrckPX^7fdS{u+P;vuIOw9&{> z#Wh?QDP3Pk<)rJf)zj0(h0lVca38*vqDf=0BZT1VbVl+mVZREGWpj85>7?kk89p0; zKY$z!O7~qf{{tzwfl_s*1G{vxh&p+1ouV}!^XP_q7siR*AkocY6XogJS=>WJ?$gth z>m;(1h(?3}t-L}O2Pj9UiQw?5AJ647f*`Z_0TIGtf>4#9XeLNe1zCuoT@%E1js3`v z+DLVDn@0BId<(?FO*7p(Q+V6ntgz?4^UrbhF literal 0 HcmV?d00001 diff --git a/Src/command_center/path_planning/__pycache__/path_planner.cpython-312.pyc b/Src/command_center/path_planning/__pycache__/path_planner.cpython-312.pyc index 83b6599a3efe0a391517d5ac83bd4327c2f75795..346160a3419e1469c65b9759dbb435207bd92959 100644 GIT binary patch literal 9847 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 HQe*!Muz|*4 literal 5497 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 diff --git a/Src/command_center/path_planning/__pycache__/path_planner.cpython-37.pyc b/Src/command_center/path_planning/__pycache__/path_planner.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41fffe676a5454ce01feea3f44f4ad9f3c1fd9ef GIT binary patch literal 6725 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 Fe*v|#O1J<3 literal 0 HcmV?d00001 diff --git a/Src/command_center/path_planning/__pycache__/rrt_algorithm.cpython-312.pyc b/Src/command_center/path_planning/__pycache__/rrt_algorithm.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..670fac1d6b691a99b22009c76c6f450840c4c598 GIT binary patch literal 14020 zcmbVzdsI{Bp6K3rkr#wVAUq`~C{aYQY6Try6s^_{M=ffL*fb^qg1p=b6)^EQ9Z!ud zR_Nmps;!}?r-t^Nn%*;W8?>j=S+k~e&8*dI(++8-YaQ+#0=nkjb)t0ES#$rozu&i$ zodn}KtFtxV^Y^`f@9*QE)6?Y?eD6!kJ6ay2sDH;y>=Q*3ORbO?qfC^E?xI>~5(`=c zBo?*^Ar^Fry2UNxZb^%zJFO+HTiPO}DFO8qWfGpJOrnnkTu&`BQYMBn31223q;+Zc z5THpX3W=VvSZyJ3bAMl#IV5Vd7;Pcx&-!eZ-X24jk?VyE`N?S^KSr6!#9&f1Oh{lB znS^Fho5&au9#oi z0l^q$r|p!URA68iK#q=OO%&v!YolOyAjkJKNa+Mki!n%@h><~>!Oady`V5S@#}*P9 zd#yH|FeI{?yV~OQR_sHo)&A!j5I$TyFHx{n`=8vB{!=Jpj__}xg<|_+h?)jTH7p~sCJ28Cs_LcA(!{Jvw3+Mjk z?)CFw=No_g{9-&4Dwvh+6b(yaQC=bB6hXDdDY_(E z0th&23HH-t=r-&1A-P`P-D~Ra!n9JaKh4U6pP!sdJQ%v3))UnOg2PJ zCI-@2fIDl=hu}Q*y(ljt5Q+0$6%h&|_wFAt;u;~m5q|uKUE#OHz(FSCk&KG{Gp&o_ zl-MK!+!Di20{A62rJWbINMw|$$|N(VnW3&tPyn5oN|I*1A}ObWJ(ZYB z0WQa+h4qxdFFtFPSq1A_3AJjzcGbiC2%{rhIl$%eaH-D#;VJ+wk3akMrqwBWjL#ue zKWku?mGINSFV)I3Olx6hG=oxIbw~*esUb#S{kecXP;mFN8{sd`bL6`C$=Jg0-VUkS z8H-8Jn615C{m2kQ8Qq2fy#*-NfLUuuX0@67^j6D|Ih5JnYv|Hj4c)*-^-ymPrCZGg z#@GQFlcnEklpxMWKc3?~4F!Lm;EcR|~(HSa{o;1kUdlK>qgE_ujspRJ(i&>$LYSTv%{_ws_@}1@{FI9*ggs zU-f+zMAHMV|94Fs>lTOSO!DchX7?faujFTyaKqp~Tn0{2f6_1W= zsFq`a$Pg}^U?s5SjG%TSW=n!4Yn`I+ zq=ke}Hig7in}Gotg}V@vbsPHhr!6L1M@Wvz4zs1b!xmE7IvBG7!~=wYe!VB4jj_vY z4M`ZYt)J;Jroqs$57R`hW{45XY`he;(_0La>v zc5JPFxaQ;Njylrq=~jC9DnMx-hJyC#K=+c5qk9gJpJJJw-vhg{voJ+FsuQt(*$1$x zc2t>qNA2k;q0>`C$97@}#)SE$$A-4cI+5y@efIPi#?YYSrJUaM4xbqQbl5UqKo(WS8XCzHHk@S=&l0kMxny@oeXW6oCfvjB`+Zm&Prk=&!S(O4x zmW&rP(B1GCB7{zjsXvL9`2A5qf2roEip|lH2@t6;O_dUNRH#9iAS7bjDI@of$fd9zG);C2jG zM+W%g=fmL-UtW0c>cYh@=6#pKPH$Any8Tx8%6p5GH9UEj5fU?o zo^~^l+#u(`Z0haigm>J)OY85kwDmIGp=^Dd1?;LGP`%7no4yCkrI17qYKNI2Qa_ZT zw^;SW-g?QL{-T4(9VW=RqkcSMaOGDcdKX1wz6eg@2Iv0wYuwy35DnR8^1lxSVzsnfh`Se$7zg#=h@lEH~o&JXB zmxT0|WAuHBE;~j;2kCQ}>M_Nr!nOC?%nF!F5drt&PjU&onPN}>6di6y0ot%wxnj&t?Jlnj_Pb+6E-)f!HasH?eAGFjp)W_7z=va1<$rK@I3YuM5nuP#u!<4*pLh?L689aD}fUCr)oo>pJ& zzh>@^?5A<#BZp|%m|0CVtEu+byq3>SetL4c_N(n*Zuf8bSwM5ZsSK)eW>saZs?1## zP=QJci%@1>uD@9C+HtLZyxz0pM*U>Hzp&2t{Ow~?$7XcjtowSMf5(x4;;6snh1r%4 zwxz>=vWso$x})d{=4hPzp@Yn9r;Ic3A4g;t6D&@0>j2Z`)KB!PB&we#`eUSV_#EIl za)|77BXxW;aC8@?PaR}{VhiG^! z2t^Ty0Ac?c=p0xR@Fj3h&HH}4_~EUE;S1qU-T@8uqf6u10d55IBm^O8x21=06Q&NcB98EplC7u&N2!!ECaR%es5FU0ALe0$0AxK3 z!8vL^C*PI(<_@Pcs3~$P=hXQRD3Pq(*$4+SRzF(r-h6%Q#8%Jd8(Sy0`d4o9?hfSC zL8U5Zq~o=p2a8Haq`}fvqsoX38UdAM7W<_|(jcH{5|nwM-b9qWA+v zBNMFFn1w)hlIrAO2=q!g+Nkwlr#q3OFZ&3L*oOU*FV%s9#VbPj4f>TBW7L^=^nn95 zkppJvL6Hp)gnVE#ONJDn--~%vNP`)~suN=9JuTL&Lz;S)FmfD5W`lmY4|F~m-@n{0 z2b7ZZgGZN|zx6t30$_P_s1qI;bnmAhR1Y zAVtAx5fYfYn-Avg=%E=tA7P}3j!reJ+jX& zV%6ntV<2;_Qyk1JAUyYkd&_tyn^~EvWIjjZ61(~XITb(*DveXc;c*%uFA|)Q$19jD zvI=A^OAk2Qr8=W52+H{H5$8{*Vn4`eQsFd|Celo_@3uHo62~pg1dK)2m%Fw%C)D4M%w3l zl*8)L1+(k}ds+?_dQ>DZVEqgV?IK|248W(hYUFlN3?ATK!4@KFP^1(0H$&$OzdE;Y z?bq|Z59WOzal|=4_1e9$ze6@p+oG1hT`%d6(7!Y$yj+P!JG_`gtfbTFC3+Ni=;B$Ed(y zH3o#B6+QjkQHP_I*@hj{wm73mP1PF#Y^W%is+08LF#xd6K>+?MDV3?deD>m5*Wk4? z<7YhkZtS1j?_aUa_xSDFsoLrGuPk3${B;Kcs)J6^4|xSwSGi2?=Glr`7XGiO4S+h) z5Xh@{D(18DM$Whoj{ZE5wdMh(kd-(af|*$(`$tun;Kts1g>qnrBNku_c?lHC}_S;JcDtP!Z9< z_>n?r#OW@`|C0Cwif6(n?h-!Hj!FrtCv8Aes7?s_X}~Kf;9n~KAxX&*B&n$7zs*08 zvZ+*(GOFkt0mX=ygs__E2Ogv7jA?4P15_X}tm+(Dl^_-8dBh5T63?^$r2U9-H{@o( zwWQEG1xJjo@&%Y5&jVv?1T8^;Dj)EGD1U;pvSOJY&y{@Gub!6-!!HAOK)?CZPxo7=;*{3=$%-J%MEq=QB|r5gp@X9+IO?NMxCnX~Zf*_ZfY?Hn=vl zz;y#&XIk;FdGXB&Z&0SGA$2mDbEfNWp~m{ZARzRem+zDZISsOkD2c9ZcT}rDu^c-) zde&{b{?f!t9@~wVCSUTe-s;Q0T{u-Z{oGeAU$*$S9SCR+j)*{ZyUJ$^tJuP-Ku%Rq zy<%2f!Ky3Vrtj42P{XW>WKg-K52!TRI%nftNyW9}0!S~$lmZ_GR{BH`s zF7)qc_8&bqd-OPa^tfMdoYk9Iz1e@HEwH|wEopZ)A(5|ei>}KiWC4}VW16jQV5=LZ zD`y)0hhFf%X!)+XGq_^)i0Cavuz2l==xy136%5D`_*Ph|F;VrzqbCIZJ|xD#8RQVy zX|N20%vKOzsfb#blBk8Li8@YXc3~$jiD$6MfW*0fr?n@|DUJxwH{TKJUG}j8o z3*1j#-#4+(pI`m0azik$WUObj$NAI)F@XIj2Oj*^URU#*O##&^zjPI+y9xSgLV~_c zNp>W!x}AXW679&8=fWE$-1P;j8K5A+l>(R|LUN%&b8);TQGFt=LEQ1XpG*L4gQ@_w zAZQ2+zqua1;J$ZZIQ+%f-Ot_yOOaFA*8(UJ{yG7J`@tY1(FwV2HednHI`+VT+g!9B zhw_p)H|Bc!2BA@ygP|AIoL9OIj#s!>x(`lNjIZ~Uu?6cqjJIsE-^=*QKJA}$_)f9a zPqGD122>4xX#mM;Ar74}Ck<2b@h5d5K9SVmMu3_YM+9((hV1Ep%qDmYq+WWgbppFi^ z;W9_S!^k0*o&a+LX8=&YgIQ$YEMQhskC=z5o#N+#R}j3wiX9$cFb$$CBSF3W1NILB zEK`?7xCE!(qVs~NvEi$pL|qje93hcyu+JQV2Qfwq1NVjylLsh(yaE`91n6SL3+rY0 z-5ioJ@OlM2vG_~~lfX>)M)6cAt*^IhupKVTCB%bZ-2-oogzaWqoIFBVoO1zn;3%+8 z#Ze58w?qntM0n2`l3}RFnKK`BT9B3b#n+_cQg@ywJCI-H+&8DnckR8S zDi0QvT+1KNcUOA$1PUG-k;L;2fr9E0$qz-P*J{UW-3HHUZ`P!4qT5GX3^T6nUTjEg$G+RIdS5~o^ z6(fR?onQ*T6wJwWWqq4d=^|$HzvfhW4tZ<7UBAmG@Ew^_1lI3@p6&~&>P8x5YIc#! z0xEfYhC2fV8(_vcn%Nv3o1^oTc@BCd-{m|GjUsDc)seMSW-iBHx?oR|SPL&!^9gIQ zLCAezzeUp~`Xt~Bcxu2W_lv3U@81u<@(X-a z<~_f7^ESLdLGu!ol|4knH0uN*VVAjwK`T*$ zWdJR+az+eqq`7vw%iej)lkGY6k;Z$-SMxuPey=Hl+M*KIsqxkB6P}uhHnw2>o%|QP z4ZdvOi7CzW!I|tYkFeXG@gF$kKYWxuaFpHlod4KycH41w^Ks|C?^XH1l2xw45y@O; z*2RWM5lkhrg38QurW2e_LT6s*co|k1m6UZ+6cYi0^5~sX2cf}b2?%1MqcTez&moG$ z5SSR0DIZTD5`nC!1O6@m{w{>;N_?&IFyioj31IHbS^#O@1Ih=lh!Cek%sn0Ft>{At z$zz@d@CL@ygA45f&Zk}SAAo5M zjt;uZ+?^BE-h*&6C9aezi)mz;#xyv#?y^ki|Z-z1FVP{KS21B~eb zz~C-GKk|fD{EVUQG+{JSyPRmXj@im^KQH0(^oS zhv54VV;Nke!(PTmiBfwTHa|HckWqnH3Qf>G0GewF@B~o`48=ElXn;0wtCKEUFR9 zK6rAG5qD4m+!oXr@sS41Q`$&fPQRRC6t;r}dK{>a9~WA+2}T9O3K8tNBg2vYEI`oF z^l1v5m8XEg$Yaczdz=uS`rX2#hH}@b;B^E@$}M=Sh9>nUqfyxURz^uk$`%9-c5z2E`%~x9!ox zeiEEHgOCmhdip|Q1H%{w@gaHNAd$l%q2Yv;K~0iD9vM?)ky*t(Im~&?{sMznAPC9H z0|c%s1-EGKg@MG_LEbu`1<0I1P@$oJAi=BqNL}_401xFSG3bPj(*d8X*CBul|Fx7- z<+Qz4>!M$+2W8K>AFf~W^2eSVea>Zi^967SxEv$l^k1Pd$NhVg1o7F)P( zWLHpK>ZV8G)<2-G@D%yv(=}7+(*}0i-kI(GqtAU^?{7KA?r&wcwX(Y7tok@WCABE= zt(!K0j%;M>o}Q^;>;Bq*;F!Plxc>z`+p1>|yy!QaU=Kj0(cfnAcb)bR46^z+5XNrep63GOZGjfqv3`+c5gWMMEhgp3p~+&>Hq${`%nMmt=Hu< zIfz+m4DhKclZSx@1H4@(ul*Sjrcmu7+{fA4#VM}5tIVLt*Z{MSr;s?3__x^ z>b^oPUKd=sCL)A*uB<#Fh8UnqRz}h=C8f%B5gDfBRN3k!gpO>cw$$F2JwuDLT@`Mz zyWg#33)e>|EC3zVmFpI}irJji5h0e$W}GyOA4cIRRCuZ}EnHLU`LxY%v;G)@dfC`kG$rp43! z(@MZwlJ_4vvV`T4LKXbEgeXEmTSxzgv4FJWzDtwLPx zhS4GNioAz?Tc#^~JJ>CI*^T>fdF7Epfp{nF+U>6J|(It!{n>6A)_ZBS6^AO-XUvu&?kUIY{J;LWjHc6mY z_|p`KJiAHcGfi)q$%P%BDQ0&aVe5~QQh)1|*v4vWBNP+{wdL;S33)^a={eZ73Xj;+?@_WVx3DE!BVs58RMJ*N(l8~Z z0EuOol2a>IMX*yyJ@&Y_-*oFCJvL%_ySe7Eu7AfmyCzjQT6J4lFrw^UJwbQ)q^r6!d06A^#y!0}C>i3@o z78lW7X@8vC{^y+k{{Q@5I2;u4T<~umi$5(0|4NQGJic9=g zj?4I!vPw>kt2r&M<$Q5p&L8)S!Vd&PHk8AHp-f5f0D7vSp{Jo2G{n6^%(o1z!k(Do zC@-f?%Tf20irJK->`o^w$N%G^l`iBH*(5D$KHh<%7)kNN;2>gLPAMtHkY**mr4m;S z6&q-dvZr9AN+B$M_0@f~xsyxpyj6eeR!nllvLj9;2^~H3E8%?suek}p7HvTnYzYwe z1;i$RxGT^~dq%N@KQV>*&|rQ5M=K^+DsMSTvS3;<*-^|?_CRa?6M@k|{_{@&zQ28> zdhyEQ&7+Gq-yK=H`}yL+Z>l#g)vvxg^1_dIe}AON3Yk>W8rjQ|BgsN8m&hA>5~x#b zq?oYA^kO!V&!_XFBaB&kB0E}OX=^OEwK(DUbv>O=Te?nLnRF~c)cC*2FwtUbbAAt~ zgT?toE~V>EP}g$>qm-p~NY}?piELxWr|U)`iDC6Xwvez`1Q_`}+CZ4bGysw$QvvXc zipWQj=y*>LTD$O?|0H&D&-ga@rxFjrMc=Gy_>I6}HLe-kQa&0xAgveLS}NB1Qvr1|)*7p36{Fu+jroZ26jt=C zxMQy|fYYuq)?$}F<2#LgR$OZYjdfT%XspNDRUDspH|)0ovb)jPg#G&4PUEcrT^_=T zA2uGriq)J_E(LmxC{Fqf<57$cG{(KJaOYm*o0#8hJcju-E6zWT`6rAa%&#?`et@D6 zQkiq9bDgmTw0_dqikq!7f>4oXoDdZ5`KDx+)`066Si13f_1@v7D~GGoKdWCmUHjFk zIwtT8>{|W@C{m;M%&z*`pVM<|XJh1#cEwbuca)_Kou$k|wnVnUiR2PxJq@i$P}g(< zrj;t{W_mK^^oah=SL-$tRl-GEkjhfrN9(~w#neEZ~-IKMsJJsp8 zm+oDypE%O7xbvdhqqW&LVLz7cf3~>raqXA$wNuwSmV0!-%Fiw>pSs^M+fAD7(d8pY zYI8U0=dRV}j=(zb%M{A+9F~b2KGY zd>DaWz=+fs@!P&pu@aaO%l#JoMMjzucMCJ(eyI|)k}0jbiH zAHCTs`hdhPJMDB?s|>CLy!g*_v7gmycK8?Y^WudwZR z_u?byVYe`LivX)1ScSPh+dmKAj@Bxiym2y77=P|H8l&5IH^&aW{EOasvC@ZD#O}p8 zd-;jmR?!&@c|hRjc*pMBD~ysCR!Z-+E8*hZ`}Oy)dK&237}wr-(&;rG)J85j+oMar zz5)5KemDa~hz^yvL?>e2M_g$kSHl|?CwbeW)!FOSbN8y(?pAMK?!eR%$>yTv zU!3V))Gn)7cKolFfBs4J*zM~1^VR9oOY>JdFnXd4eLg!|zi_;M^ph2qdEdOs<-N-) zxfG+@@eRJZbF6y#t=dPkwWIeIZyv8sUvHT3`|nrJeN_MCcGm_BED@GDAfp=V=Rts@ zvP6C~#f<{=4AD#>=h_8QFgJMemGbEW1(tLA^#f^yJ9${^lxgXC1TBuH!=j}aHz-a- zPn$XqgWgDF(*_F=4zdwWKsOLaCh|#qkth~ZdBX|osj|fqNsE8s_~1ScrCBPr%C$w0 z(6>^h!+iM@(qVAQ2>N{7f=Wc@;ZiK)!mpzV;N1uFQ)4%=8cQ@>8ra8ac3D@m$!%=dO zStK{(aJ4cR!BsrgnL?cHs%6KQ(A5sTa0CAe5l4g|Mp9%IXt)*ne1w575 zg6`XR%>e*`FQWme zhEoSo#SGl8Euke*3(~DxGFSn*&!`)PDY$dfWZ?9tk(>96ig!)n(T-dnnVq~#NS zcC6i#e0AsOGLeS!{>6Q)r$67AvH zR#zf=@}*oONH^K<6JpV7Nm{p9Jc$LT@tSJ^1Z9&r0K?}O*NST)7>bOxG`X=;FgiR1 z1caLqE^@7tJO*URrf`rmAyMbTaS@GC=xcd5H-J01v6Bu47%@++1L`5DluNNTna*sLMx=*FLzubpNB;iA%1*Z3xK1rNujEke|2$f|Ox#;YM@_l=@b* zk%b}PpSxMTKSj3S(~Ge^F@Kj7@CcrL8@q5<%ZL};^Fm%op;n@7JV+jq>!Qha&>}Je z@kp^?p_Y+Geny3oZZ1ECHJ{;aa9I(9;PEEFh#Zt2l{diEO%8N&x#80#Mz+sYc8Wge zA^S`(=O;M}@DplSk;_qAW<$0FF38}c%oU#EVL2mLWa5{lz_ucSpCSs2B?=H=S6xHe zu&d`js@?v){{H#e^o&be7)nkf&suUCIZ76;$?>z)8!2X{47fQ?Gk)CXfY2Q>BNuCtJ9je}p4kkd zlDC>Xm&~&qKzD@v6KJ4t8Z2F#htR+P!HL7})P8xTdSq_-$g%3Z(@Qr#go}4Q$rHfO zO-f8+yQuv<074Lc@x3%a)~x%!bgI9{YjXZclWRL~*$nU6%Wkes;Y!?e#=XGcDe-@y zQ~sSTL-m&6h?P00jKOWYt-|3-EW^hXL*>y7&1;4aZJ#C1K~!MreVh<}A`N<|h=45U zakovX>N_a1FRAV^t^sEcaQ+>~iRu&Z1nj_wQVCXiDj}q8L0eQ%Z^RcD&1-j#a3~KxgLW;F76qs zvsc^AKf*Fcu_lTs2L;Sznjsg)FRwL%b0Ubx0fp^UH|b*TJ}1CXs6=#6gkJe~E z*Jwq^)kHgG^FfE$A*Cb0>Z)T?O6dUPcDQN!0u7WCWQs9h_WKNJFO;#OG{$5K8_Opw z(e3lN<1gSy5G1Y_Mj)$j8Hm1lJAhCS(FWus4p$@|l*|kyjrw^+7U;7)#S?su!e^9& za$y!S6tI1RB;$UHQ80twje_li9118#^suY85~5&MtEl5!8&SSmB3H12j9omtbnjO6 z?uF{xKSdZ)y?(fU`#uVO6mF0qdXXE4eJI7t*;JlUR1phxvzv(KXz9F}MiMAy+z>%3 zr@>UbFsl|BjcJ^aUw|;9ud{68I`dVN16m4FGSY$%i|6$z`-% z5ZlW9JT>g(B*~{|SSfod@8I_48E{5H3`u5Iv=vwlk=qc}Z8gJwgbNaQj);;h2y_=w zAB-*4%pRnx7sf$x85!AqFT$$sLW5ga9-}oArk382P&>h9V20?s)t+Fl)5l%v@FR?= zP-M6Gh7kN=?uREI>Lipa^i8~G9be)3=biPZ`SaVI_2d8X>$(5_+v~$P6M4E;1nBe} z;uSgFNLUFc+~}COLIK%Dz$#=>)`h;d*;?+oPKZwKK9 z@wWgJ-x_UIzz7nt=1g;(Ji>tREbRIY(kuQQUrWguidTd59Wc4Qb&cLS#P!Y9>+yXX z4od61T?zY?_^eO~jQ`{{Vf-b$LkK5dMt}m06rkY5JGjUw+M1{c}6sc zZ9wLSuki6vy?V!;#&3AuWAb?-H5S0po|VJ z-aPUAw-9w!-}|`w(>v8`w^~&*566QwH>!Q0s&(Q#C8|7k@fK69tv$EsEPiz?^MtRh z?A5WLP0UcWK6Slz{95h9DFn+at#aiL+wr@uu+9@q;ClZzwNvMo&rDa}ywicp>#SH} zb5U1kPIxJsx1KBXCXD z!nbsqBEqfPzBaS^-{1}kQ1O?>;_wbqdWya@VRB1{vb&_=b+oNJvV~+KYYwB){17R_ z7(R2Pe9=)8j3p-M523{gZrU9=@w&;#$1<`kO~>A85m<@lsd&N;0XRWk<*&US`%5ed)4jNFUjsiNBdL%nwNT_i)D%ME7IC+@87U|u;XO#&884KlJ^td9WwVS)7%SWV!M3DDm< z7%`R+=^3$?{Q&`TE4+xsC{$ou037A@LLuvBV3aJeZ3KA9j1!o=9;Y(DcS{J|R+wf7 z0O$lJxm_x(2%`IMaLr3Ud^jBKA6mO*U{m-}WpI6TeKhFK@vmY%DBP5CVCy6y+C?A+ t;An`%@E2+K{QM^T68zNs613+^hjIKn+&?`J(^~Ft{0O=H;-D1q{~sOcwJrbv literal 0 HcmV?d00001 diff --git a/Src/command_center/path_planning/astar.py b/Src/command_center/path_planning/astar.py new file mode 100644 index 00000000..91260834 --- /dev/null +++ b/Src/command_center/path_planning/astar.py @@ -0,0 +1,283 @@ +# -*- coding: utf-8 -*- +# File: astar.py +# Purpose: 实现A*路径规划算法,支持避开危险区域 + +import heapq +import math +from typing import List, Tuple, Dict, Optional, Set +import numpy as np +from dataclasses import dataclass + +@dataclass +class Node: + x: float + y: float + g_cost: float # 从起点到当前节点的实际代价 + h_cost: float # 从当前节点到终点的估计代价 + parent: Optional['Node'] = None + + @property + def f_cost(self) -> float: + """总代价""" + return self.g_cost + self.h_cost + + def __lt__(self, other): + """用于堆比较的方法""" + return self.f_cost < other.f_cost + + def __eq__(self, other): + """相等判断,用于检查是否是同一个位置""" + if not isinstance(other, Node): + return False + return abs(self.x - other.x) < 0.1 and abs(self.y - other.y) < 0.1 + +class AStar: + def __init__(self, grid_resolution: float = 5.0): + """ + 初始化A*算法 + + Args: + grid_resolution: 网格分辨率,值越小,路径越精细但计算量越大 + """ + self.grid_resolution = grid_resolution + self.directions = [ + (1, 0), # 右 + (0, 1), # 下 + (-1, 0), # 左 + (0, -1), # 上 + (1, 1), # 右下 + (-1, 1), # 左下 + (-1, -1), # 左上 + (1, -1) # 右上 + ] + + def plan(self, start: Tuple[float, float], + goal: Tuple[float, float], + map_width: int, map_height: int, + threat_areas: List[Dict], + obstacles: List[Tuple[float, float]] = None) -> List[Tuple[float, float]]: + """ + 使用A*算法规划路径 + + Args: + start: 起点坐标 (x, y) + goal: 终点坐标 (x, y) + map_width: 地图宽度 + map_height: 地图高度 + threat_areas: 危险区域列表 + obstacles: 障碍物列表 + + Returns: + 路径点列表,如果找不到路径返回空列表 + """ + # 创建开放列表(优先队列)和关闭集合 + open_list = [] + closed_set: Set[Tuple[int, int]] = set() + + # 创建起始节点 + start_node = Node( + x=start[0], + y=start[1], + g_cost=0, + h_cost=self._heuristic(start, goal) + ) + + # 将起始节点加入开放列表 + heapq.heappush(open_list, start_node) + + # 主循环 + while open_list: + # 从开放列表中取出f值最小的节点 + current = heapq.heappop(open_list) + + # 转换为网格坐标,用于检查是否在关闭列表中 + grid_pos = self._to_grid_pos(current.x, current.y) + + # 如果节点已在关闭列表中,跳过 + if grid_pos in closed_set: + continue + + # 将当前节点加入关闭列表 + closed_set.add(grid_pos) + + # 检查是否达到目标 + if self._is_goal(current, goal): + return self._reconstruct_path(current) + + # 扩展当前节点 + for dx, dy in self.directions: + next_x = current.x + dx * self.grid_resolution + next_y = current.y + dy * self.grid_resolution + + # 检查边界 + if not (0 <= next_x < map_width and 0 <= next_y < map_height): + continue + + # 检查是否在关闭列表中 + next_grid_pos = self._to_grid_pos(next_x, next_y) + if next_grid_pos in closed_set: + continue + + # 检查是否在障碍物中 + if obstacles and self._is_in_obstacles(next_x, next_y, obstacles): + continue + + # 检查是否在危险区域中 + if self._is_in_threat_areas(next_x, next_y, threat_areas): + continue + + # 计算新的g值 + # 对角线移动距离为根号2,直线移动为1 + if dx != 0 and dy != 0: + move_cost = self.grid_resolution * 1.414 # 对角线移动成本更高 + else: + move_cost = self.grid_resolution + + new_g_cost = current.g_cost + move_cost + + # 创建新节点 + next_node = Node( + x=next_x, + y=next_y, + g_cost=new_g_cost, + h_cost=self._heuristic((next_x, next_y), goal), + parent=current + ) + + # 将新节点加入开放列表 + heapq.heappush(open_list, next_node) + + # 如果开放列表为空且没有找到路径,返回空列表 + return [] + + def _to_grid_pos(self, x: float, y: float) -> Tuple[int, int]: + """将浮点坐标转换为网格坐标""" + grid_x = int(x / self.grid_resolution) + grid_y = int(y / self.grid_resolution) + return (grid_x, grid_y) + + def _heuristic(self, p1: Tuple[float, float], p2: Tuple[float, float]) -> float: + """计算启发式函数值(欧几里得距离)""" + return math.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2) + + def _is_goal(self, node: Node, goal: Tuple[float, float]) -> bool: + """检查是否达到目标""" + return self._heuristic((node.x, node.y), goal) < self.grid_resolution * 1.5 + + def _is_in_obstacles(self, x: float, y: float, obstacles: List[Tuple[float, float]]) -> bool: + """检查点是否在障碍物中""" + for obstacle_x, obstacle_y in obstacles: + distance = math.sqrt((x - obstacle_x) ** 2 + (y - obstacle_y) ** 2) + if distance < self.grid_resolution: + return True + return False + + def _is_in_threat_areas(self, x: float, y: float, threat_areas: List[Dict]) -> bool: + """检查点是否在危险区域中""" + for area in threat_areas: + area_type = area.get('type', '') + + if area_type == 'circle': + center = area.get('center', (0, 0)) + radius = area.get('radius', 0) + distance = math.sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2) + if distance <= radius: + return True + + elif area_type == 'rectangle': + rect = area.get('rect', (0, 0, 0, 0)) + x1, y1, width, height = rect + if x1 <= x <= x1 + width and y1 <= y <= y1 + height: + return True + + elif area_type == 'polygon': + points = area.get('points', []) + if self._point_in_polygon(x, y, points): + return True + + return False + + def _point_in_polygon(self, x: float, y: float, polygon: List[Tuple[float, float]]) -> bool: + """检查点是否在多边形内(射线法)""" + if len(polygon) < 3: + return False + + inside = False + j = len(polygon) - 1 + + for i in range(len(polygon)): + xi, yi = polygon[i] + xj, yj = polygon[j] + + # 检查点是否在多边形边上 + if (yi == y and xi == x) or (yj == y and xj == x): + return True + + # 射线法判断点是否在多边形内部 + intersect = ((yi > y) != (yj > y)) and (x < (xj - xi) * (y - yi) / (yj - yi) + xi) + if intersect: + inside = not inside + + j = i + + return inside + + def _reconstruct_path(self, node: Node) -> List[Tuple[float, float]]: + """重建路径""" + path = [] + current = node + + while current: + path.append((current.x, current.y)) + current = current.parent + + return list(reversed(path)) + + def smooth_path(self, path: List[Tuple[float, float]], + threat_areas: List[Dict], + obstacles: List[Tuple[float, float]] = None, + weight_data: float = 0.5, + weight_smooth: float = 0.3, + tolerance: float = 0.000001) -> List[Tuple[float, float]]: + """ + 使用平滑算法优化路径 + + Args: + path: 原始路径 + threat_areas: 危险区域 + obstacles: 障碍物 + weight_data: 数据权重 + weight_smooth: 平滑权重 + tolerance: 收敛阈值 + + Returns: + 平滑后的路径 + """ + if len(path) <= 2: + return path + + # 将路径转换为numpy数组,方便操作 + path_array = np.array(path) + smooth_path = path_array.copy() + + # 迭代优化 + change = tolerance + while change >= tolerance: + change = 0.0 + + # 对每个点进行优化(除了起点和终点) + for i in range(1, len(path) - 1): + for j in range(2): # x, y + aux = smooth_path[i][j] + smooth_path[i][j] += weight_data * (path_array[i][j] - smooth_path[i][j]) + smooth_path[i][j] += weight_smooth * (smooth_path[i-1][j] + smooth_path[i+1][j] - 2.0 * smooth_path[i][j]) + change += abs(aux - smooth_path[i][j]) + + # 检查平滑后的路径是否经过危险区域或障碍物 + # 如果是,则返回原始路径 + for i in range(len(smooth_path)): + x, y = smooth_path[i] + if self._is_in_threat_areas(x, y, threat_areas) or (obstacles and self._is_in_obstacles(x, y, obstacles)): + return path + + return [(x, y) for x, y in smooth_path] \ No newline at end of file diff --git a/Src/command_center/path_planning/genetic_algorithm.py b/Src/command_center/path_planning/genetic_algorithm.py new file mode 100644 index 00000000..e6c7e821 --- /dev/null +++ b/Src/command_center/path_planning/genetic_algorithm.py @@ -0,0 +1,475 @@ +# -*- coding: utf-8 -*- +# File: genetic_algorithm.py +# Purpose: 实现基于遗传算法的路径规划,支持避开危险区域 + +import numpy as np +import math +import random +from typing import List, Tuple, Dict, Optional +import time + +class GeneticAlgorithm: + """遗传算法路径规划器""" + + def __init__(self, + grid_resolution: float = 5.0, + population_size: int = 100, + generations: int = 50, + mutation_rate: float = 0.1, + elite_size: int = 20, + crossover_rate: float = 0.8): + """ + 初始化遗传算法 + + Args: + grid_resolution: 网格分辨率 + population_size: 种群大小 + generations: 最大迭代代数 + mutation_rate: 变异率 + elite_size: 精英个体数量 + crossover_rate: 交叉率 + """ + self.grid_resolution = grid_resolution + self.population_size = population_size + self.generations = generations + self.mutation_rate = mutation_rate + self.elite_size = elite_size + self.crossover_rate = crossover_rate + + def plan(self, start: Tuple[float, float], + goal: Tuple[float, float], + map_width: int, map_height: int, + threat_areas: List[Dict], + obstacles: List[Tuple[float, float]] = None) -> List[Tuple[float, float]]: + """ + 使用遗传算法规划路径 + + Args: + start: 起点坐标 (x, y) + goal: 终点坐标 (x, y) + map_width: 地图宽度 + map_height: 地图高度 + threat_areas: 危险区域列表 + obstacles: 障碍物列表 + + Returns: + 路径点列表,如果找不到路径返回空列表 + """ + # 初始化种群 + population = self._initialize_population(start, goal, map_width, map_height) + + # 进化迭代 + best_route = None + best_fitness = -1 + + for generation in range(self.generations): + # 评估种群适应度 + fitness_scores = self._evaluate_fitness(population, start, goal, threat_areas, obstacles) + + # 检查最佳个体 + max_fitness_idx = np.argmax(fitness_scores) + if fitness_scores[max_fitness_idx] > best_fitness: + best_fitness = fitness_scores[max_fitness_idx] + best_route = population[max_fitness_idx] + + # 如果找到了有效路径且适应度足够高,可以提前结束 + if best_fitness > 0.8: + break + + # 选择精英个体 + elite_indices = np.argsort(fitness_scores)[-self.elite_size:] + elites = [population[i] for i in elite_indices] + + # 选择父代 + parents = self._selection(population, fitness_scores) + + # 交叉和变异产生新一代 + new_population = elites.copy() # 精英保留 + + while len(new_population) < self.population_size: + # 随机选择两个父代 + parent1, parent2 = random.sample(parents, 2) + + # 交叉 + if random.random() < self.crossover_rate: + child = self._crossover(parent1, parent2) + else: + child = parent1.copy() + + # 变异 + child = self._mutation(child, map_width, map_height) + + # 添加到新种群 + new_population.append(child) + + # 更新种群 + population = new_population[:self.population_size] + + # 返回最佳路径(如果找到) + if best_route: + return self._smooth_path(best_route, start, goal) + return [] + + def _initialize_population(self, start: Tuple[float, float], goal: Tuple[float, float], + map_width: int, map_height: int) -> List[List[Tuple[float, float]]]: + """初始化种群""" + population = [] + + # 直接连接起点和终点的个体 + direct_route = [start, goal] + population.append(direct_route) + + # 随机生成其他个体 + for _ in range(self.population_size - 1): + # 随机决定路径点数量(2-10个中间点) + num_waypoints = random.randint(2, 10) + + # 创建包含起点和终点的路径 + route = [start] + + # 添加随机中间点 + for _ in range(num_waypoints): + x = random.uniform(0, map_width) + y = random.uniform(0, map_height) + route.append((x, y)) + + route.append(goal) + population.append(route) + + return population + + def _evaluate_fitness(self, population: List[List[Tuple[float, float]]], + start: Tuple[float, float], goal: Tuple[float, float], + threat_areas: List[Dict], obstacles: List[Tuple[float, float]] = None) -> np.ndarray: + """评估种群适应度""" + fitness_scores = np.zeros(len(population)) + + for i, route in enumerate(population): + # 检查路径是否包含起点和终点 + if route[0] != start or route[-1] != goal: + fitness_scores[i] = 0 + continue + + # 路径长度(越短越好) + path_length = self._calculate_path_length(route) + length_fitness = 1.0 / (1.0 + path_length / 1000.0) # 归一化,短路径得高分 + + # 穿过危险区域的惩罚 + danger_penalty = 0 + for j in range(len(route) - 1): + segment_danger = self._segment_danger(route[j], route[j+1], threat_areas, obstacles) + danger_penalty += segment_danger + + danger_fitness = 1.0 / (1.0 + danger_penalty) # 归一化,无危险得高分 + + # 路径平滑度(转向次数和角度变化) + smoothness = self._calculate_smoothness(route) + smoothness_fitness = 1.0 / (1.0 + smoothness) # 归一化,平滑路径得高分 + + # 综合评分(权重可调整) + fitness_scores[i] = 0.4 * length_fitness + 0.5 * danger_fitness + 0.1 * smoothness_fitness + + return fitness_scores + + def _calculate_path_length(self, route: List[Tuple[float, float]]) -> float: + """计算路径总长度""" + length = 0 + for i in range(len(route) - 1): + dx = route[i+1][0] - route[i][0] + dy = route[i+1][1] - route[i][1] + length += math.sqrt(dx*dx + dy*dy) + return length + + def _segment_danger(self, p1: Tuple[float, float], p2: Tuple[float, float], + threat_areas: List[Dict], obstacles: List[Tuple[float, float]] = None) -> float: + """计算路径段穿过危险区域的程度""" + danger = 0 + + # 采样点检测 + num_samples = max(int(self._distance(p1, p2) / self.grid_resolution), 5) + + for i in range(num_samples + 1): + t = i / num_samples + x = p1[0] + t * (p2[0] - p1[0]) + y = p1[1] + t * (p2[1] - p1[1]) + + # 检查是否在危险区域内 + if self._is_in_threat_areas(x, y, threat_areas): + danger += 1 + + # 检查是否与障碍物碰撞 + if obstacles and self._is_in_obstacles(x, y, obstacles): + danger += 10 # 障碍物惩罚更高 + + return danger / (num_samples + 1) # 归一化 + + def _is_in_threat_areas(self, x: float, y: float, threat_areas: List[Dict]) -> bool: + """检查点是否在危险区域中""" + for area in threat_areas: + area_type = area.get('type', '') + + if area_type == 'circle': + center = area.get('center', (0, 0)) + radius = area.get('radius', 0) + distance = math.sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2) + if distance <= radius: + return True + + elif area_type == 'rectangle': + rect = area.get('rect', (0, 0, 0, 0)) + x1, y1, width, height = rect + if x1 <= x <= x1 + width and y1 <= y <= y1 + height: + return True + + elif area_type == 'polygon': + points = area.get('points', []) + if self._point_in_polygon(x, y, points): + return True + + return False + + def _point_in_polygon(self, x: float, y: float, polygon: List[Tuple[float, float]]) -> bool: + """检查点是否在多边形内(射线法)""" + if len(polygon) < 3: + return False + + inside = False + j = len(polygon) - 1 + + for i in range(len(polygon)): + xi, yi = polygon[i] + xj, yj = polygon[j] + + # 检查点是否在多边形边上 + if (yi == y and xi == x) or (yj == y and xj == x): + return True + + # 射线法判断点是否在多边形内部 + intersect = ((yi > y) != (yj > y)) and (x < (xj - xi) * (y - yi) / (yj - yi) + xi) + if intersect: + inside = not inside + + j = i + + return inside + + def _is_in_obstacles(self, x: float, y: float, obstacles: List[Tuple[float, float]]) -> bool: + """检查点是否在障碍物中""" + for obstacle_x, obstacle_y in obstacles: + distance = math.sqrt((x - obstacle_x) ** 2 + (y - obstacle_y) ** 2) + if distance < self.grid_resolution: + return True + return False + + def _calculate_smoothness(self, route: List[Tuple[float, float]]) -> float: + """计算路径的平滑度(转向角度变化的总和)""" + if len(route) < 3: + return 0 + + total_angle_change = 0 + + for i in range(1, len(route) - 1): + v1 = (route[i][0] - route[i-1][0], route[i][1] - route[i-1][1]) + v2 = (route[i+1][0] - route[i][0], route[i+1][1] - route[i][1]) + + # 归一化向量 + len1 = math.sqrt(v1[0]*v1[0] + v1[1]*v1[1]) + len2 = math.sqrt(v2[0]*v2[0] + v2[1]*v2[1]) + + if len1 > 0 and len2 > 0: + v1_norm = (v1[0]/len1, v1[1]/len1) + v2_norm = (v2[0]/len2, v2[1]/len2) + + # 计算点积 + dot_product = v1_norm[0]*v2_norm[0] + v1_norm[1]*v2_norm[1] + dot_product = max(-1, min(1, dot_product)) # 限制范围 + + # 计算角度变化 + angle_change = math.acos(dot_product) + total_angle_change += angle_change + + return total_angle_change + + def _selection(self, population: List[List[Tuple[float, float]]], fitness_scores: np.ndarray) -> List[List[Tuple[float, float]]]: + """选择操作,使用轮盘赌(roulette wheel)方法""" + # 轮盘赌选择 + selection_probs = fitness_scores / np.sum(fitness_scores) + selected_indices = np.random.choice( + len(population), + size=self.population_size, + p=selection_probs, + replace=True + ) + + return [population[i] for i in selected_indices] + + def _crossover(self, route1: List[Tuple[float, float]], route2: List[Tuple[float, float]]) -> List[Tuple[float, float]]: + """交叉操作,综合两个父代路径""" + if len(route1) <= 2 or len(route2) <= 2: + return route1.copy() if len(route1) > len(route2) else route2.copy() + + # 保证起点和终点 + start = route1[0] + end = route1[-1] + + # 从两条路径的中间点随机选择 + mid_points1 = route1[1:-1] + mid_points2 = route2[1:-1] + + # 选择交叉点 + crossover_point1 = random.randint(0, len(mid_points1)) + crossover_point2 = random.randint(0, len(mid_points2)) + + # 创建子代路径(保持起点和终点) + child = [start] + + # 添加前半段和后半段 + child.extend(mid_points1[:crossover_point1]) + child.extend(mid_points2[crossover_point2:]) + + # 添加终点 + child.append(end) + + return child + + def _mutation(self, route: List[Tuple[float, float]], map_width: int, map_height: int) -> List[Tuple[float, float]]: + """变异操作,随机修改路径中的点""" + # 复制路径以避免修改原始路径 + mutated_route = route.copy() + + # 仅对中间点进行变异,保留起点和终点 + for i in range(1, len(mutated_route) - 1): + # 对每个点以变异率进行变异 + if random.random() < self.mutation_rate: + # 在原点附近随机偏移 + delta_x = random.uniform(-50, 50) + delta_y = random.uniform(-50, 50) + + new_x = max(0, min(map_width, mutated_route[i][0] + delta_x)) + new_y = max(0, min(map_height, mutated_route[i][1] + delta_y)) + + mutated_route[i] = (new_x, new_y) + + # 有一定概率添加或删除中间点 + if random.random() < self.mutation_rate and len(mutated_route) > 3: + # 随机删除一个中间点 + idx_to_remove = random.randint(1, len(mutated_route) - 2) + mutated_route.pop(idx_to_remove) + + if random.random() < self.mutation_rate: + # 随机添加一个中间点 + idx_to_add = random.randint(1, len(mutated_route) - 1) + prev_point = mutated_route[idx_to_add - 1] + next_point = mutated_route[idx_to_add] + + # 在两点之间随机生成一个新点 + new_x = (prev_point[0] + next_point[0]) / 2 + random.uniform(-20, 20) + new_y = (prev_point[1] + next_point[1]) / 2 + random.uniform(-20, 20) + + new_x = max(0, min(map_width, new_x)) + new_y = max(0, min(map_height, new_y)) + + mutated_route.insert(idx_to_add, (new_x, new_y)) + + return mutated_route + + def _distance(self, p1: Tuple[float, float], p2: Tuple[float, float]) -> float: + """计算两点间距离""" + return math.sqrt((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2) + + def _smooth_path(self, route: List[Tuple[float, float]], start: Tuple[float, float], goal: Tuple[float, float]) -> List[Tuple[float, float]]: + """平滑路径,去除冗余点""" + # 确保路径起点和终点正确 + if route[0] != start: + route[0] = start + if route[-1] != goal: + route[-1] = goal + + # 如果路径点太少,直接返回 + if len(route) <= 2: + return route + + # 移除共线的冗余点 + smoothed_route = [route[0]] # 保留起点 + + for i in range(1, len(route) - 1): + prev = smoothed_route[-1] + curr = route[i] + next_point = route[i + 1] + + # 检查三点是否共线(或接近共线) + v1 = (curr[0] - prev[0], curr[1] - prev[1]) + v2 = (next_point[0] - curr[0], next_point[1] - curr[1]) + + len1 = math.sqrt(v1[0]*v1[0] + v1[1]*v1[1]) + len2 = math.sqrt(v2[0]*v2[0] + v2[1]*v2[1]) + + # 避免除零错误 + if len1 > 0.001 and len2 > 0.001: + # 归一化向量 + v1_norm = (v1[0]/len1, v1[1]/len1) + v2_norm = (v2[0]/len2, v2[1]/len2) + + # 计算点积 + dot_product = v1_norm[0]*v2_norm[0] + v1_norm[1]*v2_norm[1] + + # 如果三点不共线,保留中间点 + if abs(dot_product) < 0.98: # 角度差异大于约11度 + smoothed_route.append(curr) + else: + # 如果点太近,可以考虑跳过 + if len1 > self.grid_resolution or len2 > self.grid_resolution: + smoothed_route.append(curr) + + smoothed_route.append(route[-1]) # 保留终点 + + return smoothed_route + + def smooth_path(self, path: List[Tuple[float, float]], + threat_areas: List[Dict], + obstacles: List[Tuple[float, float]] = None, + weight_data: float = 0.5, + weight_smooth: float = 0.3, + tolerance: float = 0.000001) -> List[Tuple[float, float]]: + """ + 使用平滑算法优化路径 (与A*接口兼容) + + Args: + path: 原始路径 + threat_areas: 危险区域 + obstacles: 障碍物 + weight_data: 数据权重 + weight_smooth: 平滑权重 + tolerance: 收敛阈值 + + Returns: + 平滑后的路径 + """ + # 如果路径点太少,不需要平滑 + if len(path) <= 2: + return path + + # 将路径转换为numpy数组,方便操作 + path_array = np.array(path) + smooth_path = path_array.copy() + + # 迭代优化 + change = tolerance + while change >= tolerance: + change = 0.0 + + # 对每个点进行优化(除了起点和终点) + for i in range(1, len(path) - 1): + for j in range(2): # x, y + aux = smooth_path[i][j] + smooth_path[i][j] += weight_data * (path_array[i][j] - smooth_path[i][j]) + smooth_path[i][j] += weight_smooth * (smooth_path[i-1][j] + smooth_path[i+1][j] - 2.0 * smooth_path[i][j]) + change += abs(aux - smooth_path[i][j]) + + # 检查平滑后的路径是否经过危险区域或障碍物 + for i in range(len(smooth_path)): + x, y = smooth_path[i] + if self._is_in_threat_areas(x, y, threat_areas) or (obstacles and self._is_in_obstacles(x, y, obstacles)): + return path # 如果平滑后路径穿过危险区域,返回原路径 + + return [(x, y) for x, y in smooth_path] \ No newline at end of file diff --git a/Src/command_center/path_planning/path_planner.py b/Src/command_center/path_planning/path_planner.py index 26e6dd13..6db68ae2 100644 --- a/Src/command_center/path_planning/path_planner.py +++ b/Src/command_center/path_planning/path_planner.py @@ -1,57 +1,206 @@ # -*- coding: utf-8 -*- # File: path_planner.py -# Purpose: 路径规划的核心逻辑,可能包含调用不同路径规划算法的接口。 +# Purpose: 路径规划的核心逻辑,包含调用不同路径规划算法的接口。 -from typing import List, Tuple, Optional, Dict -from .hybrid_astar import HybridAStar +from typing import List, Tuple, Optional, Dict, Union +from .astar import AStar +from .genetic_algorithm import GeneticAlgorithm +from .rrt_algorithm import RRTAlgorithm import numpy as np import json import time class PathPlanner: - def __init__(self): - self.planner = HybridAStar() - self.current_paths: Dict[str, List[Tuple[float, float, float]]] = {} + def __init__(self, algorithm="astar", grid_resolution=5.0): + """ + 初始化路径规划器 + + Args: + algorithm: 使用的算法,可选 "astar"、"genetic" 或 "rrt" + grid_resolution: 网格分辨率 + """ + self.algorithm = algorithm + self.grid_resolution = grid_resolution + + # 根据指定算法创建规划器 + self.planner = self._create_planner(algorithm, grid_resolution) + + # 初始化路径、障碍物和危险区域 + self.current_paths: Dict[str, List[Tuple[float, float]]] = {} self.obstacles: List[Tuple[float, float]] = [] + self.threat_areas: List[Dict] = [] + + # 算法特定参数 + self.ga_population_size = 100 + self.ga_generations = 100 + self.rrt_step_size = 20 + self.rrt_max_iterations = 1000 + + def _create_planner(self, algorithm, grid_resolution): + """创建对应算法的规划器实例""" + if algorithm == "genetic": + return GeneticAlgorithm(grid_resolution=grid_resolution) + elif algorithm == "rrt": + return RRTAlgorithm(grid_resolution=grid_resolution) + else: # 默认使用 A* + return AStar(grid_resolution=grid_resolution) + + def set_algorithm(self, algorithm: str): + """设置使用的算法""" + if algorithm != self.algorithm: + self.algorithm = algorithm + self.planner = self._create_planner(algorithm, self.grid_resolution) + + # 同时设置算法特定参数 + if algorithm == "genetic": + self.planner.population_size = self.ga_population_size + self.planner.generations = self.ga_generations + elif algorithm == "rrt": + self.planner.step_size = self.rrt_step_size + self.planner.max_iterations = self.rrt_max_iterations def plan_path(self, drone_id: str, - start: Tuple[float, float, float], - goal: Tuple[float, float, float], + start: Union[Tuple[float, float, float], Tuple[float, float]], + goal: Union[Tuple[float, float, float], Tuple[float, float]], + map_width: int = 1000, + map_height: int = 1000, vehicle_length: float = 4.0, - vehicle_width: float = 2.0) -> Optional[List[Tuple[float, float, float]]]: + vehicle_width: float = 2.0) -> Optional[List[Union[Tuple[float, float, float], Tuple[float, float]]]]: """ 为指定无人机规划路径 参数: drone_id: 无人机ID - start: (x, y, theta) 起点位置和航向 - goal: (x, y, theta) 终点位置和航向 + start: 起点位置(x, y) 或 (x, y, theta) + goal: 终点位置(x, y) 或 (x, y, theta) + map_width: 地图宽度 + map_height: 地图高度 vehicle_length: 车辆长度 vehicle_width: 车辆宽度 返回: - 路径点列表 [(x, y, theta), ...] 或 None(如果找不到路径) + 路径点列表 [(x, y), ...],如果找不到路径返回None """ + # 确保坐标不包含航向信息 + start_xy = start[:2] if len(start) >= 2 else start + goal_xy = goal[:2] if len(goal) >= 2 else goal + + # 设置算法特定参数 + if self.algorithm == "genetic": + self.planner.population_size = self.ga_population_size + self.planner.generations = self.ga_generations + elif self.algorithm == "rrt": + self.planner.step_size = self.rrt_step_size + self.planner.max_iterations = self.rrt_max_iterations + # 规划路径 path = self.planner.plan( - start=start, - goal=goal, - obstacles=self.obstacles, - vehicle_length=vehicle_length, - vehicle_width=vehicle_width + start=start_xy, + goal=goal_xy, + map_width=map_width, + map_height=map_height, + threat_areas=self.threat_areas, + obstacles=self.obstacles ) if path: - self.current_paths[drone_id] = path - return path + # 平滑路径 + smoothed_path = self.planner.smooth_path( + path=path, + threat_areas=self.threat_areas, + obstacles=self.obstacles + ) + self.current_paths[drone_id] = smoothed_path + return smoothed_path + return None + def add_obstacle_point(self, x: float, y: float, radius: float = 30.0): + """ + 添加一个点状障碍物 + + Args: + x: 点的x坐标 + y: 点的y坐标 + radius: 点的影响半径 + """ + # 将点状障碍物转换为圆形危险区域 + area = { + 'type': 'circle', + 'center': (x, y), + 'radius': radius + } + self.threat_areas.append(area) + # 同时添加到障碍物列表 + self.obstacles.append((x, y)) + + def add_obstacle_circle(self, x: float, y: float, radius: float): + """ + 添加一个圆形障碍物 + + Args: + x: 圆心的x坐标 + y: 圆心的y坐标 + radius: 圆的半径 + """ + area = { + 'type': 'circle', + 'center': (x, y), + 'radius': radius + } + self.threat_areas.append(area) + + def add_obstacle_rectangle(self, x1: float, y1: float, x2: float, y2: float): + """ + 添加一个矩形障碍物 + + Args: + x1: 左上角x坐标 + y1: 左上角y坐标 + x2: 右下角x坐标 + y2: 右下角y坐标 + """ + # 确保(x1,y1)是左上角,(x2,y2)是右下角 + top_left_x = min(x1, x2) + top_left_y = min(y1, y2) + width = abs(x2 - x1) + height = abs(y2 - y1) + + area = { + 'type': 'rectangle', + 'rect': (top_left_x, top_left_y, width, height) + } + self.threat_areas.append(area) + + def add_obstacle_polygon(self, points: List[Tuple[float, float]]): + """ + 添加一个多边形障碍物 + + Args: + points: 多边形顶点列表 [(x1,y1), (x2,y2), ...] + """ + if len(points) >= 3: # 确保至少有3个点 + area = { + 'type': 'polygon', + 'points': points + } + self.threat_areas.append(area) + + def clear_obstacles(self): + """清除所有障碍物和危险区域""" + self.obstacles.clear() + self.threat_areas.clear() + def update_obstacles(self, obstacles: List[Tuple[float, float]]): """更新障碍物列表""" self.obstacles = obstacles - def get_current_path(self, drone_id: str) -> Optional[List[Tuple[float, float, float]]]: + def update_threat_areas(self, threat_areas: List[Dict]): + """更新危险区域列表""" + self.threat_areas = threat_areas + + def get_current_path(self, drone_id: str) -> Optional[List[Tuple[float, float]]]: """获取指定无人机的当前路径""" return self.current_paths.get(drone_id) @@ -59,44 +208,6 @@ class PathPlanner: """清除指定无人机的路径""" if drone_id in self.current_paths: del self.current_paths[drone_id] - - def smooth_path(self, path: List[Tuple[float, float, float]], weight_data: float = 0.5, - weight_smooth: float = 0.3, tolerance: float = 0.000001) -> List[Tuple[float, float, float]]: - """ - 使用平滑算法优化路径 - - 参数: - path: 原始路径 - weight_data: 数据项权重 - weight_smooth: 平滑项权重 - tolerance: 收敛阈值 - - 返回: - 平滑后的路径 - """ - if len(path) <= 2: - return path - - # 将路径转换为numpy数组 - path_array = np.array(path) - - # 初始化平滑后的路径 - smooth_path = path_array.copy() - - # 迭代优化 - change = tolerance - while change >= tolerance: - change = 0.0 - - # 对每个点进行优化(除了起点和终点) - for i in range(1, len(path) - 1): - for j in range(3): # x, y, theta - aux = smooth_path[i][j] - smooth_path[i][j] += weight_data * (path_array[i][j] - smooth_path[i][j]) - smooth_path[i][j] += weight_smooth * (smooth_path[i-1][j] + smooth_path[i+1][j] - 2.0 * smooth_path[i][j]) - change += abs(aux - smooth_path[i][j]) - - return [(x, y, theta) for x, y, theta in smooth_path] def save_path(self, drone_id: str, filename: str): """保存路径到文件""" diff --git a/Src/command_center/path_planning/rrt_algorithm.py b/Src/command_center/path_planning/rrt_algorithm.py new file mode 100644 index 00000000..d943f9fb --- /dev/null +++ b/Src/command_center/path_planning/rrt_algorithm.py @@ -0,0 +1,316 @@ +# -*- coding: utf-8 -*- +# File: rrt_algorithm.py +# Purpose: 实现RRT(快速随机树)路径规划算法,支持避开危险区域 + +import numpy as np +import math +import random +from typing import List, Tuple, Dict, Optional + +class Node: + """RRT树节点""" + def __init__(self, x: float, y: float): + self.x = x + self.y = y + self.parent = None + self.cost = 0.0 # 从起点到当前节点的代价 + +class RRTAlgorithm: + """RRT路径规划算法""" + + def __init__(self, + grid_resolution: float = 5.0, + max_iterations: int = 2000, + step_size: float = 20.0, + goal_sample_rate: float = 0.1, + search_radius: float = 50.0): + """ + 初始化RRT算法 + + Args: + grid_resolution: 网格分辨率 + max_iterations: 最大迭代次数 + step_size: 步长 + goal_sample_rate: 采样目标点的概率 + search_radius: 搜索半径 + """ + self.grid_resolution = grid_resolution + self.max_iterations = max_iterations + self.step_size = step_size + self.goal_sample_rate = goal_sample_rate + self.search_radius = search_radius + + def plan(self, start: Tuple[float, float], + goal: Tuple[float, float], + map_width: int, map_height: int, + threat_areas: List[Dict], + obstacles: List[Tuple[float, float]] = None) -> List[Tuple[float, float]]: + """ + 使用RRT算法规划路径 + + Args: + start: 起点坐标 (x, y) + goal: 终点坐标 (x, y) + map_width: 地图宽度 + map_height: 地图高度 + threat_areas: 危险区域列表 + obstacles: 障碍物列表 + + Returns: + 路径点列表,如果找不到路径返回空列表 + """ + # 创建起点和终点节点 + start_node = Node(start[0], start[1]) + goal_node = Node(goal[0], goal[1]) + + # 初始化树 + tree = [start_node] + + # 迭代构建树 + for i in range(self.max_iterations): + # 以一定概率直接取目标点,提高搜索效率 + if random.random() < self.goal_sample_rate: + random_point = (goal_node.x, goal_node.y) + else: + # 随机采样点 + random_point = ( + random.uniform(0, map_width), + random.uniform(0, map_height) + ) + + # 找到树中离随机点最近的节点 + nearest_node = self._find_nearest_node(tree, random_point) + + # 朝随机点方向扩展固定步长 + new_node = self._steer(nearest_node, random_point, self.step_size) + + # 检查路径是否有效 + if new_node and self._is_path_valid(nearest_node, new_node, threat_areas, obstacles): + # 将新节点添加到树中 + new_node.parent = nearest_node + new_node.cost = nearest_node.cost + self._distance((nearest_node.x, nearest_node.y), + (new_node.x, new_node.y)) + tree.append(new_node) + + # 检查是否可以连接到目标点 + dist_to_goal = self._distance((new_node.x, new_node.y), (goal_node.x, goal_node.y)) + if dist_to_goal < self.step_size: + # 尝试直接连接到目标点 + if self._is_path_valid(new_node, goal_node, threat_areas, obstacles): + goal_node.parent = new_node + goal_node.cost = new_node.cost + dist_to_goal + # 找到路径,提前结束 + return self._extract_path(goal_node) + + # RRT* 优化 (可选,提高路径质量) + # 尝试重新连接附近节点以获得更优路径 + self._rewire(tree, new_node, threat_areas, obstacles) + + # 如果达到最大迭代次数但仍未找到到达目标的路径 + # 尝试找到离目标最近的节点,并返回到该节点的路径 + closest_node = self._find_nearest_node(tree, (goal_node.x, goal_node.y)) + path = self._extract_path(closest_node) + + # 如果最近节点离目标足够近,则认为找到了路径 + if self._distance((closest_node.x, closest_node.y), (goal_node.x, goal_node.y)) < self.step_size * 2: + return path + + # 否则找不到有效路径,返回空列表 + return [] + + def _find_nearest_node(self, tree: List[Node], point: Tuple[float, float]) -> Node: + """找到树中离指定点最近的节点""" + min_dist = float('inf') + nearest_node = None + + for node in tree: + dist = self._distance((node.x, node.y), point) + if dist < min_dist: + min_dist = dist + nearest_node = node + + return nearest_node + + def _steer(self, from_node: Node, to_point: Tuple[float, float], step_size: float) -> Optional[Node]: + """从起始节点向目标点方向移动固定步长""" + dx = to_point[0] - from_node.x + dy = to_point[1] - from_node.y + dist = math.sqrt(dx*dx + dy*dy) + + if dist < 0.0001: # 距离近乎为0 + return None + + # 按照指定的步长移动 + ratio = min(step_size / dist, 1.0) + new_x = from_node.x + dx * ratio + new_y = from_node.y + dy * ratio + + new_node = Node(new_x, new_y) + return new_node + + def _is_path_valid(self, from_node: Node, to_node: Node, + threat_areas: List[Dict], obstacles: List[Tuple[float, float]] = None) -> bool: + """检查两个节点之间的路径是否有效(不经过障碍物和危险区域)""" + # 采样点检测 + dist = self._distance((from_node.x, from_node.y), (to_node.x, to_node.y)) + num_samples = max(int(dist / self.grid_resolution), 5) + + for i in range(num_samples + 1): + t = i / num_samples + x = from_node.x + t * (to_node.x - from_node.x) + y = from_node.y + t * (to_node.y - from_node.y) + + # 检查是否在危险区域内 + if self._is_in_threat_areas(x, y, threat_areas): + return False + + # 检查是否与障碍物碰撞 + if obstacles and self._is_in_obstacles(x, y, obstacles): + return False + + return True + + def _rewire(self, tree: List[Node], new_node: Node, + threat_areas: List[Dict], obstacles: List[Tuple[float, float]] = None) -> None: + """重新连接树中节点以优化路径代价 (RRT* 算法的核心步骤)""" + # 找到搜索半径内的所有节点 + near_nodes = [] + for node in tree: + if node is not new_node: # 排除新节点本身 + dist = self._distance((node.x, node.y), (new_node.x, new_node.y)) + if dist < self.search_radius: + near_nodes.append(node) + + # 对于搜索半径内的每个节点,检查是否通过新节点创建的路径更好 + for node in near_nodes: + # 计算通过新节点到达当前节点的潜在代价 + potential_cost = new_node.cost + self._distance((new_node.x, new_node.y), (node.x, node.y)) + + # 如果代价更低且路径有效,则重新连接 + if potential_cost < node.cost and self._is_path_valid(new_node, node, threat_areas, obstacles): + node.parent = new_node + node.cost = potential_cost + + def _extract_path(self, goal_node: Node) -> List[Tuple[float, float]]: + """从目标节点回溯生成路径""" + path = [] + current = goal_node + + # 回溯到起点 + while current: + path.append((current.x, current.y)) + current = current.parent + + # 反转路径顺序(从起点到终点) + return list(reversed(path)) + + def _distance(self, p1: Tuple[float, float], p2: Tuple[float, float]) -> float: + """计算两点间欧几里得距离""" + return math.sqrt((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2) + + def _is_in_threat_areas(self, x: float, y: float, threat_areas: List[Dict]) -> bool: + """检查点是否在危险区域中""" + for area in threat_areas: + area_type = area.get('type', '') + + if area_type == 'circle': + center = area.get('center', (0, 0)) + radius = area.get('radius', 0) + distance = math.sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2) + if distance <= radius: + return True + + elif area_type == 'rectangle': + rect = area.get('rect', (0, 0, 0, 0)) + x1, y1, width, height = rect + if x1 <= x <= x1 + width and y1 <= y <= y1 + height: + return True + + elif area_type == 'polygon': + points = area.get('points', []) + if self._point_in_polygon(x, y, points): + return True + + return False + + def _point_in_polygon(self, x: float, y: float, polygon: List[Tuple[float, float]]) -> bool: + """检查点是否在多边形内(射线法)""" + if len(polygon) < 3: + return False + + inside = False + j = len(polygon) - 1 + + for i in range(len(polygon)): + xi, yi = polygon[i] + xj, yj = polygon[j] + + # 检查点是否在多边形边上 + if (yi == y and xi == x) or (yj == y and xj == x): + return True + + # 射线法判断点是否在多边形内部 + intersect = ((yi > y) != (yj > y)) and (x < (xj - xi) * (y - yi) / (yj - yi) + xi) + if intersect: + inside = not inside + + j = i + + return inside + + def _is_in_obstacles(self, x: float, y: float, obstacles: List[Tuple[float, float]]) -> bool: + """检查点是否在障碍物中""" + for obstacle_x, obstacle_y in obstacles: + distance = math.sqrt((x - obstacle_x) ** 2 + (y - obstacle_y) ** 2) + if distance < self.grid_resolution: + return True + return False + + def smooth_path(self, path: List[Tuple[float, float]], + threat_areas: List[Dict], + obstacles: List[Tuple[float, float]] = None, + weight_data: float = 0.5, + weight_smooth: float = 0.3, + tolerance: float = 0.000001) -> List[Tuple[float, float]]: + """ + 使用平滑算法优化路径 (与A*接口兼容) + + Args: + path: 原始路径 + threat_areas: 危险区域 + obstacles: 障碍物 + weight_data: 数据权重 + weight_smooth: 平滑权重 + tolerance: 收敛阈值 + + Returns: + 平滑后的路径 + """ + # 如果路径点太少,不需要平滑 + if len(path) <= 2: + return path + + # 将路径转换为numpy数组,方便操作 + path_array = np.array(path) + smooth_path = path_array.copy() + + # 迭代优化 + change = tolerance + while change >= tolerance: + change = 0.0 + + # 对每个点进行优化(除了起点和终点) + for i in range(1, len(path) - 1): + for j in range(2): # x, y + aux = smooth_path[i][j] + smooth_path[i][j] += weight_data * (path_array[i][j] - smooth_path[i][j]) + smooth_path[i][j] += weight_smooth * (smooth_path[i-1][j] + smooth_path[i+1][j] - 2.0 * smooth_path[i][j]) + change += abs(aux - smooth_path[i][j]) + + # 检查平滑后的路径是否经过危险区域或障碍物 + for i in range(len(smooth_path)): + x, y = smooth_path[i] + if self._is_in_threat_areas(x, y, threat_areas) or (obstacles and self._is_in_obstacles(x, y, obstacles)): + return path # 如果平滑后路径穿过危险区域,返回原路径 + + return [(x, y) for x, y in smooth_path] \ No newline at end of file diff --git a/Src/command_center/ui/__pycache__/__init__.cpython-312.pyc b/Src/command_center/ui/__pycache__/__init__.cpython-312.pyc index c30c332363c2ec9c14e4f9772965d3d403b44cd2..ff85d770ddbbed5a40c7f97be4db8a49f3612b56 100644 GIT binary patch delta 123 zcmV->0EGXQ0p%MA?*000004mTrcmyr!HUG;(h002iiTsSr{Gd5gzZ*pr~b8lvJ zcVTj6TxV%?Xmw&-Lr+9SAT1!}iLd6Itna*t<*uIRjb=f;w&x3TN95e0ssJD4bT7p diff --git a/Src/command_center/ui/__pycache__/__init__.cpython-37.pyc b/Src/command_center/ui/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2899a17d6b98ec49829bcee843d7902f404b9b8f GIT binary patch literal 162 zcmZ?b<>g`k0zPx)jOjr7F^B^Lj6jA15Erumi4=xl22Do4l?+87VFdBj1ITr?ig~(Z z`I8-;Pj+<2yx6<`$?k1Ww=aFZqC3Xf-^DeipeR2pHMt}vxF|U$IX^cyF)t-PIW?~& lwJ4@EGbTPhGcU6wK3=b&@)n0pZhlH>PO2Tql42lc001m$FDd{4 literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/__pycache__/algorithm_config_view.cpython-37.pyc b/Src/command_center/ui/__pycache__/algorithm_config_view.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eef51b5962f7c92faa75bc2b7b3388105d4c0818 GIT binary patch literal 2906 zcmb_e-ES0C6u)=&YxcVz6lg&p@guGeJZUtIk&l9Cg2=X5&7{d>x_7#rva_?^nE|Qg z0b5cP`O*+j0&XK%O`wpF2v}PFk9jk8y4^QleD|C?vt=Rh<|H$7&bhyP?(dv?@40tx zHk%^wPz5i_bp&6pB1<4W92C<#+m zWHYHG&6JWd(@NURC>e_Vq)OJzDLG0G5Efz48N#AEosKAZ78@dkcoV#nLdHuJKQ&lc zcf53Qc(3)vLG6U)qVmBLC0;zJjp`LJ9(L{Vy{_X}RWDgQz%921D6qe8aWm|hEDl)a zsD*a9I8-yLDCLU#EO)e`w{!rF>!qVX=Z|#T*2+341?ovfr-aN}Pb3uVH$l*eLK#t{ zSu#c$WzvkSM1t6qD2uW93{he%!DRTyXGxSO35Yc5b?>Q^Ep9mD=73clGs?q;e!N6O z$M~1v#ewXN@NvjAsna<^$rPze_-6#%&_jiY7qi`(&OKRGjjG|Os+SOd*8t{GTd$14 zKdGvvie}rY`aAje6L7e{f5g8rzw+Rll?P`=)|T$9EdJr&zuEZh%*epU`}U92xHX}d zoRJ}38Yx+(sa2U;(yNZnM_gk>Yf)EAg1dSQ$nUJ3;GN((ft+!@5SpTsTb~o=&KA@4 zc$0vR!8-u2%^`A(_at>7q0{uF_dWGj+ zkottwHzfmxO!SH|0=ZAfw7z9Z77_t}G>|5GC$`pK0{pg6?+f+qsEfU{<&c|$<+taJ z#=_;*dzYH0&o-78+%7P!-kSGk>&@Bk{N>AQ_pdd6U38&7lGUq^{2#t<&OL6k`F9?! z&41tcGqja|`VpUpyUKuv=at=|@Xszc3%Y;dj(`55KfAPg>2AM^J7@LUbaVD>zZkc9 z>hj8?dOMvaR%l}l%0iQYRQ__q!hydgX98!9`Lm7X8?dYWyXX6b`?bfnR~CO+y)`G? zV_*OL75~v!jdQd8B80)gBkk@(a)h`U#6G;Yx_EKz{vCgL=14&9_RZ#{$ARz0)rHmi ztbgjff8k=k7xY+pe9gaoCCnVUfKVWbfuXR2V>#m(C71DoyI#p_wxe-%Tz9!)J4VTq zHO4-4bkpW(Y|jMk$91DT?s&4TJA+!atP5#atGK$C1>2BOEkh-D^qLok{)5(WFA3&W z`3F;Qpm0ork}Yd$&8oQ-%`vR1k^{A@S9LB_Tj>DZbRD6oVAPc?=ZtsCc>ysk%NbW|nltXDV0>|)=!|1IqlrrGx7LY_oVtU>g!o zlqyE)sLs3u6xu2jWG~&;-^;Z3EhKq2jNqkB&8Vssabzm#AbVvz6ZOxNwL? zQ=nohp3T#%fYaa&Psf}zx&3+E+h+KG;orUqU&x;qfY=LozXnZXPx0RIJl;<&Mj7uT zRn64lK9W*Z(_(G~<&3I+?rN2wMcfy}^5K{pjstnw@TyYMD-~6Jl0zW30YPHPVg1GZ zWMd(bX(lQU%Q24DLxm16fA~bvd246U2{ygBQIMeeh@FTlL2Sooijk-eqU?x09ZYPd hI>#eIjPr!plbztn?*@UshwDvD^E;7@^dH@>OSJ$1 literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/__pycache__/base_map_view.cpython-312.pyc b/Src/command_center/ui/__pycache__/base_map_view.cpython-312.pyc index 0200506d34aed270b7c171cbb3edb4586d907b41..13a5d21208ab17cb561949254f7089161a809762 100644 GIT binary patch delta 4077 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 delta 2185 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 C#YJEM delta 67 zcmeyfgmLu}M()$Ryj%=GFu7l8BllM(o?o^pRxt%d`B|ySC7WZJXD~C~+Wd{JUW)O{ V<}$@8oIoD8Hm5q{t;wQ6=>R4K7xVxC diff --git a/Src/command_center/ui/__pycache__/drone_detail_view.cpython-37.pyc b/Src/command_center/ui/__pycache__/drone_detail_view.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20df740ed423c990ca7418869758e30ea7398309 GIT binary patch literal 10434 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 diff --git a/Src/command_center/ui/__pycache__/drone_list_view.cpython-37.pyc b/Src/command_center/ui/__pycache__/drone_list_view.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34e2712dfc35b42a9fa7b4529da2ee9a0dac5c5f GIT binary patch literal 14735 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 GrT+)g1T|v- literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/__pycache__/drone_management.cpython-37.pyc b/Src/command_center/ui/__pycache__/drone_management.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebb25cde5e7b480d21a6e25cda73496e3854e5e2 GIT binary patch literal 6976 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 If5|ld4~oXKyZ`_I literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/__pycache__/drone_manager.cpython-37.pyc b/Src/command_center/ui/__pycache__/drone_manager.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbbb4d74e2940b9273666da8fcb6eab6bc3154e1 GIT binary patch literal 7690 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 literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/__pycache__/integrated_map_view.cpython-312.pyc b/Src/command_center/ui/__pycache__/integrated_map_view.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12f98d0457f829f7b6e1120d93c86cb4520d8a9c GIT binary patch literal 63380 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 literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/__pycache__/login_view.cpython-312.pyc b/Src/command_center/ui/__pycache__/login_view.cpython-312.pyc index 4a511513b17c7bf900f7230dd1f73135360b8b25..481fbf0434b1c8890bba55ecaf7f8e3bab72f778 100644 GIT binary patch delta 1315 zcmZ`&Z%kWN6u-AEZE0^`%f{B$jnPpCtr@t*WG=WdwlOiv+AJ~4Bpok>KH7)<6YqO3 z{E=i+5y7Cs-li;~s|kx;rgjq}NrM`{nW%{*HO#76On9aH1Sa5&pS<^Vm8$W6I642% zJ->6#TXokw%r_Q`36QsIqCRreI?Xs>|8(Q2heIHwi&|n#7ZbB~Ss~DX4iKss1tC2L zZ)#}(0A02rgV#7chHJcq>HC%lt-R&9BzeiC0j-)*`J;X`sOazdAU4qe|Znu)9WM&O9%rl!c_ zT_d0$$w9OoTIFBi9=XHf-mNX60>KdQ>U^7e;(YJ&)j~SGDUVrd< z1mmG7$Eu;nh)sA&KFd6LY{k4|Dv~s2&E7l6wv6^Jp}otfVF@*4k!M}6tMtPSEwDJ2 zQSB0{P3n@7?+-kcMNhA5s0kOL=pj(dIkf+Sh5Rk5Kd38fUMpHief9kVG>2#?>H8TO zT6b70Li+MJ`8(vkU?|skjZ%ojxLwcq+x;!B;zH!-Q_2|GfNO=9KR3A^yfdc>RKYNJ zA~D#^$NLgE78LnJ+#_(&zWuJc{F_s&bD28Il|?IGyi!bA=XK#0QV2hOfl!}hC3N+2 zDnGuUEPSGTG^5O>Bs$Jw8VONW&66LWA|H_)e=C%^+l5cBuPsg%(hK)qeZ7#r;w{b` zA0a+lBgaRnNXLySS~>HBPd`C_joXMyphrf!!|J%3i7@Q^&$vrBcZLSc5ONW$>p`_c z?q>Eq`=ix5pdj>e(J0G$A*L7!kdU@wM>gt8 ziAip-x$L$IKm5JEs-lx@UCH9}R>|jW-7n;-LP0Uu6BM}S5~HG4#E<5<0g6!+t3QoL zVu_Fxtq=11Tino$Y zlYQc}qRpy|6B&7anWb386cpuWr6!k57G~CE6rAkHEY8TaIg8rCC9b)$;y`Uh zB0!=_&?i4VGcPPNwOlVVFS8`RG*e*n9j^O~j1rUg@JKSsOwQ$BqRUXjn8KW`0o0>V z!<51TW=YpDWPx1>asmQm@lNJrlAC;zS6PM?=0HYqOf00Li0n37n3kF_SdAy6j zp&(=gG-o{(f+t86B-Oz1 zghTi`hx|njsH5Dka(Dn8HAhHa)CMSDBnTov5m}@SB6KD{5E7LV12VyeIzSBN14)4h zw#od$3gSGhK8)XFm{>g-izHw&ewuuf-Gt)|z{*xKKqNpey2W9Wo1apelWJGw1>}O9 kTC58sJ}@&fGJa>8{83m;^b3nCBcmtd1op2CU0C9Y@oB#j- diff --git a/Src/command_center/ui/__pycache__/login_view.cpython-37.pyc b/Src/command_center/ui/__pycache__/login_view.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74045d8eb9b5e188cfe8e3dfe1bc40bb58cbe73e GIT binary patch literal 2468 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 JM9Lc5{~K*OhIjw~ delta 977 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 literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/__pycache__/path_layer_view.cpython-37.pyc b/Src/command_center/ui/__pycache__/path_layer_view.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b8e135c51e718814ec95a1ae64c345b17ae5807 GIT binary patch literal 2764 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 delta 108 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 N(0N`RlPgGU{}0a)NG<>X literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/base_map_view.py b/Src/command_center/ui/base_map_view.py index a72685b8..5a6ce348 100644 --- a/Src/command_center/ui/base_map_view.py +++ b/Src/command_center/ui/base_map_view.py @@ -7,6 +7,7 @@ from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QToolBar, QAction, QFileDialog) from PyQt5.QtCore import Qt, pyqtSignal, QPointF, QRect, QRectF from PyQt5.QtGui import QPixmap, QPainter, QPen, QColor, QTransform, QCursor, QMouseEvent +from database.database import load_danger_zones class BaseMapView(QWidget): # 定义信号 @@ -80,6 +81,17 @@ class BaseMapView(QWidget): self.map_data_model.set_map(pixmap) self.reset_view() # Reset zoom/pan on new map load self.map_loaded.emit() + + # 清除旧的危险区域 + self.map_data_model.threat_areas.clear() + + + + # 从数据库加载危险区域 + danger_zones = load_danger_zones() + for zone in danger_zones: + self.map_data_model.add_threat_area(zone) + self.update_map() def reset_view(self): print("BaseMapView: reset_view called") # Debug print @@ -167,6 +179,39 @@ class BaseMapView(QWidget): for point in self.map_data_model.threat_points: painter.drawPoint(QPointF(point[0], point[1])) # Draw at original coords (transformed) + # 绘制危险区域 + if hasattr(self.map_data_model, 'threat_areas') and self.map_data_model.threat_areas: + for area in self.map_data_model.threat_areas: + area_type = area.get('type', '') + color = area.get('color', QColor(255, 0, 0, 128)) # 默认半透明红色 + + + # 设置画笔和画刷 + pen = QPen(color.darker(150), max(0.5, 2.0 / self.scale_factor)) + painter.setPen(pen) + painter.setBrush(color) + + if area_type == 'circle': + center = area.get('center', (0, 0)) + radius = area.get('radius', 50) + + painter.drawEllipse(QPointF(center[0], center[1]), radius, radius) + + + elif area_type == 'rectangle': + rect = area.get('rect', (0, 0, 100, 100)) + + painter.drawRect(QRectF(rect[0], rect[1], rect[2], rect[3])) + + elif area_type == 'polygon': + points = area.get('points', []) + if len(points) >= 3: + # 创建QPointF列表 + qpoints = [QPointF(p[0], p[1]) for p in points] + + # 绘制多边形 + painter.drawPolygon(*qpoints) + # Draw start point if self.map_data_model.start_point: point_size = max(1.0, 5.0 / self.scale_factor) diff --git a/Src/command_center/ui/drone_list_view.py b/Src/command_center/ui/drone_list_view.py index 69b4200a..ba0f265b 100644 --- a/Src/command_center/ui/drone_list_view.py +++ b/Src/command_center/ui/drone_list_view.py @@ -5,7 +5,8 @@ from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTableWidget, QTableWidgetItem, QComboBox, QColorDialog, QMenu, QAction, - QDialog, QLineEdit, QFormLayout, QMessageBox) + QDialog, QLineEdit, QFormLayout, QMessageBox, + QListWidget, QListWidgetItem) from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QColor, QBrush @@ -59,74 +60,114 @@ class AddDroneDialog(QDialog): } class DroneListView(QWidget): - # 定义信号 - drone_selected = pyqtSignal(str) # 无人机被选中时发出信号 + """无人机列表视图""" - def __init__(self, comm_manager=None): - super().__init__() - self.comm_manager = comm_manager - self.drone_colors = {} # 存储无人机颜色 - self.drone_groups = {} # 存储无人机分组 - self.init_ui() - self.setup_communication() - - def setup_communication(self): - """设置通信回调""" - if self.comm_manager: - self.comm_manager.register_callback("drone_added", self.on_drone_added) - self.comm_manager.register_callback("drone_removed", self.on_drone_removed) - self.comm_manager.register_callback("drone_updated", self.on_drone_updated) - - def on_drone_added(self, drone_id, data): - """处理无人机添加事件""" - self.add_drone_to_table(drone_id, self.comm_manager.get_drone(drone_id)) + drone_selected = pyqtSignal(str) # 无人机选择信号:drone_id + + def __init__(self, drone_manager, parent=None): + super().__init__(parent) + self.drone_manager = drone_manager + self.selected_drone_id = None - def on_drone_removed(self, drone_id, data): - """处理无人机移除事件""" - self.remove_drone_from_table(drone_id) + # 连接信号 + self.drone_manager.drone_added.connect(self._on_drone_added) + self.drone_manager.drone_removed.connect(self._on_drone_removed) - def on_drone_updated(self, drone_id, data): - """处理无人机更新事件""" - self.update_drone_in_table(drone_id, self.comm_manager.get_drone(drone_id)) + self._init_ui() - def add_drone_to_table(self, drone_id, info): - """添加无人机到表格""" - row = self.drone_table.rowCount() - self.drone_table.insertRow(row) + # 默认添加一架无人机 + self._add_default_drone() - # 设置各列数据 - self.drone_table.setItem(row, 0, QTableWidgetItem(info.get("id", ""))) - self.drone_table.setItem(row, 1, QTableWidgetItem(info.get("model", ""))) - self.drone_table.setItem(row, 2, QTableWidgetItem(info.get("status", ""))) - self.drone_table.setItem(row, 3, QTableWidgetItem(f"{info.get('latitude', 0):.6f}, {info.get('longitude', 0):.6f}")) - self.drone_table.setItem(row, 4, QTableWidgetItem(f"{info.get('battery', 0)}%")) - self.drone_table.setItem(row, 5, QTableWidgetItem(f"{info.get('payload_type', '')} ({info.get('payload_weight', 0)}kg)")) - self.drone_table.setItem(row, 6, QTableWidgetItem(info.get("group", ""))) - self.drone_table.setItem(row, 7, QTableWidgetItem(f"{info.get('signal_strength', 0)}%")) + def _init_ui(self): + """初始化UI""" + layout = QVBoxLayout(self) - # 应用颜色 - if drone_id in self.drone_colors: - self.update_drone_color(drone_id) + # 标题 + title_label = QLabel("无人机列表") + layout.addWidget(title_label) + + # 无人机列表 + self.drone_list = QListWidget() + self.drone_list.currentItemChanged.connect(self._on_drone_selection_changed) + layout.addWidget(self.drone_list) + + # 按钮区域 + btn_layout = QHBoxLayout() + + # 添加无人机按钮 + self.add_drone_btn = QPushButton("添加无人机") + self.add_drone_btn.clicked.connect(self._on_add_drone_clicked) + btn_layout.addWidget(self.add_drone_btn) + + # 删除无人机按钮 + self.remove_drone_btn = QPushButton("删除无人机") + self.remove_drone_btn.clicked.connect(self._on_remove_drone_clicked) + self.remove_drone_btn.setEnabled(False) # 初始禁用 + btn_layout.addWidget(self.remove_drone_btn) + + layout.addLayout(btn_layout) + + def _add_default_drone(self): + """添加默认无人机""" + self.drone_manager.add_drone(position=(50, 50), name="默认无人机") + + def _on_add_drone_clicked(self): + """添加无人机按钮点击事件""" + # 使用中心点位置添加新无人机 + self.drone_manager.add_drone(position=(100, 100)) + + def _on_remove_drone_clicked(self): + """删除无人机按钮点击事件""" + if self.selected_drone_id: + # 确认删除 + reply = QMessageBox.question( + self, + "确认删除", + f"确定要删除所选无人机吗?", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No + ) - def remove_drone_from_table(self, drone_id): - """从表格中移除无人机""" - for row in range(self.drone_table.rowCount()): - if self.drone_table.item(row, 0).text() == drone_id: - self.drone_table.removeRow(row) - break + if reply == QMessageBox.Yes: + self.drone_manager.remove_drone(self.selected_drone_id) + self.selected_drone_id = None + self.remove_drone_btn.setEnabled(False) + + def _on_drone_added(self, drone_id): + """无人机添加事件处理""" + drone_info = self.drone_manager.get_drone_info(drone_id) + if drone_info: + # 创建列表项 + item = QListWidgetItem(drone_info['name']) + item.setData(Qt.UserRole, drone_id) # 存储drone_id + self.drone_list.addItem(item) + + # 如果是第一个无人机,选中它 + if self.drone_list.count() == 1: + self.drone_list.setCurrentItem(item) - def update_drone_in_table(self, drone_id, info): - """更新表格中的无人机信息""" - for row in range(self.drone_table.rowCount()): - if self.drone_table.item(row, 0).text() == drone_id: - self.drone_table.setItem(row, 1, QTableWidgetItem(info.get("model", ""))) - self.drone_table.setItem(row, 2, QTableWidgetItem(info.get("status", ""))) - self.drone_table.setItem(row, 3, QTableWidgetItem(f"{info.get('latitude', 0):.6f}, {info.get('longitude', 0):.6f}")) - self.drone_table.setItem(row, 4, QTableWidgetItem(f"{info.get('battery', 0)}%")) - self.drone_table.setItem(row, 5, QTableWidgetItem(f"{info.get('payload_type', '')} ({info.get('payload_weight', 0)}kg)")) - self.drone_table.setItem(row, 6, QTableWidgetItem(info.get("group", ""))) - self.drone_table.setItem(row, 7, QTableWidgetItem(f"{info.get('signal_strength', 0)}%")) + def _on_drone_removed(self, drone_id): + """无人机移除事件处理""" + # 从列表中找到对应项并移除 + for i in range(self.drone_list.count()): + item = self.drone_list.item(i) + if item.data(Qt.UserRole) == drone_id: + self.drone_list.takeItem(i) break + + def _on_drone_selection_changed(self, current, previous): + """无人机选择变更事件处理""" + if current: + drone_id = current.data(Qt.UserRole) + self.selected_drone_id = drone_id + self.remove_drone_btn.setEnabled(True) + # 发出选择信号 + self.drone_selected.emit(drone_id) + # 显示调试消息 + print(f"已选择无人机: {drone_id}") + else: + self.selected_drone_id = None + self.remove_drone_btn.setEnabled(False) def init_ui(self): # 创建主布局 diff --git a/Src/command_center/ui/drone_management.py b/Src/command_center/ui/drone_management.py new file mode 100644 index 00000000..c16b7e9b --- /dev/null +++ b/Src/command_center/ui/drone_management.py @@ -0,0 +1,251 @@ +from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QTableWidget, QTableWidgetItem, QStatusBar, + QGroupBox, QSplitter, QFrame, QListWidget, QListWidgetItem, + QFormLayout, QDoubleSpinBox, QMessageBox, QSlider) +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtGui import QColor, QPixmap, QFont + +from .map_data_model import MapDataModel +from .drone_manager import DroneManager +from .drone_list_view import DroneListView +from .integrated_map_view import IntegratedMapView + +class DroneManagementWindow(QMainWindow): + """无人机管理主窗口""" + + # 定义信号 + switch_to_map_view = pyqtSignal() + + def __init__(self): + super().__init__() + + # 创建地图数据模型 + self.map_data_model = MapDataModel() + + # 创建无人机管理器 + self.drone_manager = DroneManager(self.map_data_model) + + # 初始化UI + self.init_ui() + + # 窗口设置 + self.setWindowTitle("无人机后勤输送系统 - 无人机管理") + self.setMinimumSize(1200, 800) + + # 添加默认无人机 + self.add_default_drones() + + def init_ui(self): + """初始化UI""" + # 创建中心部件 + central_widget = QWidget() + self.setCentralWidget(central_widget) + + # 创建主布局 + main_layout = QVBoxLayout(central_widget) + + # 创建标题 + title_label = QLabel("无人机管理中心") + title_label.setAlignment(Qt.AlignCenter) + title_font = QFont() + title_font.setPointSize(16) + title_font.setBold(True) + title_label.setFont(title_font) + main_layout.addWidget(title_label) + + # 创建分割器,左侧是无人机列表,右侧是无人机详情 + splitter = QSplitter(Qt.Horizontal) + + # 左侧无人机列表 + left_panel = QFrame() + left_layout = QVBoxLayout(left_panel) + + # 无人机列表视图 + self.drone_list_view = DroneListView(self.drone_manager) + left_layout.addWidget(self.drone_list_view) + + # 操作按钮区域 + button_group = QGroupBox("操作") + button_layout = QVBoxLayout() + + self.add_drone_btn = QPushButton("添加无人机") + self.add_drone_btn.clicked.connect(self.add_drone) + + self.switch_to_map_btn = QPushButton("打开地图视图") + self.switch_to_map_btn.clicked.connect(self.open_map_view) + + button_layout.addWidget(self.add_drone_btn) + button_layout.addWidget(self.switch_to_map_btn) + + button_group.setLayout(button_layout) + left_layout.addWidget(button_group) + + # 右侧无人机详情 + right_panel = QFrame() + right_layout = QVBoxLayout(right_panel) + + # 无人机详情区域 + details_group = QGroupBox("无人机详情") + details_layout = QFormLayout() + + # 无人机位置信息 + self.drone_id_label = QLabel("未选择") + self.drone_type_label = QLabel("未选择") + self.x_pos_spin = QDoubleSpinBox() + self.x_pos_spin.setRange(0, 10000) + self.x_pos_spin.setDecimals(2) + self.y_pos_spin = QDoubleSpinBox() + self.y_pos_spin.setRange(0, 10000) + self.y_pos_spin.setDecimals(2) + + self.update_position_btn = QPushButton("更新位置") + self.update_position_btn.clicked.connect(self.update_drone_position) + + details_layout.addRow("无人机ID:", self.drone_id_label) + details_layout.addRow("无人机类型:", self.drone_type_label) + details_layout.addRow("X坐标:", self.x_pos_spin) + details_layout.addRow("Y坐标:", self.y_pos_spin) + details_layout.addRow(self.update_position_btn) + + # 添加速度控制 + speed_layout = QVBoxLayout() + speed_layout.setContentsMargins(0, 10, 0, 0) + + speed_title = QLabel("移动速度调整") + speed_title.setStyleSheet("font-weight: bold") + speed_layout.addWidget(speed_title) + + speed_info_layout = QHBoxLayout() + self.speed_label = QLabel("50 ms/帧") + speed_info_layout.addWidget(QLabel("快")) + speed_info_layout.addStretch() + speed_info_layout.addWidget(self.speed_label) + speed_info_layout.addStretch() + speed_info_layout.addWidget(QLabel("慢")) + speed_layout.addLayout(speed_info_layout) + + self.speed_slider = QSlider(Qt.Horizontal) + self.speed_slider.setRange(10, 200) # 10ms到200ms + self.speed_slider.setValue(50) # 默认50ms + self.speed_slider.setTickPosition(QSlider.TicksBelow) + self.speed_slider.setTickInterval(20) + self.speed_slider.valueChanged.connect(self.on_speed_changed) + speed_layout.addWidget(self.speed_slider) + + details_layout.addRow(speed_layout) + + details_group.setLayout(details_layout) + right_layout.addWidget(details_group) + + # 无人机状态信息 + status_group = QGroupBox("状态信息") + status_layout = QVBoxLayout() + + self.status_table = QTableWidget() + self.status_table.setColumnCount(2) + self.status_table.setHorizontalHeaderLabels(["参数", "值"]) + self.status_table.setRowCount(4) + self.status_table.setItem(0, 0, QTableWidgetItem("当前状态")) + self.status_table.setItem(0, 1, QTableWidgetItem("待命")) + self.status_table.setItem(1, 0, QTableWidgetItem("电池电量")) + self.status_table.setItem(1, 1, QTableWidgetItem("100%")) + self.status_table.setItem(2, 0, QTableWidgetItem("负载情况")) + self.status_table.setItem(2, 1, QTableWidgetItem("无")) + self.status_table.setItem(3, 0, QTableWidgetItem("当前任务")) + self.status_table.setItem(3, 1, QTableWidgetItem("无")) + + status_layout.addWidget(self.status_table) + status_group.setLayout(status_layout) + right_layout.addWidget(status_group) + + # 添加到分割器 + splitter.addWidget(left_panel) + splitter.addWidget(right_panel) + + # 设置分割器的初始大小比例 + splitter.setSizes([400, 800]) + + main_layout.addWidget(splitter) + + # 状态栏 + self.status_bar = QStatusBar() + self.setStatusBar(self.status_bar) + self.status_bar.showMessage("就绪") + + # 连接无人机选择信号 + self.drone_list_view.drone_selected.connect(self.on_drone_selected) + + def add_default_drones(self): + """添加默认无人机""" + self.drone_manager.add_drone(position=(100, 100), name="运输无人机1", drone_type="运输型") + self.drone_manager.add_drone(position=(200, 200), name="侦察无人机1", drone_type="侦察型") + self.drone_manager.add_drone(position=(300, 300), name="战斗无人机1", drone_type="战斗型") + + def add_drone(self): + """添加新无人机""" + # 简单实现,后续可以添加对话框让用户输入无人机信息 + pos_x = 100 + (self.drone_manager.drone_count % 5) * 50 + pos_y = 100 + (self.drone_manager.drone_count % 5) * 50 + drone_id = self.drone_manager.add_drone(position=(pos_x, pos_y)) + self.status_bar.showMessage(f"已添加新无人机: {drone_id}") + + def on_drone_selected(self, drone_id): + """处理无人机选择事件""" + drone_info = self.drone_manager.get_drone_info(drone_id) + position = self.drone_manager.get_drone_position(drone_id) + + if drone_info and position: + self.drone_id_label.setText(drone_id) + self.drone_type_label.setText(drone_info.get('type', '未知')) + self.x_pos_spin.setValue(position[0]) + self.y_pos_spin.setValue(position[1]) + + # 更新速度滑块 + speed = self.drone_manager.get_drone_speed(drone_id) + self.speed_slider.setValue(speed) + self.speed_label.setText(f"{speed} ms/帧") + + # 更新状态表格 + self.status_table.setItem(0, 1, QTableWidgetItem("待命")) + self.status_table.setItem(1, 1, QTableWidgetItem("100%")) + + self.status_bar.showMessage(f"已选择无人机: {drone_id}") + + def on_speed_changed(self, value): + """处理速度滑块变更事件""" + drone_id = self.drone_id_label.text() + if drone_id != "未选择": + # 更新标签显示 + self.speed_label.setText(f"{value} ms/帧") + + # 更新无人机速度 + self.drone_manager.set_drone_speed(drone_id, value) + + self.status_bar.showMessage(f"无人机 {drone_id} 速度已调整为 {value} ms/帧") + + def update_drone_position(self): + """更新无人机位置""" + drone_id = self.drone_id_label.text() + if drone_id == "未选择": + QMessageBox.warning(self, "警告", "请先选择一架无人机") + return + + x = self.x_pos_spin.value() + y = self.y_pos_spin.value() + + # 获取当前位置中的航向角 + current_position = self.drone_manager.get_drone_position(drone_id) + heading = current_position[2] if current_position else 0 + + # 更新位置 + self.drone_manager.map_data_model.update_drone_position(drone_id, (x, y, heading)) + self.status_bar.showMessage(f"无人机 {drone_id} 位置已更新") + + def open_map_view(self): + """打开地图视图""" + # 获取当前无人机列表,用于调试 + drone_ids = self.drone_manager.get_all_drones() + print(f"打开地图视图,当前有 {len(drone_ids)} 架无人机: {drone_ids}") + + # 发送切换信号 + self.switch_to_map_view.emit() \ No newline at end of file diff --git a/Src/command_center/ui/drone_manager.py b/Src/command_center/ui/drone_manager.py new file mode 100644 index 00000000..8eae8509 --- /dev/null +++ b/Src/command_center/ui/drone_manager.py @@ -0,0 +1,278 @@ +from PyQt5.QtCore import QObject, QTimer, pyqtSignal +import math + +class DronePathAnimation(QObject): + """无人机路径动画控制器""" + + animation_step = pyqtSignal(str, tuple) # 每步动画发出信号:drone_id, position(x, y, heading) + animation_finished = pyqtSignal(str) # 动画结束信号:drone_id + + def __init__(self, map_data_model): + super().__init__() + self.map_data_model = map_data_model + self.drone_paths = {} # {drone_id: [(x1,y1), (x2,y2), ...]} + self.animation_timers = {} # {drone_id: QTimer} + self.current_path_index = {} # {drone_id: current_index} + self.animation_speed = 50 # ms,控制动画速度 + + def set_drone_path(self, drone_id, path): + """设置无人机路径 + + Args: + drone_id: 无人机ID + path: 路径点列表 [(x1,y1), (x2,y2), ...] + """ + if len(path) >= 2: # 至少需要起点和终点 + self.drone_paths[drone_id] = path + self.current_path_index[drone_id] = 0 + + def start_animation(self, drone_id): + """开始无人机沿路径移动的动画 + + Args: + drone_id: 无人机ID + + Returns: + 成功开始返回True,否则返回False + """ + if drone_id not in self.drone_paths or not self.drone_paths[drone_id]: + return False + + # 如果已有动画,先停止 + if drone_id in self.animation_timers and self.animation_timers[drone_id].isActive(): + self.animation_timers[drone_id].stop() + + # 设置无人机初始位置为路径起点 + start_point = self.drone_paths[drone_id][0] + self.map_data_model.add_drone(drone_id, (start_point[0], start_point[1], 0)) + + # 创建定时器 + timer = QTimer(self) + timer.timeout.connect(lambda: self._animation_step(drone_id)) + self.animation_timers[drone_id] = timer + + # 重置路径索引并开始 + self.current_path_index[drone_id] = 0 + timer.start(self.animation_speed) + return True + + def stop_animation(self, drone_id): + """停止无人机动画 + + Args: + drone_id: 无人机ID + """ + if drone_id in self.animation_timers and self.animation_timers[drone_id].isActive(): + self.animation_timers[drone_id].stop() + + def _animation_step(self, drone_id): + """单步更新无人机位置 + + Args: + drone_id: 无人机ID + """ + if drone_id not in self.drone_paths: + return + + path = self.drone_paths[drone_id] + current_index = self.current_path_index[drone_id] + + # 如果到达终点,停止动画 + if current_index >= len(path) - 1: + self.animation_timers[drone_id].stop() + self.animation_finished.emit(drone_id) + return + + # 计算当前位置和下一个位置 + current_point = path[current_index] + next_point = path[current_index + 1] + + # 计算航向角度(弧度) + dx = next_point[0] - current_point[0] + dy = next_point[1] - current_point[1] + heading = math.atan2(dy, dx) + + # 更新无人机位置 + self.map_data_model.update_drone_position(drone_id, (next_point[0], next_point[1], heading)) + + # 发出位置更新信号 + self.animation_step.emit(drone_id, (next_point[0], next_point[1], heading)) + + # 更新路径索引 + self.current_path_index[drone_id] = current_index + 1 + + def set_animation_speed(self, speed_ms): + """设置动画速度 + + Args: + speed_ms: 动画帧间隔,单位毫秒,值越大动画越慢 + """ + if speed_ms >= 10 and speed_ms <= 1000: # 限制合理范围 + self.animation_speed = speed_ms + + # 更新所有正在运行的动画定时器 + for drone_id, timer in self.animation_timers.items(): + if timer.isActive(): + timer.setInterval(speed_ms) + + def get_animation_speed(self): + """获取当前动画速度""" + return self.animation_speed + + +class DroneManager(QObject): + """无人机管理器""" + + drone_added = pyqtSignal(str) # 无人机添加信号:drone_id + drone_removed = pyqtSignal(str) # 无人机移除信号:drone_id + drone_updated = pyqtSignal(str, tuple) # 无人机更新信号:drone_id, position + + def __init__(self, map_data_model): + super().__init__() + self.map_data_model = map_data_model + self.drones = {} # {drone_id: {'name': 名称, 'type': 类型, 'speed': 移动速度}} + self.drone_count = 0 + self.path_animation = DronePathAnimation(map_data_model) + + def add_drone(self, position=(0, 0), name=None, drone_type="标准无人机", speed=50): + """添加新无人机 + + Args: + position: 位置元组 (x, y) + name: 无人机名称,如不提供则自动生成 + drone_type: 无人机类型 + speed: 无人机移动速度,单位毫秒/帧 + + Returns: + drone_id: 新添加的无人机ID + """ + self.drone_count += 1 + drone_id = f"drone_{self.drone_count}" + + if name is None: + name = f"无人机-{self.drone_count}" + + self.drones[drone_id] = { + 'name': name, + 'type': drone_type, + 'speed': speed + } + + # 添加到地图数据模型 + self.map_data_model.add_drone(drone_id, position) + + # 发出无人机添加信号 + self.drone_added.emit(drone_id) + return drone_id + + def remove_drone(self, drone_id): + """移除无人机 + + Args: + drone_id: 无人机ID + """ + if drone_id in self.drones: + # 停止可能的动画 + self.path_animation.stop_animation(drone_id) + + # 从数据结构中移除 + del self.drones[drone_id] + self.map_data_model.remove_drone(drone_id) + + # 发出无人机移除信号 + self.drone_removed.emit(drone_id) + + def set_drone_path(self, drone_id, path): + """设置无人机路径 + + Args: + drone_id: 无人机ID + path: 路径点列表 [(x1,y1), (x2,y2), ...] + """ + if drone_id in self.drones: + self.path_animation.set_drone_path(drone_id, path) + + def start_drone_movement(self, drone_id): + """开始无人机移动 + + Args: + drone_id: 无人机ID + + Returns: + 成功开始返回True,否则返回False + """ + if drone_id not in self.drones: + return False + + # 获取无人机速度设置 + speed = self.get_drone_speed(drone_id) + + # 设置动画控制器速度 + self.path_animation.set_animation_speed(speed) + + # 开始动画 + return self.path_animation.start_animation(drone_id) + + def stop_drone_movement(self, drone_id): + """停止无人机移动 + + Args: + drone_id: 无人机ID + """ + self.path_animation.stop_animation(drone_id) + + def get_drone_info(self, drone_id): + """获取无人机信息 + + Args: + drone_id: 无人机ID + + Returns: + 无人机信息字典或None(如果无人机不存在) + """ + return self.drones.get(drone_id) + + def get_drone_position(self, drone_id): + """获取无人机位置 + + Args: + drone_id: 无人机ID + + Returns: + 位置元组 (x, y, heading) 或 None(如果无人机不存在) + """ + return self.map_data_model.get_drone_position(drone_id) + + def get_all_drones(self): + """获取所有无人机ID + + Returns: + 无人机ID列表 + """ + return list(self.drones.keys()) + + def set_drone_speed(self, drone_id, speed): + """设置无人机移动速度 + + Args: + drone_id: 无人机ID + speed: 速度值,单位毫秒/帧 + """ + if drone_id in self.drones: + self.drones[drone_id]['speed'] = speed + + # 更新动画控制器的速度 + self.path_animation.set_animation_speed(speed) + + def get_drone_speed(self, drone_id): + """获取无人机速度 + + Args: + drone_id: 无人机ID + + Returns: + 速度值或默认值50 + """ + if drone_id in self.drones and 'speed' in self.drones[drone_id]: + return self.drones[drone_id]['speed'] + return 50 \ No newline at end of file diff --git a/Src/command_center/ui/integrated_map_view.py b/Src/command_center/ui/integrated_map_view.py new file mode 100644 index 00000000..166270ee --- /dev/null +++ b/Src/command_center/ui/integrated_map_view.py @@ -0,0 +1,1299 @@ +# -*- coding: utf-8 -*- +# File: integrated_map_view.py +# Purpose: 集成地图视图,将地图显示、危险区域管理和路径规划整合到一个界面中 + +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QComboBox, QSpinBox, QDoubleSpinBox, + QGroupBox, QFormLayout, QMessageBox, QTabWidget, + QToolBar, QAction, QRadioButton, QButtonGroup, + QSplitter, QFrame, QSlider) +from PyQt5.QtCore import Qt, pyqtSignal, QPointF, QTimer, QRectF +from PyQt5.QtGui import QColor, QPen, QPixmap, QImage, QPainter, QBrush, QPainterPath +import math + +from .base_map_view import BaseMapView +from .drone_manager import DroneManager +from .drone_list_view import DroneListView +from path_planning.path_planner import PathPlanner +from database.database import clear_danger_zones, save_danger_zone, save_path, clear_paths + +class OperationMode: + """操作模式枚举""" + NONE = 0 + ADD_THREAT_CIRCLE = 1 + ADD_THREAT_RECTANGLE = 2 + ADD_THREAT_POLYGON = 3 + SELECT_START = 4 + SELECT_GOAL = 5 + PAN_MAP = 6 + +class IntegratedMapView(QWidget): + """集成地图视图,整合地图显示、危险区域管理和路径规划""" + + def __init__(self, map_data_model, drone_manager=None): + super().__init__() + self.map_data_model = map_data_model + self.path_planner = PathPlanner() + + # 使用传入的无人机管理器或创建新的 + if drone_manager: + self.drone_manager = drone_manager + else: + # 仅在未提供时创建新实例(为兼容性保留) + self.drone_manager = DroneManager(map_data_model) + print("警告:未提供无人机管理器,创建了新实例。可能无法共享无人机数据。") + + # 状态变量 + self.operation_mode = OperationMode.NONE + self.current_shape_points = [] + self.threat_radius = 50 + self.grid_resolution = 5.0 + self.selected_drone_id = None + + # 创建状态栏 + self.status_label = QLabel() + + self.init_ui() + self.connect_signals() + + # 初始加载无人机列表 + self.refresh_drone_list() + + def init_ui(self): + # 创建主布局 + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + + # 创建工具栏 + toolbar = QToolBar("操作工具栏") + toolbar.setMovable(False) + toolbar.setMinimumHeight(40) # 增加工具栏高度 + toolbar.setStyleSheet("QToolBar { border: 1px solid #ccc; }") + + # 地图操作工具 + self.load_map_action = QAction("加载地图", self) + self.zoom_in_action = QAction("放大", self) + self.zoom_out_action = QAction("缩小", self) + self.pan_action = QAction("平移", self) + self.pan_action.setCheckable(True) + + toolbar.addAction(self.load_map_action) + toolbar.addAction(self.zoom_in_action) + toolbar.addAction(self.zoom_out_action) + toolbar.addAction(self.pan_action) + toolbar.addSeparator() + + # 添加模式选择下拉框 + self.mode_label = QLabel("操作模式:") + toolbar.addWidget(self.mode_label) + + self.mode_combo = QComboBox() + self.mode_combo.addItems(["浏览", "添加圆形危险区", "添加矩形危险区", "添加多边形危险区", "选择起点", "选择终点"]) + self.mode_combo.setMinimumWidth(150) # 增加宽度 + toolbar.addWidget(self.mode_combo) + + # 半径/参数设置 + toolbar.addSeparator() + self.radius_label = QLabel("危险区域半径:") + toolbar.addWidget(self.radius_label) + + self.radius_spin = QSpinBox() + self.radius_spin.setRange(10, 200) + self.radius_spin.setValue(self.threat_radius) + toolbar.addWidget(self.radius_spin) + + # 更新地图按钮 + toolbar.addSeparator() + self.refresh_map_btn = QPushButton("刷新地图") + self.refresh_map_btn.setMinimumHeight(30) + self.refresh_map_btn.setStyleSheet("font-weight: bold;") + toolbar.addWidget(self.refresh_map_btn) + + # 添加到主布局 + main_layout.addWidget(toolbar) + + # 创建分割器,左侧是地图,右侧是控制面板 + splitter = QSplitter(Qt.Horizontal) + + # 地图视图区域 + self.map_container = QFrame() + map_layout = QVBoxLayout(self.map_container) + map_layout.setContentsMargins(0, 0, 0, 0) + + self.map_view = IntegratedMapDisplay(self.map_data_model) + self.map_view.setMinimumSize(800, 600) # 增加地图最小尺寸 + map_layout.addWidget(self.map_view) + + # 控制面板区域 + control_panel = QFrame() + control_panel.setMaximumWidth(350) # 增加控制面板宽度 + control_panel.setMinimumWidth(350) + control_layout = QVBoxLayout(control_panel) + control_layout.setSpacing(10) # 增加面板中各元素的间距 + + # 无人机操作区域(替换原来的无人机管理) + drone_control_group = QGroupBox("无人机操作") + drone_control_layout = QVBoxLayout() + drone_control_layout.setContentsMargins(5, 10, 5, 10) + drone_control_layout.setSpacing(10) + + # 无人机选择 + drone_select_layout = QHBoxLayout() + drone_select_layout.addWidget(QLabel("选择无人机:")) + self.drone_select_combo = QComboBox() + self.drone_select_combo.setMinimumWidth(180) + drone_select_layout.addWidget(self.drone_select_combo) + drone_control_layout.addLayout(drone_select_layout) + + # 速度控制 + speed_layout = QVBoxLayout() + speed_title_layout = QHBoxLayout() + speed_title_layout.addWidget(QLabel("移动速度:")) + self.speed_value_label = QLabel("50 ms/帧") + self.speed_value_label.setAlignment(Qt.AlignRight) + speed_title_layout.addWidget(self.speed_value_label) + speed_layout.addLayout(speed_title_layout) + + speed_slider_layout = QHBoxLayout() + speed_slider_layout.addWidget(QLabel("快")) + self.speed_slider = QSlider(Qt.Horizontal) + self.speed_slider.setRange(10, 200) # 10ms到200ms + self.speed_slider.setValue(50) # 默认50ms + self.speed_slider.setTickPosition(QSlider.TicksBelow) + self.speed_slider.setTickInterval(30) + self.speed_slider.valueChanged.connect(self.on_speed_changed) + speed_slider_layout.addWidget(self.speed_slider) + speed_slider_layout.addWidget(QLabel("慢")) + speed_layout.addLayout(speed_slider_layout) + + drone_control_layout.addLayout(speed_layout) + + # 操作按钮 + drone_action_layout = QHBoxLayout() + self.drone_move_btn = QPushButton("行进") + self.drone_move_btn.setMinimumHeight(35) + self.drone_move_btn.setEnabled(False) + self.drone_move_btn.clicked.connect(self.move_drone) + + self.drone_stop_btn = QPushButton("停止") + self.drone_stop_btn.setMinimumHeight(35) + self.drone_stop_btn.setEnabled(False) + self.drone_stop_btn.clicked.connect(self.stop_drone) + + drone_action_layout.addWidget(self.drone_move_btn) + drone_action_layout.addWidget(self.drone_stop_btn) + drone_control_layout.addLayout(drone_action_layout) + + drone_control_group.setLayout(drone_control_layout) + + # 起点设置区域 + start_group = QGroupBox("起点设置") + start_layout = QFormLayout() + start_layout.setContentsMargins(5, 10, 5, 10) + start_layout.setSpacing(10) + + self.start_x_spin = QDoubleSpinBox() + self.start_x_spin.setRange(0, 10000) + self.start_x_spin.setValue(100) + + self.start_y_spin = QDoubleSpinBox() + self.start_y_spin.setRange(0, 10000) + self.start_y_spin.setValue(100) + + self.start_heading_spin = QDoubleSpinBox() + self.start_heading_spin.setRange(0, 360) + self.start_heading_spin.setValue(0) + + self.set_start_btn = QPushButton("在地图上选择起点") + self.set_start_btn.setCheckable(True) + self.set_start_btn.setMinimumHeight(30) # 增加按钮高度 + + start_layout.addRow("X坐标:", self.start_x_spin) + start_layout.addRow("Y坐标:", self.start_y_spin) + start_layout.addRow("航向角:", self.start_heading_spin) + start_layout.addRow(self.set_start_btn) + + start_group.setLayout(start_layout) + + # 终点设置区域 + goal_group = QGroupBox("终点设置") + goal_layout = QFormLayout() + goal_layout.setContentsMargins(5, 10, 5, 10) + goal_layout.setSpacing(10) + + self.goal_x_spin = QDoubleSpinBox() + self.goal_x_spin.setRange(0, 10000) + self.goal_x_spin.setValue(500) + + self.goal_y_spin = QDoubleSpinBox() + self.goal_y_spin.setRange(0, 10000) + self.goal_y_spin.setValue(500) + + self.goal_heading_spin = QDoubleSpinBox() + self.goal_heading_spin.setRange(0, 360) + self.goal_heading_spin.setValue(0) + + self.set_goal_btn = QPushButton("在地图上选择终点") + self.set_goal_btn.setCheckable(True) + self.set_goal_btn.setMinimumHeight(30) # 增加按钮高度 + + goal_layout.addRow("X坐标:", self.goal_x_spin) + goal_layout.addRow("Y坐标:", self.goal_y_spin) + goal_layout.addRow("航向角:", self.goal_heading_spin) + goal_layout.addRow(self.set_goal_btn) + + goal_group.setLayout(goal_layout) + + # 算法选择区域 - 重新设计以优化布局 + algorithm_group = QGroupBox("算法设置") + algorithm_layout = QVBoxLayout() # 使用垂直布局代替表单布局 + algorithm_layout.setContentsMargins(5, 10, 5, 10) + algorithm_layout.setSpacing(10) + + # 算法选择和网格分辨率 + algo_basic_form = QFormLayout() + + self.algorithm_combo = QComboBox() + self.algorithm_combo.addItems(["A*算法", "遗传算法", "RRT算法"]) + + self.grid_resolution_spin = QDoubleSpinBox() + self.grid_resolution_spin.setRange(1, 50) + self.grid_resolution_spin.setValue(self.grid_resolution) + self.grid_resolution_spin.setSingleStep(1) + + algo_basic_form.addRow("规划算法:", self.algorithm_combo) + algo_basic_form.addRow("网格分辨率:", self.grid_resolution_spin) + algorithm_layout.addLayout(algo_basic_form) + + # 算法特定参数分组 + # 遗传算法参数 + ga_params_group = QGroupBox("遗传算法参数") + ga_params_layout = QFormLayout() + + self.ga_pop_size_spin = QSpinBox() + self.ga_pop_size_spin.setRange(10, 1000) + self.ga_pop_size_spin.setValue(100) + + self.ga_generations_spin = QSpinBox() + self.ga_generations_spin.setRange(10, 1000) + self.ga_generations_spin.setValue(100) + + ga_params_layout.addRow("种群大小:", self.ga_pop_size_spin) + ga_params_layout.addRow("迭代代数:", self.ga_generations_spin) + + ga_params_group.setLayout(ga_params_layout) + algorithm_layout.addWidget(ga_params_group) + + # RRT算法参数 + rrt_params_group = QGroupBox("RRT算法参数") + rrt_params_layout = QFormLayout() + + self.rrt_step_size_spin = QDoubleSpinBox() + self.rrt_step_size_spin.setRange(1, 100) + self.rrt_step_size_spin.setValue(20) + + self.rrt_iterations_spin = QSpinBox() + self.rrt_iterations_spin.setRange(100, 10000) + self.rrt_iterations_spin.setValue(1000) + + rrt_params_layout.addRow("步长:", self.rrt_step_size_spin) + rrt_params_layout.addRow("迭代次数:", self.rrt_iterations_spin) + + rrt_params_group.setLayout(rrt_params_layout) + algorithm_layout.addWidget(rrt_params_group) + + algorithm_group.setLayout(algorithm_layout) + + # 按钮区域 + button_group = QGroupBox("操作") + button_layout = QVBoxLayout() + button_layout.setContentsMargins(5, 10, 5, 10) + button_layout.setSpacing(10) + + self.plan_path_btn = QPushButton("规划路径") + self.plan_path_btn.setMinimumHeight(40) # 增加按钮高度 + self.plan_path_btn.setStyleSheet("font-weight: bold;") + + self.clear_path_btn = QPushButton("清除路径") + self.clear_path_btn.setMinimumHeight(30) + + self.clear_threats_btn = QPushButton("清除危险区域") + self.clear_threats_btn.setMinimumHeight(30) + + button_layout.addWidget(self.plan_path_btn) + button_layout.addWidget(self.clear_path_btn) + button_layout.addWidget(self.clear_threats_btn) + + # 添加完成多边形按钮 + self.complete_polygon_btn = QPushButton("完成多边形") + self.complete_polygon_btn.setEnabled(False) + self.complete_polygon_btn.setMinimumHeight(30) + button_layout.addWidget(self.complete_polygon_btn) + + button_group.setLayout(button_layout) + + # 添加所有组到控制面板 + control_layout.addWidget(drone_control_group) + control_layout.addWidget(start_group) + control_layout.addWidget(goal_group) + control_layout.addWidget(algorithm_group) + control_layout.addWidget(button_group) + control_layout.addStretch() + + # 添加到分割器 + splitter.addWidget(self.map_container) + splitter.addWidget(control_panel) + + # 设置分割器的初始大小 + splitter.setSizes([800, 350]) + + main_layout.addWidget(splitter) + + # 添加状态栏在底部 + status_layout = QHBoxLayout() + self.status_label.setMinimumHeight(25) + self.status_label.setStyleSheet("padding: 5px; background-color: #f0f0f0; border-top: 1px solid #ccc;") + status_layout.addWidget(self.status_label) + main_layout.addLayout(status_layout) + + def connect_signals(self): + """连接信号和槽""" + # 工具栏操作 + self.load_map_action.triggered.connect(self.map_view.load_map_image) + self.zoom_in_action.triggered.connect(self.map_view.zoom_in) + self.zoom_out_action.triggered.connect(self.map_view.zoom_out) + self.pan_action.toggled.connect(self.toggle_pan_mode) + + # 模式选择 + self.mode_combo.currentIndexChanged.connect(self.change_operation_mode) + + # 参数设置 + self.radius_spin.valueChanged.connect(self.set_threat_radius) + + # 地图点击处理 + self.map_view.point_clicked.connect(self.handle_map_click) + + # 按钮操作 + self.set_start_btn.toggled.connect(self.toggle_start_selection) + self.set_goal_btn.toggled.connect(self.toggle_goal_selection) + + # 添加路径规划相关按钮连接 + self.plan_path_btn.clicked.connect(self.plan_path) + self.clear_path_btn.clicked.connect(self.clear_path) + self.clear_threats_btn.clicked.connect(self.clear_threats) + self.complete_polygon_btn.clicked.connect(self.complete_polygon) + + # 刷新地图 + self.refresh_map_btn.clicked.connect(self.refresh_map) + + # 无人机下拉框选择 + self.drone_select_combo.currentIndexChanged.connect(self.on_drone_combo_selected) + + # 无人机动画相关信号 + self.drone_manager.path_animation.animation_finished.connect(self.on_drone_animation_finished) + + # 添加动画步骤信号连接(如果需要实时更新) + # 注意:我们在移动时再连接步骤信号,避免在不移动时也频繁更新 + + # 速度滑块 + self.speed_slider.valueChanged.connect(self.on_speed_changed) + + # 算法选择信号连接 + if hasattr(self, 'algorithm_combo'): + self.algorithm_combo.currentTextChanged.connect(self.on_algorithm_changed) + + # 数据模型变化 + if hasattr(self.map_data_model, 'data_changed'): + self.map_data_model.data_changed.connect(self.update_planner_data) + + def on_drone_animation_finished(self, drone_id): + """处理无人机动画完成事件""" + if drone_id == self.selected_drone_id: + self.showMessage(f"无人机 {drone_id} 已到达目的地") + + # 更新按钮状态 + self.update_drone_buttons_state() + + # 提示用户 + QMessageBox.information(self, "任务完成", f"无人机 {drone_id} 已到达目的地!") + + def on_speed_changed(self, value): + """速度滑块值变更处理""" + self.speed_value_label.setText(f"{value} ms/帧") + + if self.selected_drone_id: + # 更新无人机速度 + self.drone_manager.set_drone_speed(self.selected_drone_id, value) + + def on_drone_combo_selected(self, index): + """处理无人机下拉框选择变化 + + Args: + index: 选择的项目索引 + """ + if index < 0 or self.drone_select_combo.count() == 0: + self.selected_drone_id = None + self.update_drone_buttons_state() + return + + # 获取选中的无人机ID + self.selected_drone_id = self.drone_select_combo.itemData(index) + + # 如果选择了有效的无人机 + if self.selected_drone_id: + # 获取无人机信息 + drone_info = self.drone_manager.get_drone_info(self.selected_drone_id) + + # 获取无人机当前位置 + position = self.drone_manager.get_drone_position(self.selected_drone_id) + + if position: + x, y, heading = position + + # 更新起点坐标为无人机当前位置 + if hasattr(self, 'start_x_spin'): + self.start_x_spin.setValue(x) + if hasattr(self, 'start_y_spin'): + self.start_y_spin.setValue(y) + if hasattr(self, 'start_heading_spin'): + # 转换弧度为角度 + self.start_heading_spin.setValue(math.degrees(heading) % 360) + + # 更新状态信息 + name = drone_info.get('name', self.selected_drone_id) if drone_info else self.selected_drone_id + self.showMessage(f"已选择无人机: {name},当前位置: ({int(x)}, {int(y)})") + + # 如果地图上有这个无人机,将视图中心设置到无人机位置 + self.map_view.centerOn(x, y) + + # 高亮显示选中的无人机 + self.map_data_model.set_selected_drone(self.selected_drone_id) + self.map_view.update() + else: + self.showMessage(f"已选择无人机: {self.selected_drone_id},但无法获取位置信息") + + # 更新按钮状态 + self.update_drone_buttons_state() + + def update_drone_buttons_state(self): + """更新无人机操作按钮状态""" + has_drone = self.selected_drone_id is not None + + # 默认状态 + self.drone_move_btn.setEnabled(has_drone and len(self.map_data_model.paths) > 0) + self.drone_stop_btn.setEnabled(has_drone) + + def stop_drone(self): + """停止无人机移动""" + if self.selected_drone_id: + # 停止无人机动画 + self.drone_manager.stop_drone_movement(self.selected_drone_id) + + # 获取无人机当前位置 + position = self.drone_manager.get_drone_position(self.selected_drone_id) + if position: + x, y, heading = position + + # 更新起点坐标为无人机当前位置 + if hasattr(self, 'start_x_spin'): + self.start_x_spin.setValue(x) + if hasattr(self, 'start_y_spin'): + self.start_y_spin.setValue(y) + if hasattr(self, 'start_heading_spin'): + # 转换弧度为角度 + self.start_heading_spin.setValue(math.degrees(heading) % 360) + + # 更新状态信息 + self.showMessage(f"已停止无人机 {self.selected_drone_id},当前位置({int(x)}, {int(y)})已设为新起点") + + # 通知用户 + QMessageBox.information(self, "无人机已停止", + f"无人机 {self.selected_drone_id} 已停止,当前位置已设为新起点。\n\n" + f"您可以修改终点或危险区域,然后重新规划路径让无人机继续行进。") + else: + self.showMessage(f"已停止无人机 {self.selected_drone_id} 的移动") + + # 刷新地图以更新无人机位置 + self.map_view.update() + + # 更新按钮状态 + self.drone_move_btn.setEnabled(True) + self.drone_stop_btn.setEnabled(False) + + def move_drone(self): + """开始无人机沿规划路径移动""" + if not self.selected_drone_id: + QMessageBox.warning(self, "无人机操作", "请先选择一个无人机") + return + + # 检查是否已经规划了路径 + if not self.map_data_model.paths or len(self.map_data_model.paths) == 0: + # 如果没有路径,检查是否设置了终点 + if hasattr(self, 'goal_x_spin') and hasattr(self, 'goal_y_spin'): + goal_x = self.goal_x_spin.value() + goal_y = self.goal_y_spin.value() + + # 获取当前无人机位置作为起点 + start_pos = self.drone_manager.get_drone_position(self.selected_drone_id) + if start_pos: + # 自动进行路径规划 + self.showMessage("无现有路径,正在自动规划...") + self.start_x_spin.setValue(start_pos[0]) + self.start_y_spin.setValue(start_pos[1]) + self.plan_path() + else: + QMessageBox.warning(self, "无人机操作", "无法获取无人机当前位置") + return + else: + QMessageBox.warning(self, "无人机操作", "请先规划路径") + return + + # 设置速度 + speed = self.speed_slider.value() + self.drone_manager.set_drone_speed(self.selected_drone_id, speed) + + # 开始移动 + success = self.drone_manager.start_drone_movement(self.selected_drone_id) + if success: + self.showMessage(f"无人机 {self.selected_drone_id} 开始移动,速度: {speed} ms/帧") + + # 更新按钮状态 + self.drone_move_btn.setEnabled(False) + self.drone_stop_btn.setEnabled(True) + + # 连接动画完成信号 + self.drone_manager.path_animation.animation_finished.connect(self.on_drone_animation_finished) + self.drone_manager.path_animation.animation_step.connect(self.on_drone_animation_step) + else: + QMessageBox.warning(self, "无人机操作", "无法开始无人机移动,请检查是否已规划路径") + + def on_drone_animation_step(self, drone_id, position): + """无人机动画每一步更新回调""" + # 更新地图显示 + self.map_view.update() + + # 显示当前位置信息 + x, y, heading = position + self.showMessage(f"无人机 {drone_id} 位置: ({int(x)}, {int(y)})") + + # 可在此处添加更复杂的状态显示逻辑 + + def refresh_drone_list(self): + """刷新无人机列表""" + # 保存当前选中的无人机ID + current_drone_id = self.selected_drone_id + + # 清空下拉框 + self.drone_select_combo.clear() + + # 确保无人机管理器实例可用 + if not self.drone_manager: + self.showMessage("无法刷新无人机列表:无人机管理器不可用") + return + + # 获取所有无人机 + all_drones = self.drone_manager.get_all_drones() + + if not all_drones: + self.drone_select_combo.addItem("无可用无人机", None) + self.drone_select_combo.setEnabled(False) + self.drone_move_btn.setEnabled(False) + self.drone_stop_btn.setEnabled(False) + self.selected_drone_id = None + return + + self.drone_select_combo.setEnabled(True) + + # 填充下拉框 + for drone_id in all_drones: + # 获取无人机信息 + drone_info = self.drone_manager.get_drone_info(drone_id) + position = self.drone_manager.get_drone_position(drone_id) + + if drone_info and 'name' in drone_info: + # 显示名称和ID,方便用户识别 + display_name = f"{drone_info['name']} (ID: {drone_id})" + + # 如果有位置信息,在显示名称中添加位置信息 + if position: + x, y = int(position[0]), int(position[1]) + display_name = f"{display_name} - 位置({x},{y})" + else: + # 仅使用ID + display_name = f"无人机 {drone_id}" + + # 添加到下拉框,使用drone_id作为用户数据 + self.drone_select_combo.addItem(display_name, drone_id) + + # 尝试恢复之前选中的无人机 + if current_drone_id: + # 查找匹配的索引 + for i in range(self.drone_select_combo.count()): + if self.drone_select_combo.itemData(i) == current_drone_id: + self.drone_select_combo.setCurrentIndex(i) + break + else: + # 默认选中第一个无人机 + self.drone_select_combo.setCurrentIndex(0) + self.on_drone_combo_selected(0) + + # 更新按钮状态 + self.update_drone_buttons_state() + + def change_operation_mode(self, index): + """切换操作模式""" + # 重置其他按钮状态 + self.set_start_btn.setChecked(False) + self.set_goal_btn.setChecked(False) + self.pan_action.setChecked(False) + self.map_view.setCursor(Qt.ArrowCursor) + + # 设置新的操作模式 + mode_map = { + 0: OperationMode.NONE, + 1: OperationMode.ADD_THREAT_CIRCLE, + 2: OperationMode.ADD_THREAT_RECTANGLE, + 3: OperationMode.ADD_THREAT_POLYGON, + 4: OperationMode.SELECT_START, + 5: OperationMode.SELECT_GOAL + } + + self.operation_mode = mode_map.get(index, OperationMode.NONE) + + # 特殊处理 + if self.operation_mode == OperationMode.ADD_THREAT_POLYGON: + self.complete_polygon_btn.setEnabled(True) + else: + self.complete_polygon_btn.setEnabled(False) + self.current_shape_points = [] + + # 根据模式更新提示 + mode_tips = { + OperationMode.NONE: "浏览模式", + OperationMode.ADD_THREAT_CIRCLE: "点击地图添加圆形危险区域", + OperationMode.ADD_THREAT_RECTANGLE: "点击地图添加矩形危险区域的两个对角点", + OperationMode.ADD_THREAT_POLYGON: "点击地图添加多边形顶点,完成后点击'完成多边形'按钮", + OperationMode.SELECT_START: "点击地图选择起点", + OperationMode.SELECT_GOAL: "点击地图选择终点", + OperationMode.PAN_MAP: "拖动地图以平移视图" + } + + self.statusBar().showMessage(mode_tips.get(self.operation_mode, "")) + + def statusBar(self): + """返回状态标签,用于显示状态信息""" + return self + + def showMessage(self, message): + """显示状态信息""" + if hasattr(self, 'status_label'): + self.status_label.setText(message) + + def clearMessage(self): + """清除状态信息""" + if hasattr(self, 'status_label'): + self.status_label.clear() + + def toggle_pan_mode(self, checked): + """切换地图平移模式""" + if checked: + self.operation_mode = OperationMode.PAN_MAP + self.map_view.setCursor(Qt.OpenHandCursor) + self.mode_combo.setCurrentIndex(0) # 重置模式下拉框 + else: + self.operation_mode = OperationMode.NONE + self.map_view.setCursor(Qt.ArrowCursor) + + def set_threat_radius(self, value): + """设置危险区域半径""" + self.threat_radius = value + + def set_grid_resolution(self, value): + """设置网格分辨率""" + self.grid_resolution = value + self.path_planner.grid_resolution = value + + def toggle_start_selection(self, checked): + """切换到起点选择模式""" + if checked: + self.operation_mode = OperationMode.SELECT_START + self.mode_combo.setCurrentIndex(0) # 重置模式下拉框 + self.set_goal_btn.setChecked(False) + self.statusBar().showMessage("点击地图选择起点") + else: + self.operation_mode = OperationMode.NONE + self.statusBar().clearMessage() + + def toggle_goal_selection(self, checked): + """切换到终点选择模式""" + if checked: + self.operation_mode = OperationMode.SELECT_GOAL + self.mode_combo.setCurrentIndex(0) # 重置模式下拉框 + self.set_start_btn.setChecked(False) + self.statusBar().showMessage("点击地图选择终点") + else: + self.operation_mode = OperationMode.NONE + self.statusBar().clearMessage() + + def on_algorithm_changed(self, algorithm_name): + """算法选择变更""" + algorithm_map = { + "A*算法": "astar", + "遗传算法": "genetic", + "RRT算法": "rrt" + } + + selected_algorithm = algorithm_map.get(algorithm_name, "astar") + self.path_planner.set_algorithm(selected_algorithm) + + # 更新参数可见性 + is_ga = selected_algorithm == "genetic" + is_rrt = selected_algorithm == "rrt" + + # 使用QGroupBox来控制参数组的可见性 + for group in self.findChildren(QGroupBox): + if group.title() == "遗传算法参数": + group.setVisible(is_ga) + elif group.title() == "RRT算法参数": + group.setVisible(is_rrt) + + # 更新算法特定参数 + if is_ga: + self.update_ga_parameters() + elif is_rrt: + self.update_rrt_parameters() + + def update_ga_parameters(self): + """更新遗传算法参数""" + if hasattr(self.path_planner, 'ga_population_size'): + self.path_planner.ga_population_size = self.ga_pop_size_spin.value() + + if hasattr(self.path_planner, 'ga_generations'): + self.path_planner.ga_generations = self.ga_generations_spin.value() + + def update_rrt_parameters(self): + """更新RRT算法参数""" + if hasattr(self.path_planner, 'rrt_step_size'): + self.path_planner.rrt_step_size = self.rrt_step_size_spin.value() + + if hasattr(self.path_planner, 'rrt_max_iterations'): + self.path_planner.rrt_max_iterations = self.rrt_iterations_spin.value() + + def update_planner_data(self): + """将地图数据模型中的数据更新到路径规划器""" + # 更新危险区域 + self.path_planner.clear_obstacles() + + # 添加危险点 + if hasattr(self.map_data_model, 'threat_points'): + for point in self.map_data_model.threat_points: + self.path_planner.add_obstacle_point(point[0], point[1], 30) # 使用默认半径30 + + # 添加危险区域 + if hasattr(self.map_data_model, 'threat_areas'): + for area in self.map_data_model.threat_areas: + if area['type'] == 'circle': + center, radius = area['center'], area['radius'] + self.path_planner.add_obstacle_circle(center[0], center[1], radius) + elif area['type'] == 'rectangle': + rect = area['rect'] # (x, y, width, height) + self.path_planner.add_obstacle_rectangle(rect[0], rect[1], rect[0] + rect[2], rect[1] + rect[3]) + elif area['type'] == 'polygon': + points = area['points'] + self.path_planner.add_obstacle_polygon(points) + + def add_threat_area(self, area): + """添加危险区域""" + if hasattr(self.map_data_model, 'add_threat_area'): + self.map_data_model.add_threat_area(area) + + def complete_polygon(self): + """完成多边形添加""" + if len(self.current_shape_points) >= 3: + area = { + 'type': 'polygon', + 'points': self.current_shape_points, + 'color': QColor(255, 0, 0, 128) # 半透明红色 + } + self.add_threat_area(area) + self.current_shape_points = [] + self.complete_polygon_btn.setEnabled(False) + else: + QMessageBox.warning(self, "多边形不完整", "多边形至少需要3个点") + + def handle_map_click(self, point): + """处理地图点击事件""" + try: + x, y = point + + if self.operation_mode == OperationMode.ADD_THREAT_CIRCLE: + # 添加圆形危险区域 + area = { + 'type': 'circle', + 'center': (x, y), + 'radius': self.threat_radius, + 'color': QColor(255, 0, 0, 128) # 半透明红色 + } + self.add_threat_area(area) + save_danger_zone(area) + self.showMessage(f"已添加圆形危险区域 - 中心:({x:.1f}, {y:.1f}), 半径:{self.threat_radius}") + + elif self.operation_mode == OperationMode.ADD_THREAT_RECTANGLE: + # 如果是矩形的第一个点 + if not self.current_shape_points: + self.current_shape_points.append((x, y)) + self.showMessage(f"已设置矩形第一个点 ({x:.1f}, {y:.1f}),请点击设置第二个点") + else: + # 第二个点,完成矩形 + x1, y1 = self.current_shape_points[0] + x2, y2 = x, y + + # 创建矩形,确保左上角和右下角的正确顺序 + top_left_x = min(x1, x2) + top_left_y = min(y1, y2) + width = abs(x2 - x1) + height = abs(y2 - y1) + + area = { + 'type': 'rectangle', + 'rect': (top_left_x, top_left_y, width, height), + 'color': QColor(255, 0, 0, 128) # 半透明红色 + } + self.add_threat_area(area) + save_danger_zone(area) + self.current_shape_points = [] + self.showMessage(f"已添加矩形危险区域 - 左上角:({top_left_x:.1f}, {top_left_y:.1f}), 宽:{width:.1f}, 高:{height:.1f}") + + elif self.operation_mode == OperationMode.ADD_THREAT_POLYGON: + # 添加点到当前多边形 + self.current_shape_points.append((x, y)) + # 更新显示 + if hasattr(self.map_data_model, 'data_changed'): + self.map_data_model.data_changed.emit() + save_danger_zone(area) + self.showMessage(f"已添加多边形顶点 ({x:.1f}, {y:.1f}),当前共 {len(self.current_shape_points)} 个顶点") + + elif self.operation_mode == OperationMode.SELECT_START: + # 设置起点 + self.start_x_spin.setValue(x) + self.start_y_spin.setValue(y) + + # 更新地图数据模型中的起点 + if hasattr(self.map_data_model, 'set_start_point'): + self.map_data_model.set_start_point((x, y)) + + # 更新选定无人机的位置 + if self.selected_drone_id and self.drone_manager: + try: + # 先停止正在进行的动画 + self.drone_manager.path_animation.stop_animation(self.selected_drone_id) + # 使用正确的方法更新无人机位置 + heading = math.radians(self.start_heading_spin.value()) + self.map_data_model.update_drone_position(self.selected_drone_id, (x, y, heading)) + self.showMessage(f"已设置起点 ({x:.1f}, {y:.1f}) 并将无人机移动到该位置") + except Exception as e: + print(f"更新无人机位置错误: {str(e)}") + self.showMessage(f"已设置起点 ({x:.1f}, {y:.1f}),但无法更新无人机位置") + else: + self.showMessage(f"已设置起点 ({x:.1f}, {y:.1f})") + + elif self.operation_mode == OperationMode.SELECT_GOAL: + # 设置终点 + self.goal_x_spin.setValue(x) + self.goal_y_spin.setValue(y) + + # 更新地图数据模型中的终点 + if hasattr(self.map_data_model, 'set_goal_point'): + self.map_data_model.set_goal_point((x, y)) + + self.showMessage(f"已设置终点 ({x:.1f}, {y:.1f})") + except Exception as e: + import traceback + traceback.print_exc() + QMessageBox.critical(self, "错误", f"处理地图点击时发生错误: {str(e)}") + self.showMessage("操作失败,请查看错误信息") + + def plan_path(self): + """执行路径规划""" + # 检查是否选择了无人机 + if not self.selected_drone_id: + QMessageBox.warning(self, "提示", "请先选择一个无人机") + return + + # 获取起点和终点 + start_x = self.start_x_spin.value() + start_y = self.start_y_spin.value() + start_heading = math.radians(self.start_heading_spin.value()) + + goal_x = self.goal_x_spin.value() + goal_y = self.goal_y_spin.value() + goal_heading = math.radians(self.goal_heading_spin.value()) + + # 设置网格分辨率 + grid_resolution = self.grid_resolution_spin.value() + self.path_planner.grid_resolution = grid_resolution + + # 获取地图尺寸 + map_width = 1000 + map_height = 1000 + if self.map_data_model and self.map_data_model.map_pixmap: + map_width = self.map_data_model.map_pixmap.width() + map_height = self.map_data_model.map_pixmap.height() + + # 更新地图数据模型中的起点和终点 + if self.map_data_model: + self.map_data_model.set_start_point((start_x, start_y)) + self.map_data_model.set_goal_point((goal_x, goal_y)) + + # 如果有选定的无人机,更新其位置为起点 + if self.selected_drone_id: + # 停止之前的任何动画 + self.drone_manager.path_animation.stop_animation(self.selected_drone_id) + # 更新无人机位置到起点 + self.map_data_model.update_drone_position(self.selected_drone_id, (start_x, start_y, start_heading)) + + # 更新路径规划器的障碍物和危险区域数据 + self.update_planner_data() + + # 根据算法设置不同参数 + if self.path_planner.algorithm == "genetic": + # 更新遗传算法参数 + self.update_ga_parameters() + elif self.path_planner.algorithm == "rrt": + # 更新RRT算法参数 + self.update_rrt_parameters() + + # 执行路径规划 + start = (start_x, start_y) + goal = (goal_x, goal_y) + + # 显示规划中提示 + self.statusBar().showMessage("正在规划路径...") + + try: + # 使用drone_id参数,如果有选定的无人机,使用其ID + path = self.path_planner.plan_path( + drone_id=self.selected_drone_id, + start=start, + goal=goal, + map_width=map_width, + map_height=map_height + ) + + # 清除状态栏提示 + self.statusBar().clearMessage() + + if path and len(path) >= 2: + # 更新地图中的路径显示 + if self.map_data_model: + self.map_data_model.paths = [path] + + # 如果有选定的无人机,为其设置路径 + self.drone_manager.set_drone_path(self.selected_drone_id, path) + self.update_drone_buttons_state() + save_path(self.selected_drone_id, path) + QMessageBox.information(self, "路径规划", "路径规划成功!") + else: + QMessageBox.warning(self, "路径规划", "无法找到有效路径,请尝试调整参数或避开障碍物") + except Exception as e: + import traceback + traceback.print_exc() + QMessageBox.critical(self, "路径规划错误", f"路径规划失败: {str(e)}") + self.statusBar().clearMessage() + + def clear_path(self): + """清除规划的路径""" + if self.map_data_model: + self.map_data_model.paths = [] + + self.path_planner.clear_path("planning_drone") + + # 如果有选定的无人机,停止其动画 + if self.selected_drone_id: + self.drone_manager.path_animation.stop_animation(self.selected_drone_id) + clear_paths(self.selected_drone_id) + self.update_drone_buttons_state() + + def clear_threats(self): + """清除所有危险区域""" + if hasattr(self.map_data_model, 'threat_areas'): + self.map_data_model.threat_areas = [] + if hasattr(self.map_data_model, 'threat_points'): + self.map_data_model.threat_points = [] + self.current_shape_points = [] + self.map_data_model.data_changed.emit() + + # 同步删除数据库中的危险区域 + clear_danger_zones() + + def refresh_map(self): + """刷新地图显示,更新所有地图元素""" + try: + # 添加状态提示 + self.showMessage("正在刷新地图...") + + # 保存当前地图视图状态 + current_scale = self.map_view.scale_factor + current_offset = self.map_view.map_offset + + # 保存当前选中的无人机ID + current_drone_id = self.selected_drone_id + + # 暂时将选中的无人机ID设为None,避免在刷新列表时居中显示 + self.selected_drone_id = None + + # 确保无人机管理器数据同步到地图数据模型 + if self.drone_manager and self.map_data_model: + # 获取所有无人机位置信息并更新 + for drone_id in self.drone_manager.get_all_drones(): + position = self.drone_manager.get_drone_position(drone_id) + if position: + # 确保无人机位置正确显示在地图上 + self.map_data_model.update_drone_position(drone_id, position) + + # 刷新无人机下拉列表 (注意这里会调用on_drone_combo_selected) + self.refresh_drone_list() + + # 恢复选中的无人机ID + self.selected_drone_id = current_drone_id + + # 更新地图数据模型中的数据 + self.map_data_model.data_changed.emit() + + # 刷新无人机图标 + if hasattr(self.map_view, 'load_drone_images'): + self.map_view.load_drone_images() + + # 恢复地图视图状态 + self.map_view.scale_factor = current_scale + self.map_view.map_offset = current_offset + + # 更新地图视图 + self.map_view.update() + + # 提示刷新完成 + self.showMessage("地图已刷新,显示所有无人机位置") + + # 在状态显示3秒后清除 + QTimer.singleShot(3000, self.clearMessage) + except Exception as e: + self.showMessage(f"地图刷新失败: {str(e)}") + print(f"地图刷新错误: {str(e)}") + +class IntegratedMapDisplay(BaseMapView): + """集成地图显示区域,继承自BaseMapView""" + + # 定义信号 + point_clicked = pyqtSignal(tuple) + + def __init__(self, map_data_model): + super().__init__(map_data_model) + self.drone_images = {} # 存储无人机图像 {drone_id: QPixmap} + self.load_drone_images() + + def centerOn(self, x, y): + """将视图中心设置到指定坐标 + + Args: + x: 地图上的X坐标 + y: 地图上的Y坐标 + """ + if self.map_data_model.map_pixmap and not self.map_data_model.map_pixmap.isNull(): + # 计算地图中心点 + label_size = self.map_label.size() + + # 计算新的偏移量,使指定点居中 + new_offset_x = label_size.width() / 2 - x * self.scale_factor + new_offset_y = label_size.height() / 2 - y * self.scale_factor + + # 设置新的偏移量 + self.map_offset = QPointF(new_offset_x, new_offset_y) + + # 更新地图显示 + self.update_map() + + def load_drone_images(self): + """加载无人机图像""" + # 创建红色无人机图标作为默认图标 + self.default_drone_pixmap = self.create_drone_icon(QColor(255, 0, 0)) # 红色 + + # 创建不同颜色的无人机图标用于区分 + colors = [ + QColor(255, 0, 0), # 红色(主要颜色) + QColor(0, 0, 255), # 蓝色 + QColor(0, 180, 0), # 绿色 + QColor(255, 165, 0), # 橙色 + QColor(128, 0, 128) # 紫色 + ] + + # 为每种颜色创建图标 + self.drone_icons = {} + for i, color in enumerate(colors): + icon = self.create_drone_icon(color) + self.drone_icons[f"type_{i}"] = icon + + def create_drone_icon(self, color): + """创建无人机图标 + + Args: + color: 图标颜色 + + Returns: + QPixmap: 创建的图标 + """ + size = 50 # 增大图标尺寸以提高可见性 + drone_image = QImage(size, size, QImage.Format_ARGB32) + drone_image.fill(Qt.transparent) # 透明背景 + + # 绘制无人机图标 + painter = QPainter(drone_image) + painter.setRenderHint(QPainter.Antialiasing) + + # 绘制更尖锐的三角形无人机形状(指向上方) + path = QPainterPath() + path.moveTo(size/2, 3) # 顶部中心(机头) + path.lineTo(size-3, size-5) # 右下角 + path.lineTo(3, size-5) # 左下角 + path.closeSubpath() + + # 使用亮红色作为默认颜色,提高可见性 + if color == QColor(255, 0, 0): # 如果是红色,使用更鲜艳的红色 + color = QColor(255, 30, 30) + + # 填充主体颜色,使用纯色 + painter.setBrush(QBrush(color)) + painter.setPen(QPen(Qt.black, 2.5)) # 黑色边框更粗更明显 + painter.drawPath(path) + + # 添加高光效果使图标更立体 + highlight_path = QPainterPath() + highlight_path.moveTo(size/2, 3) + highlight_path.lineTo(size/2 + 5, size/4) + highlight_path.lineTo(size/2 - 5, size/4) + highlight_path.closeSubpath() + + painter.setBrush(QBrush(QColor(255, 255, 255, 70))) # 半透明白色高光 + painter.setPen(Qt.NoPen) + painter.drawPath(highlight_path) + + painter.end() + + # 转换为QPixmap并返回 + return QPixmap.fromImage(drone_image) + + def handle_map_click(self, map_point): + """处理地图点击,发出点击信号""" + self.point_clicked.emit((map_point.x(), map_point.y())) + + def drawForeground(self, painter, rect): + """绘制前景层,包括无人机""" + super().drawForeground(painter, rect) + + # 将变换应用到绘图 + original_transform = painter.transform() + + # 获取选中的无人机ID + selected_drone_id = None + if hasattr(self.map_data_model, 'selected_drone'): + selected_drone_id = self.map_data_model.selected_drone + + # 绘制所有无人机 + if hasattr(self.map_data_model, 'drones') and self.map_data_model.drones: + for drone_id, position in self.map_data_model.drones.items(): + x, y, heading = position + + # 保存原始状态 + painter.save() + + # 设置绘图位置和旋转 + painter.translate(x, y) + painter.rotate(math.degrees(heading)) + + # 默认使用红色图标提高可见性 + icon = self.default_drone_pixmap + + # 图标中心点调整 + icon_width = icon.width() + icon_height = icon.height() + + # 绘制无人机图标(中心点对齐) + painter.drawPixmap(-icon_width//2, -icon_height//2, icon) + + # 恢复状态用于绘制文本 + painter.restore() + + # 提取无人机ID和名称 + drone_info = "" + try: + # 获取无人机名称(如果可用) + from ui.drone_manager import DroneManager + if isinstance(self.map_data_model.parent(), DroneManager): + drone_manager = self.map_data_model.parent() + drone_info = drone_manager.drones.get(drone_id, {}).get('name', drone_id) + else: + drone_info = drone_id + except: + drone_info = drone_id + + # 设置文本颜色和字体 + painter.save() + text_font = painter.font() + text_font.setBold(True) + text_font.setPointSize(10) # 增大字体尺寸 + painter.setFont(text_font) + + # 为文本添加高对比度背景以提高可见性 + text_rect = painter.fontMetrics().boundingRect(drone_info) + text_x = x - text_rect.width() // 2 + text_y = y + 30 # 增加与无人机图标的距离 + + # 绘制高对比度背景 + bg_rect = QRectF(text_x - 4, text_y - text_rect.height(), + text_rect.width() + 8, text_rect.height() + 4) + + # 使用红色背景搭配白色文字,提高可见性 + painter.fillRect(bg_rect, QColor(200, 10, 10, 220)) + + # 添加边框 + painter.setPen(QPen(Qt.black, 1.0)) + painter.drawRect(bg_rect) + + # 绘制白色文字 + painter.setPen(Qt.white) + painter.drawText(QPointF(text_x, text_y), drone_info) + + # 如果是选中的无人机,添加突出显示效果 + if drone_id == selected_drone_id: + # 绘制选中指示器 - 明显的圆形高亮 + painter.setPen(QPen(QColor(255, 255, 0), 2.5)) # 黄色粗线 + painter.drawEllipse(QPointF(x, y), 30, 30) + + # 添加第二个圆形作为强调 + painter.setPen(QPen(QColor(255, 165, 0), 1.5, Qt.DashLine)) # 橙色虚线 + painter.drawEllipse(QPointF(x, y), 35, 35) + + # 显示"已选择"标记 + select_font = painter.font() + select_font.setBold(True) + select_font.setPointSize(8) + painter.setFont(select_font) + + select_text = "已选择" + select_rect = painter.fontMetrics().boundingRect(select_text) + select_x = x - select_rect.width() // 2 + select_y = y - 40 # 在无人机上方显示 + + # 绘制背景 + select_bg_rect = QRectF(select_x - 3, select_y - select_rect.height(), + select_rect.width() + 6, select_rect.height() + 3) + painter.fillRect(select_bg_rect, QColor(255, 215, 0, 210)) # 金色背景 + + # 绘制文字 + painter.setPen(Qt.black) + painter.drawText(QPointF(select_x, select_y), select_text) + else: + # 非选中无人机只添加一个简单的雷达效果 + painter.setPen(QPen(QColor(255, 100, 100, 120), 1.5, Qt.DashLine)) + painter.drawEllipse(QPointF(x, y), 25, 25) + + painter.restore() \ No newline at end of file diff --git a/Src/command_center/ui/login_view.py b/Src/command_center/ui/login_view.py index c88e2429..7442efad 100644 --- a/Src/command_center/ui/login_view.py +++ b/Src/command_center/ui/login_view.py @@ -6,7 +6,7 @@ from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QMessageBox) from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QFont - +from database.database import register_user, query_user class LoginView(QWidget): # 定义登录成功信号 login_successful = pyqtSignal() @@ -67,13 +67,33 @@ class LoginView(QWidget): if not username or not password: QMessageBox.warning(self, "错误", "请输入用户名和密码") return - - # 添加默认测试账号 + # 使用数据库查询验证用户 + if query_user(username, password): + self.login_successful.emit() + else: + QMessageBox.warning(self, "错误", "用户名或密码错误") + """# 添加默认测试账号 if username == "admin" and password == "admin123": self.login_successful.emit() else: - QMessageBox.warning(self, "错误", "用户名或密码错误") + QMessageBox.warning(self, "错误", "用户名或密码错误")""" def handle_register(self): - # TODO: 实现注册功能 - QMessageBox.information(self, "提示", "注册功能待实现") \ No newline at end of file + """# TODO: 实现注册功能 + QMessageBox.information(self, "提示", "注册功能待实现") """ + username = self.username_input.text() + password = self.password_input.text() + if not username or not password: + QMessageBox.warning(self, "错误", "请输入用户名和密码") + return + # 检查用户是否已存在 + if query_user(username, password): + QMessageBox.warning(self, "错误", "用户已存在") + return + + # 注册新用户 + if register_user(username, password): + QMessageBox.information(self, "提示", "注册成功") + else: + QMessageBox.warning(self, "错误", "注册失败,请重试") + \ No newline at end of file diff --git a/Src/command_center/ui/map_data_model.py b/Src/command_center/ui/map_data_model.py index 8a5b009a..f9463371 100644 --- a/Src/command_center/ui/map_data_model.py +++ b/Src/command_center/ui/map_data_model.py @@ -1,15 +1,18 @@ from PyQt5.QtCore import QObject, pyqtSignal - +from database.database import save_danger_zone class MapDataModel(QObject): data_changed = pyqtSignal() def __init__(self): super().__init__() self.map_pixmap = None - self.threat_points = [] + self.threat_points = [] # 保留向后兼容 + self.threat_areas = [] # 新增:危险区域列表 self.start_point = None self.goal_point = None self.paths = [] + self.drones = {} # 存储无人机位置信息 {drone_id: (x, y, heading)} + self.selected_drone = None # 当前选中的无人机ID def set_map(self, pixmap): self.map_pixmap = pixmap @@ -18,6 +21,17 @@ class MapDataModel(QObject): def add_threat_point(self, point): self.threat_points.append(point) self.data_changed.emit() + + def add_threat_area(self, area): + """添加危险区域 + area: 字典,包含类型(type)和相应的参数 + {'type': 'circle', 'center': (x, y), 'radius': r, 'color': color} + {'type': 'rectangle', 'rect': (x, y, width, height), 'color': color} + {'type': 'polygon', 'points': [(x1,y1), (x2,y2), ...], 'color': color} + """ + self.threat_areas.append(area) + + self.data_changed.emit() def set_start_point(self, point): self.start_point = point @@ -29,6 +43,7 @@ class MapDataModel(QObject): def clear_all(self): self.threat_points.clear() + self.threat_areas.clear() self.start_point = None self.goal_point = None self.paths = [] @@ -36,4 +51,68 @@ class MapDataModel(QObject): def set_paths(self, paths): self.paths = paths - self.data_changed.emit() \ No newline at end of file + self.data_changed.emit() + + def add_drone(self, drone_id, position): + """添加或更新无人机位置 + + Args: + drone_id: 无人机ID + position: 位置元组 (x, y, heading),heading可选 + """ + if len(position) == 2: + # 如果只提供了x,y坐标,添加默认航向角0 + self.drones[drone_id] = (position[0], position[1], 0) + else: + self.drones[drone_id] = position + self.data_changed.emit() + + def update_drone_position(self, drone_id, position): + """更新无人机位置 + + Args: + drone_id: 无人机ID + position: 新位置元组 (x, y, heading) + """ + if drone_id in self.drones: + self.drones[drone_id] = position + self.data_changed.emit() + + def get_drone_position(self, drone_id): + """获取无人机位置 + + Args: + drone_id: 无人机ID + + Returns: + 位置元组 (x, y, heading) 或 None(如果无人机不存在) + """ + return self.drones.get(drone_id) + + def remove_drone(self, drone_id): + """移除无人机 + + Args: + drone_id: 无人机ID + """ + if drone_id in self.drones: + del self.drones[drone_id] + self.data_changed.emit() + + def set_selected_drone(self, drone_id): + """设置当前选中的无人机 + + Args: + drone_id: 无人机ID,或None表示取消选择 + """ + if drone_id is None or drone_id in self.drones: + self.selected_drone = drone_id + self.data_changed.emit() + + def get_selected_drone(self): + """获取当前选中的无人机ID + + Returns: + 当前选中的无人机ID或None + """ + return self.selected_drone \ No newline at end of file