parent
b32501dad6
commit
368b9eca39
@ -0,0 +1,3 @@
|
||||
{
|
||||
"cmake.ignoreCMakeListsMissing": true
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,271 @@
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
import json
|
||||
import os
|
||||
from PyQt5.QtGui import QColor
|
||||
|
||||
def load_db_config():
|
||||
# 获取当前文件的目录
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# 构建配置文件的完整路径
|
||||
config_path = os.path.join(current_dir, 'db_config.json')
|
||||
with open(config_path, 'r') as config_file:
|
||||
config = json.load(config_file)
|
||||
return config
|
||||
|
||||
def create_connection():
|
||||
"""进行数据库连接"""
|
||||
connection = None
|
||||
try:
|
||||
config = load_db_config()
|
||||
connection = mysql.connector.connect(
|
||||
host=config['host'], # 数据库主机地址
|
||||
user=config['user'], # 数据库用户名
|
||||
password=config['password'], # 数据库密码
|
||||
database=config['database'] # 数据库名称
|
||||
)
|
||||
if connection.is_connected():
|
||||
print("Connected to MySQL database")
|
||||
except Error as e:
|
||||
print(f"Error: '{e}' occurred")
|
||||
return connection
|
||||
|
||||
def close_connection(connection):
|
||||
"""关闭连接"""
|
||||
if connection.is_connected():
|
||||
connection.close()
|
||||
print("MySQL connection is closed")
|
||||
|
||||
def query_user(username, password):
|
||||
"""查询用户信息"""
|
||||
connection = create_connection()
|
||||
user_exists = False
|
||||
try:
|
||||
if connection.is_connected():
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
query = "SELECT * FROM users WHERE username = %s AND password = %s"
|
||||
cursor.execute(query, (username, password))
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
user_exists = True
|
||||
cursor.close()
|
||||
except Error as e:
|
||||
print(f"Error: '{e}' occurred")
|
||||
finally:
|
||||
if connection.is_connected():
|
||||
close_connection(connection)
|
||||
return user_exists
|
||||
|
||||
def register_user(username, password):
|
||||
"""注册新用户"""
|
||||
connection = create_connection()
|
||||
success = False
|
||||
try:
|
||||
if connection.is_connected():
|
||||
cursor = connection.cursor()
|
||||
query = "INSERT INTO users (username, password) VALUES (%s, %s)"
|
||||
cursor.execute(query, (username, password))
|
||||
connection.commit()
|
||||
success = True
|
||||
cursor.close()
|
||||
except Error as e:
|
||||
print(f"Error: '{e}' occurred")
|
||||
finally:
|
||||
if connection.is_connected():
|
||||
close_connection(connection)
|
||||
return success
|
||||
|
||||
|
||||
def save_danger_zone(area):
|
||||
"""保存危险区域到数据库,避免重复"""
|
||||
if danger_zone_exists(area):
|
||||
print("危险区域已存在,跳过存储。")
|
||||
return False
|
||||
connection = create_connection()
|
||||
success = False
|
||||
try:
|
||||
if connection.is_connected():
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# 标准化坐标
|
||||
if area['type'] == 'circle':
|
||||
coordinates = json.dumps({
|
||||
'center': [round(c, 6) for c in area.get('center', (0, 0))],
|
||||
'radius': round(area.get('radius', 50), 2)
|
||||
})
|
||||
elif area['type'] == 'rectangle':
|
||||
coordinates = json.dumps({
|
||||
'rect': [round(r, 6) for r in area.get('rect', (0, 0, 100, 100))]
|
||||
})
|
||||
elif area['type'] == 'polygon':
|
||||
coordinates = json.dumps({
|
||||
'points': [[round(p[0], 6), round(p[1], 6)] for p in area.get('points', [])]
|
||||
})
|
||||
else:
|
||||
return False
|
||||
|
||||
# 检查是否已存在
|
||||
check_query = """
|
||||
SELECT id FROM danger_zones WHERE type = %s AND coordinates = %s
|
||||
"""
|
||||
cursor.execute(check_query, (area['type'], coordinates))
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
print("Danger zone already exists in the database.")
|
||||
return False
|
||||
|
||||
# 插入新记录
|
||||
insert_query = """
|
||||
INSERT INTO danger_zones (type, coordinates, color)
|
||||
VALUES (%s, %s, %s)
|
||||
"""
|
||||
color = area['color'].name() # 假设QColor对象
|
||||
cursor.execute(insert_query, (area['type'], coordinates, color))
|
||||
connection.commit()
|
||||
success = True
|
||||
cursor.close()
|
||||
except Error as e:
|
||||
print(f"Error: '{e}' occurred")
|
||||
finally:
|
||||
if connection.is_connected():
|
||||
close_connection(connection)
|
||||
return success
|
||||
|
||||
def load_danger_zones():
|
||||
"""从数据库加载危险区域信息"""
|
||||
connection = create_connection()
|
||||
danger_zones = []
|
||||
try:
|
||||
if connection.is_connected():
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
query = "SELECT type, coordinates, color FROM danger_zones"
|
||||
cursor.execute(query)
|
||||
results = cursor.fetchall()
|
||||
for row in results:
|
||||
coordinates = json.loads(row['coordinates'])
|
||||
if row['type'] == 'circle':
|
||||
zone = {
|
||||
'type': row['type'],
|
||||
'center': coordinates['center'],
|
||||
'radius': coordinates['radius'],
|
||||
'color': QColor(row['color'])
|
||||
}
|
||||
elif row['type'] == 'rectangle':
|
||||
zone = {
|
||||
'type': row['type'],
|
||||
'rect': coordinates['rect'],
|
||||
'color': QColor(row['color'])
|
||||
}
|
||||
elif row['type'] == 'polygon':
|
||||
zone = {
|
||||
'type': row['type'],
|
||||
'points': coordinates['points'],
|
||||
'color': QColor(row['color'])
|
||||
}
|
||||
danger_zones.append(zone)
|
||||
cursor.close()
|
||||
except Error as e:
|
||||
print(f"Error: '{e}' occurred")
|
||||
finally:
|
||||
if connection.is_connected():
|
||||
close_connection(connection)
|
||||
return danger_zones
|
||||
|
||||
def clear_danger_zones():
|
||||
"""删除数据库中的所有危险区域数据"""
|
||||
connection = create_connection()
|
||||
print("clear_danger_zones called")
|
||||
try:
|
||||
if connection.is_connected():
|
||||
cursor = connection.cursor()
|
||||
query = "DELETE FROM danger_zones"
|
||||
cursor.execute(query)
|
||||
connection.commit()
|
||||
cursor.close()
|
||||
except Error as e:
|
||||
print(f"Error: '{e}' occurred")
|
||||
finally:
|
||||
if connection.is_connected():
|
||||
close_connection(connection)
|
||||
|
||||
def save_path(drone_id, path):
|
||||
"""保存路径信息到数据库"""
|
||||
connection = create_connection()
|
||||
success = False
|
||||
print("save_path called")
|
||||
try:
|
||||
if connection.is_connected():
|
||||
cursor = connection.cursor()
|
||||
query = """
|
||||
INSERT INTO paths (drone_id, path)
|
||||
VALUES (%s, %s)
|
||||
"""
|
||||
path_str = json.dumps(path) # 将路径转换为JSON字符串
|
||||
cursor.execute(query, (drone_id, path_str))
|
||||
connection.commit()
|
||||
success = True
|
||||
cursor.close()
|
||||
except Error as e:
|
||||
print(f"Error: '{e}' occurred")
|
||||
finally:
|
||||
if connection.is_connected():
|
||||
close_connection(connection)
|
||||
return success
|
||||
|
||||
def clear_paths(drone_id):
|
||||
"""删除数据库中与特定无人机ID相关的路径数据"""
|
||||
connection = create_connection()
|
||||
try:
|
||||
if connection.is_connected():
|
||||
cursor = connection.cursor()
|
||||
query = "DELETE FROM paths WHERE drone_id = %s"
|
||||
cursor.execute(query, (drone_id,))
|
||||
connection.commit()
|
||||
cursor.close()
|
||||
except Error as e:
|
||||
print(f"Error: '{e}' occurred")
|
||||
finally:
|
||||
if connection.is_connected():
|
||||
close_connection(connection)
|
||||
|
||||
def danger_zone_exists(area):
|
||||
"""检查数据库中是否存在相同的危险区域"""
|
||||
connection = create_connection()
|
||||
exists = False
|
||||
try:
|
||||
if connection.is_connected():
|
||||
cursor = connection.cursor(dictionary=True)
|
||||
|
||||
# 标准化坐标
|
||||
if area['type'] == 'circle':
|
||||
coordinates = json.dumps({
|
||||
'center': [round(c, 6) for c in area.get('center', (0, 0))],
|
||||
'radius': round(area.get('radius', 50), 2)
|
||||
})
|
||||
elif area['type'] == 'rectangle':
|
||||
coordinates = json.dumps({
|
||||
'rect': [round(r, 6) for r in area.get('rect', (0, 0, 100, 100))]
|
||||
})
|
||||
elif area['type'] == 'polygon':
|
||||
coordinates = json.dumps({
|
||||
'points': [[round(p[0], 6), round(p[1], 6)] for p in area.get('points', [])]
|
||||
})
|
||||
else:
|
||||
return False
|
||||
|
||||
# 检查是否已存在
|
||||
check_query = """
|
||||
SELECT id FROM danger_zones WHERE type = %s AND coordinates = %s
|
||||
"""
|
||||
cursor.execute(check_query, (area['type'], coordinates))
|
||||
result = cursor.fetchone()
|
||||
print(result)
|
||||
exists = result is not None
|
||||
print(exists)
|
||||
cursor.close()
|
||||
except Error as e:
|
||||
print(f"Error: '{e}' occurred")
|
||||
finally:
|
||||
if connection.is_connected():
|
||||
close_connection(connection)
|
||||
return exists
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"host": "localhost",
|
||||
"user": "root",
|
||||
"password": "227622",
|
||||
"database": "unv"
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(255) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS danger_zones (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
coordinates JSON NOT NULL,
|
||||
color VARCHAR(20) NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS paths (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
drone_id VARCHAR(50) NOT NULL,
|
||||
path JSON NOT NULL
|
||||
);
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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()
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue