@ -1,32 +0,0 @@
|
||||
# 应用环境
|
||||
ENVIRONMENT=development # development, testing, production
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
|
||||
# Web服务器配置
|
||||
WEB_HOST=0.0.0.0
|
||||
WEB_PORT=8080
|
||||
|
||||
# 通信服务器配置
|
||||
COMM_HOST=0.0.0.0
|
||||
COMM_PORT=5000
|
||||
|
||||
# 无人机连接配置
|
||||
DRONE_CONNECTION_STRING=udpin:localhost:14550
|
||||
|
||||
# 基地位置
|
||||
BASE_LATITUDE=39.9
|
||||
BASE_LONGITUDE=116.3
|
||||
|
||||
# 飞行参数
|
||||
DEFAULT_ALTITUDE=10.0
|
||||
DEFAULT_AIRSPEED=3.0
|
||||
|
||||
# 安全设置
|
||||
ENABLE_GEO_FENCE=True
|
||||
MAX_DISTANCE_FROM_BASE=5000.0
|
||||
MIN_BATTERY_LEVEL=30.0
|
||||
|
||||
# 其他配置
|
||||
ENABLE_SIMULATION=True
|
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 200 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 148 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 66 KiB |
@ -1,3 +0,0 @@
|
||||
"""
|
||||
Config package initialization
|
||||
"""
|
@ -1,110 +0,0 @@
|
||||
import os
|
||||
import logging
|
||||
|
||||
# 基础配置
|
||||
class Config:
|
||||
"""基础配置类"""
|
||||
|
||||
# 应用配置
|
||||
APP_NAME = "智能战场医疗后送系统"
|
||||
VERSION = "1.0.0"
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL = "INFO"
|
||||
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
|
||||
# Web服务器配置
|
||||
WEB_HOST = "0.0.0.0"
|
||||
WEB_PORT = 8080
|
||||
|
||||
# 通信服务器配置
|
||||
COMM_HOST = "0.0.0.0"
|
||||
COMM_PORT = 5000
|
||||
|
||||
# 无人机连接配置
|
||||
DRONE_CONNECTION_STRING = "udpin:localhost:14550"
|
||||
|
||||
# 基地位置(纬度、经度)
|
||||
BASE_LATITUDE = 39.9
|
||||
BASE_LONGITUDE = 116.3
|
||||
|
||||
# 飞行参数
|
||||
DEFAULT_ALTITUDE = 10.0 # 默认飞行高度(米)
|
||||
DEFAULT_AIRSPEED = 3.0 # 默认空速(米/秒)
|
||||
|
||||
# 安全设置
|
||||
ENABLE_GEO_FENCE = True
|
||||
MAX_DISTANCE_FROM_BASE = 5000.0 # 最大飞行距离(米)
|
||||
MIN_BATTERY_LEVEL = 30.0 # 最低电量百分比
|
||||
|
||||
# 伤员检测配置
|
||||
CASUALTY_DETECTOR_MODEL_PATH = "models/yolov5s.pt"
|
||||
DETECTION_THRESHOLD = 0.5 # 检测阈值
|
||||
|
||||
# 其他配置
|
||||
ENABLE_SIMULATION = True
|
||||
|
||||
@staticmethod
|
||||
def get_log_level():
|
||||
"""获取日志级别"""
|
||||
level_map = {
|
||||
"DEBUG": logging.DEBUG,
|
||||
"INFO": logging.INFO,
|
||||
"WARNING": logging.WARNING,
|
||||
"ERROR": logging.ERROR,
|
||||
"CRITICAL": logging.CRITICAL
|
||||
}
|
||||
return level_map.get(Config.LOG_LEVEL, logging.INFO)
|
||||
|
||||
@staticmethod
|
||||
def setup_logging():
|
||||
"""设置日志"""
|
||||
logging.basicConfig(
|
||||
level=Config.get_log_level(),
|
||||
format=Config.LOG_FORMAT
|
||||
)
|
||||
|
||||
# 降低第三方库的日志级别
|
||||
logging.getLogger("werkzeug").setLevel(logging.WARNING)
|
||||
logging.getLogger("socketio").setLevel(logging.WARNING)
|
||||
logging.getLogger("engineio").setLevel(logging.WARNING)
|
||||
logging.getLogger("geventwebsocket").setLevel(logging.WARNING)
|
||||
|
||||
logging.info(f"日志系统已初始化: 级别={Config.LOG_LEVEL}")
|
||||
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
"""开发环境配置"""
|
||||
ENABLE_SIMULATION = True
|
||||
LOG_LEVEL = "DEBUG"
|
||||
|
||||
|
||||
class ProductionConfig(Config):
|
||||
"""生产环境配置"""
|
||||
ENABLE_SIMULATION = False
|
||||
LOG_LEVEL = "INFO"
|
||||
|
||||
|
||||
class TestingConfig(Config):
|
||||
"""测试环境配置"""
|
||||
ENABLE_SIMULATION = True
|
||||
LOG_LEVEL = "DEBUG"
|
||||
WEB_PORT = 8081
|
||||
COMM_PORT = 5001
|
||||
|
||||
|
||||
# 根据环境变量选择配置
|
||||
def get_config():
|
||||
"""获取当前环境的配置"""
|
||||
env = os.environ.get("ENVIRONMENT", "development").lower()
|
||||
|
||||
if env == "production":
|
||||
return ProductionConfig()
|
||||
elif env == "testing":
|
||||
return TestingConfig()
|
||||
else:
|
||||
return DevelopmentConfig()
|
||||
|
||||
|
||||
# 导出当前配置
|
||||
current_config = get_config()
|
@ -1,29 +0,0 @@
|
||||
import os
|
||||
import logging
|
||||
|
||||
# 数据库配置
|
||||
class DatabaseConfig:
|
||||
"""数据库配置类"""
|
||||
|
||||
# SQLite配置
|
||||
SQLITE_DATABASE_PATH = os.path.join(os.getcwd(), 'battlefield_medical_system.db')
|
||||
|
||||
# 初始化配置
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.logger.info("初始化数据库配置 - 使用SQLite")
|
||||
|
||||
# SQLAlchemy配置
|
||||
@property
|
||||
def SQLALCHEMY_DATABASE_URI(self):
|
||||
return f"sqlite:///{self.SQLITE_DATABASE_PATH}"
|
||||
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
SQLALCHEMY_ECHO = False
|
||||
|
||||
def get_database_uri(self):
|
||||
"""获取数据库URI"""
|
||||
return self.SQLALCHEMY_DATABASE_URI
|
||||
|
||||
# 导出当前配置
|
||||
db_config = DatabaseConfig()
|
@ -1,14 +0,0 @@
|
||||
with open('.env', 'w', encoding='utf-8') as f:
|
||||
f.write('''# MySQL数据库配置
|
||||
MYSQL_HOST=localhost
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_USER=root
|
||||
MYSQL_PASSWORD=
|
||||
MYSQL_DATABASE=battlefield_medical_system
|
||||
SQLALCHEMY_ECHO=False
|
||||
|
||||
# 基本配置
|
||||
ENABLE_SIMULATION=True
|
||||
''')
|
||||
|
||||
print("已创建.env文件")
|
@ -1,168 +0,0 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import os
|
||||
import random
|
||||
|
||||
# 创建目录
|
||||
os.makedirs('test_images', exist_ok=True)
|
||||
os.makedirs('src/ui/static/training_images', exist_ok=True)
|
||||
|
||||
def create_battlefield_scene(width=800, height=600, file_path="", for_training=False):
|
||||
"""创建战场场景图片"""
|
||||
# 背景 - 草地/沙地
|
||||
background = np.ones((height, width, 3), dtype=np.uint8) * 150
|
||||
|
||||
# 添加一些纹理
|
||||
for i in range(0, height, 5):
|
||||
for j in range(0, width, 5):
|
||||
val = np.random.randint(120, 180)
|
||||
background[i:i+5, j:j+5] = [val-30, val, val-50] # 草地/沙地颜色
|
||||
|
||||
# 添加几个可能是"伤员"的物体
|
||||
casualties = []
|
||||
|
||||
# 随机伤员数量
|
||||
casualties_count = random.randint(1, 5)
|
||||
|
||||
for i in range(casualties_count):
|
||||
# 随机位置
|
||||
x = random.randint(100, width-100)
|
||||
y = random.randint(100, height-100)
|
||||
|
||||
# 随机尺寸
|
||||
body_width = random.randint(40, 70)
|
||||
body_height = random.randint(25, 45)
|
||||
head_size = random.randint(15, 25)
|
||||
|
||||
# 随机角度
|
||||
angle = random.randint(0, 360)
|
||||
|
||||
# 随机颜色 - 模拟不同肤色和衣服
|
||||
body_color = (
|
||||
random.randint(20, 40),
|
||||
random.randint(20, 40),
|
||||
random.randint(80, 120)
|
||||
)
|
||||
|
||||
head_color = (
|
||||
random.randint(60, 90),
|
||||
random.randint(60, 90),
|
||||
random.randint(130, 170)
|
||||
)
|
||||
|
||||
# 绘制伤员
|
||||
cv2.ellipse(background, (x, y), (body_width, body_height), angle, 0, 360, body_color, -1)
|
||||
|
||||
# 计算头部位置 - 考虑角度
|
||||
head_offset_x = int(np.cos(np.radians(angle)) * (body_width + head_size/2))
|
||||
head_offset_y = int(np.sin(np.radians(angle)) * (body_height + head_size/2))
|
||||
head_x = x + head_offset_x
|
||||
head_y = y + head_offset_y
|
||||
|
||||
# 确保头部在图像范围内
|
||||
head_x = max(head_size, min(width-head_size, head_x))
|
||||
head_y = max(head_size, min(height-head_size, head_y))
|
||||
|
||||
cv2.circle(background, (head_x, head_y), head_size, head_color, -1)
|
||||
|
||||
# 保存伤员坐标 - 用于训练标注
|
||||
if for_training:
|
||||
# 计算伤员边界框 (xmin, ymin, xmax, ymax)
|
||||
xmin = max(0, x - body_width)
|
||||
ymin = max(0, y - body_height)
|
||||
xmax = min(width, x + body_width)
|
||||
ymax = min(height, y + body_height)
|
||||
|
||||
# 调整确保头部也在边界框内
|
||||
xmin = min(xmin, head_x - head_size)
|
||||
ymin = min(ymin, head_y - head_size)
|
||||
xmax = max(xmax, head_x + head_size)
|
||||
ymax = max(ymax, head_y + head_size)
|
||||
|
||||
# 存储为像素坐标
|
||||
casualties.append({
|
||||
'x': int(xmin),
|
||||
'y': int(ymin),
|
||||
'width': int(xmax - xmin),
|
||||
'height': int(ymax - ymin),
|
||||
'class': 'casualty'
|
||||
})
|
||||
|
||||
# 添加一些其他物体 - 障碍物、车辆等
|
||||
for _ in range(random.randint(3, 8)):
|
||||
x = np.random.randint(50, width-50)
|
||||
y = np.random.randint(50, height-50)
|
||||
size = np.random.randint(10, 60)
|
||||
color = (
|
||||
np.random.randint(0, 100),
|
||||
np.random.randint(0, 100),
|
||||
np.random.randint(0, 100)
|
||||
)
|
||||
shape_type = random.choice(['circle', 'rectangle'])
|
||||
|
||||
if shape_type == 'circle':
|
||||
cv2.circle(background, (x, y), size, color, -1)
|
||||
else:
|
||||
rect_width = random.randint(size//2, size*2)
|
||||
rect_height = random.randint(size//2, size*2)
|
||||
cv2.rectangle(
|
||||
background,
|
||||
(x-rect_width//2, y-rect_height//2),
|
||||
(x+rect_width//2, y+rect_height//2),
|
||||
color,
|
||||
-1
|
||||
)
|
||||
|
||||
# 添加一些模糊和噪声
|
||||
background = cv2.GaussianBlur(background, (5, 5), 0)
|
||||
|
||||
# 添加一些噪声
|
||||
noise = np.zeros(background.shape, np.uint8)
|
||||
cv2.randn(noise, (0, 0, 0), (10, 10, 10))
|
||||
background = cv2.add(background, noise)
|
||||
|
||||
# 保存图像
|
||||
cv2.imwrite(file_path, background)
|
||||
|
||||
return casualties
|
||||
|
||||
# 创建基本测试图像
|
||||
create_battlefield_scene(
|
||||
width=800,
|
||||
height=600,
|
||||
file_path='test_images/battlefield_scene.jpg',
|
||||
for_training=False
|
||||
)
|
||||
print("测试图像已创建: test_images/battlefield_scene.jpg")
|
||||
|
||||
# 创建用于训练的图像和标注
|
||||
training_count = 10 # 生成10张训练用图片
|
||||
for i in range(training_count):
|
||||
# 生成图片文件名
|
||||
img_filename = f"battlefield_training_{i+1}.jpg"
|
||||
img_path = f"src/ui/static/training_images/{img_filename}"
|
||||
|
||||
# 生成图片和标注
|
||||
casualties = create_battlefield_scene(
|
||||
width=1024,
|
||||
height=768,
|
||||
file_path=img_path,
|
||||
for_training=True
|
||||
)
|
||||
|
||||
# 保存标注信息到JSON文件
|
||||
if casualties:
|
||||
import json
|
||||
|
||||
# 确保标注目录存在
|
||||
os.makedirs('src/ui/static/annotations', exist_ok=True)
|
||||
|
||||
# 创建标注文件名和图像ID
|
||||
image_id = f"img_{int(i+1)}"
|
||||
annotation_path = f"src/ui/static/annotations/{image_id}.json"
|
||||
|
||||
# 保存标注
|
||||
with open(annotation_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(casualties, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"已生成 {training_count} 张训练图片和标注,位于 src/ui/static/training_images/ 和 src/ui/static/annotations/")
|
@ -1,89 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 确保目录结构存在
|
||||
mkdir -p src/drone/
|
||||
mkdir -p src/communication/
|
||||
mkdir -p src/positioning/
|
||||
mkdir -p src/ui/templates/
|
||||
mkdir -p src/ui/static/css/
|
||||
mkdir -p src/ui/static/js/
|
||||
mkdir -p src/ui/static/img/
|
||||
mkdir -p config/
|
||||
mkdir -p tests/
|
||||
|
||||
# 创建空的__init__.py文件确保模块可以被导入
|
||||
touch src/__init__.py
|
||||
touch src/drone/__init__.py
|
||||
touch src/communication/__init__.py
|
||||
touch src/positioning/__init__.py
|
||||
touch src/ui/__init__.py
|
||||
touch config/__init__.py
|
||||
touch tests/__init__.py
|
||||
|
||||
# 创建无人机和伤员图标
|
||||
echo "创建示例图标..."
|
||||
# 无人机图标
|
||||
cat > src/ui/static/img/drone.png << EOF
|
||||
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAA
|
||||
AXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAA
|
||||
DsMAAA7DAcdvqGQAAAKDSURBVFhHxZdPSBRhGMZnZ/9o/9xd
|
||||
d1ddT0GHUiGDoEMQhEGHOhR0COoQdKhLHToEXYIuQYc8BR3q
|
||||
VNChQ9Ch6BAEHaJDRIeIVNBW27Vd/7XrzrwzO9O8M9/MfLM7
|
||||
s+sXPPDyvO/7vd+8O7PrjrFGnMP+vgZ0dnZmzs/Pb0aj0QBG
|
||||
e97QJyqRSOB8Po8WFxcPAZcYehD0H9GRy+WyiAcpM4BvuKn/
|
||||
qCiKKkVRphgacCrYcRFPOHWQxpPJ5HWn/lZQFYkHQYWC0mJU
|
||||
wkk8CAqgKvHyIMchCDGqkuPQoEQQJMehAWLUiSAYZJbfb5TN
|
||||
Zvc45zrQCfN+NiC0iG4RoQUQIYi7xMuDHIcgxKhKjkODEkGQ
|
||||
HIcGiFF1iECQWbqDZmZmWs+0w2P9osBJtBvQCQdxXRA4wE5g
|
||||
NZvNcpzjnMsTlHQA12q1E4CfAF47OTnp5TgvJCh6jO5Fh4eH
|
||||
Wcj7gPfAHx1APB7/heMxdH9lZeUaJz00CrgYCCGb8/PzF7PZ
|
||||
7KKqqsfGzMuHVAECyePj428qlconY+blQwbQBXg3nU4/xgXk
|
||||
G6/CILtgjGgX8AGOBYyJ14YxoxeZqrPwkN2CfUQDbI2yGRGx
|
||||
lI+FdAuwM1qtVhPbIxmPx28ZMxdEAXbGW1tbK81m81MqlRox
|
||||
Zi6ILoB3wpOTk0cAj8Hr7e3t58bMBa/nQAdxPB/GA/nU1dV1
|
||||
wZhZIP8GaJBMJge2t7efxWKxH9PT0/nBwcFOjnPCi4EQYYLZ
|
||||
WVb8FZIpE0D89MTERO/Ozs55o+TlQ7YLsJPvPEbrOA7X6/XL
|
||||
uCZrxk4P2QDojPgf0wlgEvB+aWlpbWho6J/REqA6Rv8B0U+G
|
||||
YRp/6oQAAAAASUVORK5CYII=
|
||||
EOF
|
||||
|
||||
# 伤员图标
|
||||
cat > src/ui/static/img/casualty.png << EOF
|
||||
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAA
|
||||
AXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAA
|
||||
DsMAAA7DAcdvqGQAAAKDSURBVFhHxZdPSBRhGMZnZ/9o/9xd
|
||||
d1ddT0GHUiGDoEMQhEGHOhR0COoQdKhLHToEXYIuQYc8BR3q
|
||||
VNChQ9Ch6BAEHaJDRIeIVNBW27Vd/7XrzrwzO9O8M9/MfLM7
|
||||
s+sXPPDyvO/7vd+8O7PrjrFGnMP+vgZ0dnZmzs/Pb0aj0QBG
|
||||
e97QJyqRSOB8Po8WFxcPAZcYehD0H9GRy+WyiAcpM4BvuKn/
|
||||
qCiKKkVRphgacCrYcRFPOHWQxpPJ5HWn/lZQFYkHQYWC0mJU
|
||||
wkk8CAqgKvHyIMchCDGqkuPQoEQQJMehAWLUiSAYZJbfb5TN
|
||||
Zvc45zrQCfN+NiC0iG4RoQUQIYi7xMuDHIcgxKhKjkODEkGQ
|
||||
HIcGiFF1iECQWbqDZmZmWs+0w2P9osBJtBvQCQdxXRA4wE5g
|
||||
NZvNcpzjnMsTlHQA12q1E4CfAF47OTnp5TgvJCh6jO5Fh4eH
|
||||
Wcj7gPfAHx1APB7/heMxdH9lZeUaJz00CrgYCCGb8/PzF7PZ
|
||||
7KKqqsfGzMuHVAECyePj428qlconY+blQwbQBXg3nU4/xgXk
|
||||
G6/CILtgjGgX8AGOBYyJ14YxoxeZqrPwkN2CfUQDbI2yGRGx
|
||||
lI+FdAuwM1qtVhPbIxmPx28ZMxdEAXbGW1tbK81m81MqlRox
|
||||
Zi6ILoB3wpOTk0cAj8Hr7e3t58bMBa/nQAdxPB/GA/nU1dV1
|
||||
wZhZIP8GaJBMJge2t7efxWKxH9PT0/nBwcFOjnPCi4EQYYLZ
|
||||
WVb8FZIpE0D89MTERO/Ozs55o+TlQ7YLsJPvPEbrOA7X6/XL
|
||||
uCZrxk4P2QDojPgf0wlgEvB+aWlpbWho6J/REqA6Rv8B0U+G
|
||||
YRp/6oQAAAAASUVORK5CYII=
|
||||
EOF
|
||||
|
||||
# 创建环境变量文件
|
||||
cp .env.example .env
|
||||
|
||||
# 创建一个简单的运行脚本
|
||||
cat > run.sh << EOF
|
||||
#!/bin/bash
|
||||
python simulation.py "\$@"
|
||||
EOF
|
||||
chmod +x run.sh
|
||||
|
||||
echo "项目初始化完成!"
|
||||
echo "运行以下命令安装依赖:"
|
||||
echo " pip install -r requirements.txt"
|
||||
echo "运行系统:"
|
||||
echo " ./run.sh"
|
@ -1,9 +0,0 @@
|
||||
pymavlink==2.4.35
|
||||
dronekit==2.9.2
|
||||
pyserial==3.5
|
||||
flask==2.3.3
|
||||
flask-socketio==5.3.6
|
||||
python-dotenv==1.0.0
|
||||
geopy==2.4.1
|
||||
numpy==1.24.3
|
||||
pandas==2.0.3
|
@ -1,199 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import random
|
||||
import threading
|
||||
import signal
|
||||
import argparse
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.append(current_dir)
|
||||
|
||||
from config.config import current_config as config
|
||||
|
||||
# 确保环境变量设置为启用模拟
|
||||
os.environ['ENABLE_SIMULATION'] = 'True'
|
||||
|
||||
# 设置日志
|
||||
config.setup_logging()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DroneSimulator:
|
||||
"""无人机模拟器"""
|
||||
|
||||
def __init__(self, port=14550):
|
||||
"""初始化模拟器"""
|
||||
self.port = port
|
||||
self.running = False
|
||||
self.thread = None
|
||||
self.latitude = config.BASE_LATITUDE
|
||||
self.longitude = config.BASE_LONGITUDE
|
||||
self.altitude = 0.0
|
||||
self.battery = 100.0
|
||||
self.armed = False
|
||||
self.mode = "GUIDED"
|
||||
|
||||
def start(self):
|
||||
"""启动模拟器"""
|
||||
if self.running:
|
||||
return
|
||||
|
||||
self.running = True
|
||||
self.thread = threading.Thread(target=self._simulation_loop, daemon=True)
|
||||
self.thread.start()
|
||||
logger.info(f"模拟器已启动在端口 {self.port}")
|
||||
|
||||
def stop(self):
|
||||
"""停止模拟器"""
|
||||
if not self.running:
|
||||
return
|
||||
|
||||
self.running = False
|
||||
if self.thread:
|
||||
self.thread.join(timeout=2)
|
||||
logger.info("模拟器已停止")
|
||||
|
||||
def _simulation_loop(self):
|
||||
"""模拟循环"""
|
||||
while self.running:
|
||||
# 模拟电池消耗
|
||||
if self.armed and self.battery > 0:
|
||||
self.battery -= 0.01
|
||||
if self.battery < 0:
|
||||
self.battery = 0
|
||||
|
||||
# 模拟位置变化
|
||||
if self.armed and self.mode == "GUIDED" and self.altitude > 0:
|
||||
# 随机漂移
|
||||
self.latitude += random.uniform(-0.00001, 0.00001)
|
||||
self.longitude += random.uniform(-0.00001, 0.00001)
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
def arm(self):
|
||||
"""解锁无人机"""
|
||||
self.armed = True
|
||||
logger.info("模拟无人机已解锁")
|
||||
|
||||
def disarm(self):
|
||||
"""锁定无人机"""
|
||||
self.armed = False
|
||||
logger.info("模拟无人机已锁定")
|
||||
|
||||
def set_mode(self, mode):
|
||||
"""设置模式"""
|
||||
self.mode = mode
|
||||
logger.info(f"模拟无人机模式已设置为 {mode}")
|
||||
|
||||
def takeoff(self, altitude):
|
||||
"""起飞"""
|
||||
self.arm()
|
||||
self.altitude = altitude
|
||||
logger.info(f"模拟无人机起飞到高度 {altitude}m")
|
||||
|
||||
def land(self):
|
||||
"""降落"""
|
||||
self.altitude = 0
|
||||
self.disarm()
|
||||
logger.info("模拟无人机已降落")
|
||||
|
||||
def goto(self, lat, lon, alt):
|
||||
"""飞往指定位置"""
|
||||
logger.info(f"模拟无人机飞往 ({lat}, {lon}, {alt})")
|
||||
|
||||
# 模拟飞行时间
|
||||
distance = ((lat - self.latitude) ** 2 + (lon - self.longitude) ** 2) ** 0.5 * 111000
|
||||
speed = 5.0 # 假设速度为5m/s
|
||||
flight_time = distance / speed
|
||||
|
||||
# 模拟飞行过程
|
||||
start_lat = self.latitude
|
||||
start_lon = self.longitude
|
||||
start_alt = self.altitude
|
||||
|
||||
for i in range(10):
|
||||
progress = (i + 1) / 10
|
||||
self.latitude = start_lat + (lat - start_lat) * progress
|
||||
self.longitude = start_lon + (lon - start_lon) * progress
|
||||
self.altitude = start_alt + (alt - start_alt) * progress
|
||||
time.sleep(flight_time / 10)
|
||||
|
||||
self.latitude = lat
|
||||
self.longitude = lon
|
||||
self.altitude = alt
|
||||
logger.info(f"模拟无人机已到达 ({lat}, {lon}, {alt})")
|
||||
|
||||
|
||||
def setup_simulation():
|
||||
"""设置模拟环境"""
|
||||
# 启动模拟器
|
||||
simulator = DroneSimulator()
|
||||
simulator.start()
|
||||
|
||||
# 创建SITL连接字符串和覆盖环境变量
|
||||
os.environ['DRONE_CONNECTION_STRING'] = f'udpin:localhost:{simulator.port}'
|
||||
|
||||
return simulator
|
||||
|
||||
|
||||
def run_simulation():
|
||||
"""运行模拟"""
|
||||
# 设置参数解析
|
||||
parser = argparse.ArgumentParser(description='智能战场医疗后送系统模拟器')
|
||||
parser.add_argument('--web-port', type=int, default=8080, help='Web服务端口')
|
||||
parser.add_argument('--comm-port', type=int, default=5000, help='通信服务端口')
|
||||
parser.add_argument('--drone-port', type=int, default=14550, help='无人机模拟器端口')
|
||||
args = parser.parse_args()
|
||||
|
||||
# 设置环境变量
|
||||
os.environ['WEB_PORT'] = str(args.web_port)
|
||||
os.environ['COMM_PORT'] = str(args.comm_port)
|
||||
|
||||
# 启动模拟器
|
||||
simulator = DroneSimulator(port=args.drone_port)
|
||||
simulator.start()
|
||||
|
||||
# 导入主程序模块
|
||||
from main import MedicalEvacuationSystem
|
||||
|
||||
# 创建系统实例
|
||||
system = MedicalEvacuationSystem()
|
||||
|
||||
# 注册信号处理
|
||||
def signal_handler(sig, frame):
|
||||
logger.info("接收到停止信号,正在关闭模拟器...")
|
||||
system.stop()
|
||||
simulator.stop()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
# 启动系统
|
||||
try:
|
||||
if system.start():
|
||||
logger.info("系统启动成功,运行在模拟模式")
|
||||
|
||||
# 保持程序运行
|
||||
while True:
|
||||
time.sleep(1)
|
||||
else:
|
||||
logger.error("系统启动失败")
|
||||
simulator.stop()
|
||||
return 1
|
||||
except Exception as e:
|
||||
logger.error(f"模拟运行错误: {str(e)}")
|
||||
system.stop()
|
||||
simulator.stop()
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = run_simulation()
|
||||
sys.exit(exit_code)
|
@ -1,140 +0,0 @@
|
||||
import socket
|
||||
import json
|
||||
import logging
|
||||
from threading import Thread, Lock
|
||||
|
||||
class CommunicationManager:
|
||||
def __init__(self, host='0.0.0.0', port=5000):
|
||||
"""
|
||||
初始化通信管理器
|
||||
:param host: 服务器主机地址
|
||||
:param port: 服务器端口
|
||||
"""
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.server_socket = None
|
||||
self.clients = []
|
||||
self.lock = Lock()
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.casualty_handler = None
|
||||
self.supply_handler = None
|
||||
|
||||
def set_casualty_handler(self, handler):
|
||||
"""
|
||||
设置伤员处理回调函数
|
||||
:param handler: 处理函数
|
||||
"""
|
||||
self.casualty_handler = handler
|
||||
self.logger.info("已设置伤员处理回调函数")
|
||||
|
||||
def set_supply_handler(self, handler):
|
||||
"""
|
||||
设置物资处理回调函数
|
||||
:param handler: 处理函数
|
||||
"""
|
||||
self.supply_handler = handler
|
||||
self.logger.info("已设置物资处理回调函数")
|
||||
|
||||
def start_server(self):
|
||||
"""启动通信服务器"""
|
||||
try:
|
||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.server_socket.bind((self.host, self.port))
|
||||
self.server_socket.listen(5)
|
||||
self.logger.info(f"通信服务器启动在 {self.host}:{self.port}")
|
||||
|
||||
# 启动客户端接受线程
|
||||
Thread(target=self._accept_clients, daemon=True).start()
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"启动服务器失败: {str(e)}")
|
||||
|
||||
def _accept_clients(self):
|
||||
"""接受新的客户端连接"""
|
||||
while True:
|
||||
try:
|
||||
client_socket, address = self.server_socket.accept()
|
||||
self.logger.info(f"新客户端连接: {address}")
|
||||
|
||||
with self.lock:
|
||||
self.clients.append(client_socket)
|
||||
|
||||
# 为每个客户端启动一个处理线程
|
||||
Thread(target=self._handle_client, args=(client_socket,), daemon=True).start()
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"接受客户端连接失败: {str(e)}")
|
||||
|
||||
def _handle_client(self, client_socket):
|
||||
"""处理客户端消息"""
|
||||
while True:
|
||||
try:
|
||||
data = client_socket.recv(1024).decode('utf-8')
|
||||
if not data:
|
||||
break
|
||||
|
||||
message = json.loads(data)
|
||||
self._process_message(message)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"处理客户端消息失败: {str(e)}")
|
||||
break
|
||||
|
||||
# 客户端断开连接
|
||||
with self.lock:
|
||||
if client_socket in self.clients:
|
||||
self.clients.remove(client_socket)
|
||||
client_socket.close()
|
||||
|
||||
def _process_message(self, message):
|
||||
"""
|
||||
处理接收到的消息
|
||||
:param message: 消息内容
|
||||
"""
|
||||
message_type = message.get('type')
|
||||
if message_type == 'casualty_location':
|
||||
self._handle_casualty_location(message)
|
||||
elif message_type == 'medical_supply_request':
|
||||
self._handle_medical_supply_request(message)
|
||||
elif message_type == 'drone_status':
|
||||
self._handle_drone_status(message)
|
||||
|
||||
def _handle_casualty_location(self, message):
|
||||
"""处理伤员位置信息"""
|
||||
location = message.get('location')
|
||||
self.logger.info(f"收到伤员位置: {location}")
|
||||
if self.casualty_handler:
|
||||
self.casualty_handler(message)
|
||||
|
||||
def _handle_medical_supply_request(self, message):
|
||||
"""处理医疗物资请求"""
|
||||
supplies = message.get('supplies')
|
||||
location = message.get('location')
|
||||
self.logger.info(f"收到医疗物资请求: {supplies} 送往 {location}")
|
||||
if self.supply_handler:
|
||||
self.supply_handler(message)
|
||||
|
||||
def _handle_drone_status(self, message):
|
||||
"""处理无人机状态信息"""
|
||||
status = message.get('status')
|
||||
self.logger.info(f"收到无人机状态: {status}")
|
||||
# TODO: 实现无人机状态处理逻辑
|
||||
|
||||
def broadcast_message(self, message):
|
||||
"""
|
||||
广播消息给所有客户端
|
||||
:param message: 要广播的消息
|
||||
"""
|
||||
message_str = json.dumps(message)
|
||||
with self.lock:
|
||||
for client in self.clients:
|
||||
try:
|
||||
client.send(message_str.encode('utf-8'))
|
||||
except Exception as e:
|
||||
self.logger.error(f"发送消息失败: {str(e)}")
|
||||
|
||||
def close(self):
|
||||
"""关闭通信服务器"""
|
||||
if self.server_socket:
|
||||
self.server_socket.close()
|
||||
self.logger.info("通信服务器已关闭")
|
@ -1,19 +0,0 @@
|
||||
from flask import Flask
|
||||
from config.database import db_config
|
||||
from src.db.models import db
|
||||
|
||||
def init_db(app: Flask):
|
||||
"""初始化数据库"""
|
||||
# 配置SQLAlchemy
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = db_config.SQLALCHEMY_DATABASE_URI
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = db_config.SQLALCHEMY_TRACK_MODIFICATIONS
|
||||
app.config['SQLALCHEMY_ECHO'] = db_config.SQLALCHEMY_ECHO
|
||||
|
||||
# 初始化SQLAlchemy
|
||||
db.init_app(app)
|
||||
|
||||
# 创建所有表(如果不存在)
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
return db
|
@ -1,271 +0,0 @@
|
||||
import logging
|
||||
import time
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from queue import Queue
|
||||
from threading import Thread, Lock
|
||||
|
||||
class MissionType(Enum):
|
||||
"""任务类型枚举"""
|
||||
IDLE = 0
|
||||
MEDICAL_SUPPLY = 1
|
||||
SURVEILLANCE = 2
|
||||
RETURN_TO_BASE = 3
|
||||
|
||||
class MissionStatus(Enum):
|
||||
"""任务状态枚举"""
|
||||
PENDING = 0
|
||||
IN_PROGRESS = 1
|
||||
COMPLETED = 2
|
||||
FAILED = 3
|
||||
|
||||
class Mission:
|
||||
"""任务类"""
|
||||
def __init__(self,
|
||||
mission_id: str,
|
||||
mission_type: MissionType,
|
||||
target_location: Tuple[float, float],
|
||||
altitude: float = 10.0,
|
||||
payload: Dict = None):
|
||||
"""
|
||||
初始化任务
|
||||
:param mission_id: 任务ID
|
||||
:param mission_type: 任务类型
|
||||
:param target_location: 目标位置(纬度, 经度)
|
||||
:param altitude: 飞行高度(米)
|
||||
:param payload: 任务负载(如物资信息)
|
||||
"""
|
||||
self.mission_id = mission_id
|
||||
self.mission_type = mission_type
|
||||
self.target_location = target_location
|
||||
self.altitude = altitude
|
||||
self.payload = payload or {}
|
||||
self.status = MissionStatus.PENDING
|
||||
self.start_time = None
|
||||
self.end_time = None
|
||||
self.error_message = None
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
"""转换成字典"""
|
||||
return {
|
||||
'mission_id': self.mission_id,
|
||||
'mission_type': self.mission_type.name,
|
||||
'target_location': self.target_location,
|
||||
'altitude': self.altitude,
|
||||
'payload': self.payload,
|
||||
'status': self.status.name,
|
||||
'start_time': self.start_time,
|
||||
'end_time': self.end_time,
|
||||
'error_message': self.error_message
|
||||
}
|
||||
|
||||
|
||||
class MissionPlanner:
|
||||
"""任务规划器"""
|
||||
def __init__(self, drone_controller):
|
||||
"""
|
||||
初始化任务规划器
|
||||
:param drone_controller: 无人机控制器
|
||||
"""
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.drone_controller = drone_controller
|
||||
self.mission_queue = Queue()
|
||||
self.current_mission = None
|
||||
self.mission_history = []
|
||||
self.lock = Lock()
|
||||
self.running = False
|
||||
self.worker_thread = None
|
||||
|
||||
def start(self):
|
||||
"""启动任务规划器"""
|
||||
if self.running:
|
||||
self.logger.warning("任务规划器已经在运行")
|
||||
return
|
||||
|
||||
self.running = True
|
||||
self.worker_thread = Thread(target=self._mission_worker, daemon=True)
|
||||
self.worker_thread.start()
|
||||
self.logger.info("任务规划器已启动")
|
||||
|
||||
def stop(self):
|
||||
"""停止任务规划器"""
|
||||
if not self.running:
|
||||
return
|
||||
|
||||
self.running = False
|
||||
if self.worker_thread:
|
||||
self.worker_thread.join(timeout=5)
|
||||
self.logger.info("任务规划器已停止")
|
||||
|
||||
def add_mission(self, mission: Mission) -> bool:
|
||||
"""
|
||||
添加任务到队列
|
||||
:param mission: 任务对象
|
||||
:return: 是否添加成功
|
||||
"""
|
||||
try:
|
||||
self.mission_queue.put(mission)
|
||||
self.logger.info(f"添加任务: ID={mission.mission_id}, 类型={mission.mission_type.name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"添加任务失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_current_mission(self) -> Optional[Mission]:
|
||||
"""
|
||||
获取当前执行的任务
|
||||
:return: 当前任务或None
|
||||
"""
|
||||
with self.lock:
|
||||
return self.current_mission
|
||||
|
||||
def get_mission_queue_size(self) -> int:
|
||||
"""
|
||||
获取任务队列大小
|
||||
:return: 队列中的任务数
|
||||
"""
|
||||
return self.mission_queue.qsize()
|
||||
|
||||
def get_mission_history(self) -> List[Dict]:
|
||||
"""
|
||||
获取任务历史
|
||||
:return: 任务历史记录列表
|
||||
"""
|
||||
with self.lock:
|
||||
return [mission.to_dict() for mission in self.mission_history]
|
||||
|
||||
def _mission_worker(self):
|
||||
"""任务工作线程"""
|
||||
while self.running:
|
||||
try:
|
||||
if not self.mission_queue.empty():
|
||||
# 从队列中获取下一个任务
|
||||
mission = self.mission_queue.get()
|
||||
|
||||
with self.lock:
|
||||
self.current_mission = mission
|
||||
self.current_mission.status = MissionStatus.IN_PROGRESS
|
||||
self.current_mission.start_time = time.time()
|
||||
|
||||
self.logger.info(f"开始执行任务: ID={mission.mission_id}, 类型={mission.mission_type.name}")
|
||||
|
||||
# 根据任务类型执行不同操作
|
||||
success = self._execute_mission(mission)
|
||||
|
||||
with self.lock:
|
||||
if success:
|
||||
self.current_mission.status = MissionStatus.COMPLETED
|
||||
self.logger.info(f"任务完成: ID={mission.mission_id}")
|
||||
else:
|
||||
self.current_mission.status = MissionStatus.FAILED
|
||||
self.logger.error(f"任务失败: ID={mission.mission_id}")
|
||||
|
||||
self.current_mission.end_time = time.time()
|
||||
self.mission_history.append(self.current_mission)
|
||||
self.current_mission = None
|
||||
|
||||
self.mission_queue.task_done()
|
||||
else:
|
||||
# 无任务时等待
|
||||
time.sleep(1)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"任务执行过程出错: {str(e)}")
|
||||
if self.current_mission:
|
||||
with self.lock:
|
||||
self.current_mission.status = MissionStatus.FAILED
|
||||
self.current_mission.error_message = str(e)
|
||||
self.current_mission.end_time = time.time()
|
||||
self.mission_history.append(self.current_mission)
|
||||
self.current_mission = None
|
||||
|
||||
def _execute_mission(self, mission: Mission) -> bool:
|
||||
"""
|
||||
执行任务
|
||||
:param mission: 任务对象
|
||||
:return: 是否执行成功
|
||||
"""
|
||||
try:
|
||||
if mission.mission_type == MissionType.MEDICAL_SUPPLY:
|
||||
return self._execute_medical_supply_mission(mission)
|
||||
elif mission.mission_type == MissionType.SURVEILLANCE:
|
||||
return self._execute_surveillance_mission(mission)
|
||||
elif mission.mission_type == MissionType.RETURN_TO_BASE:
|
||||
return self._execute_return_to_base_mission(mission)
|
||||
else:
|
||||
self.logger.warning(f"未知任务类型: {mission.mission_type}")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error(f"执行任务失败: {str(e)}")
|
||||
mission.error_message = str(e)
|
||||
return False
|
||||
|
||||
def _execute_medical_supply_mission(self, mission: Mission) -> bool:
|
||||
"""
|
||||
执行医疗物资运送任务
|
||||
:param mission: 任务对象
|
||||
:return: 是否执行成功
|
||||
"""
|
||||
try:
|
||||
# 飞往目标位置
|
||||
lat, lon = mission.target_location
|
||||
self.drone_controller.goto_location(lat, lon, mission.altitude)
|
||||
|
||||
# 模拟投放物资(实际应该有硬件操作)
|
||||
self.logger.info(f"投放医疗物资: {mission.payload.get('supply_name', '未知物资')}")
|
||||
time.sleep(2) # 模拟投放时间
|
||||
|
||||
# 返回基地
|
||||
return self._execute_return_to_base_mission(mission)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"医疗物资任务执行失败: {str(e)}")
|
||||
mission.error_message = str(e)
|
||||
return False
|
||||
|
||||
def _execute_surveillance_mission(self, mission: Mission) -> bool:
|
||||
"""
|
||||
执行侦察任务
|
||||
:param mission: 任务对象
|
||||
:return: 是否执行成功
|
||||
"""
|
||||
try:
|
||||
# 飞往目标位置
|
||||
lat, lon = mission.target_location
|
||||
self.drone_controller.goto_location(lat, lon, mission.altitude)
|
||||
|
||||
# 模拟侦察(实际应该有摄像头操作)
|
||||
self.logger.info(f"进行区域侦察: ({lat}, {lon})")
|
||||
time.sleep(5) # 模拟侦察时间
|
||||
|
||||
# 返回基地
|
||||
return self._execute_return_to_base_mission(mission)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"侦察任务执行失败: {str(e)}")
|
||||
mission.error_message = str(e)
|
||||
return False
|
||||
|
||||
def _execute_return_to_base_mission(self, mission: Mission) -> bool:
|
||||
"""
|
||||
执行返回基地任务
|
||||
:param mission: 任务对象
|
||||
:return: 是否执行成功
|
||||
"""
|
||||
try:
|
||||
# 获取基地位置(这里应该从配置中读取)
|
||||
base_lat, base_lon = 39.9, 116.3 # 默认位置,实际应该从配置获取
|
||||
|
||||
# 飞往基地
|
||||
self.logger.info("返回基地")
|
||||
self.drone_controller.goto_location(base_lat, base_lon, mission.altitude)
|
||||
|
||||
# 降落
|
||||
self.drone_controller.land()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"返回基地任务执行失败: {str(e)}")
|
||||
mission.error_message = str(e)
|
||||
return False
|
@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"x": 192.75340195819638,
|
||||
"y": 269.1060189546518,
|
||||
"width": 442.41011984021304,
|
||||
"height": 100.79002079002078,
|
||||
"class": "casualty"
|
||||
}
|
||||
]
|
@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"x": 11.985121427837171,
|
||||
"y": 117.62776025236593,
|
||||
"width": 335.2926315789474,
|
||||
"height": 184.41640378548897,
|
||||
"class": "casualty"
|
||||
}
|
||||
]
|
@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"x": 12.780651033233484,
|
||||
"y": 53.81395348837209,
|
||||
"width": 707.7720207253885,
|
||||
"height": 412.9186046511628,
|
||||
"class": "casualty"
|
||||
}
|
||||
]
|
@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"x": 112.26251631820757,
|
||||
"y": 376.3773436462995,
|
||||
"width": 446.6321243523316,
|
||||
"height": 147.99805825242714,
|
||||
"class": "casualty"
|
||||
}
|
||||
]
|
@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"x": 677.3609536917098,
|
||||
"y": 894.9805757837389,
|
||||
"width": 1138.0725388601036,
|
||||
"height": 442.63106796116494,
|
||||
"class": "casualty"
|
||||
}
|
||||
]
|
@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"x": 194.732217611838,
|
||||
"y": 293.69761911478724,
|
||||
"width": 268.61626248216834,
|
||||
"height": 167.63714902807772,
|
||||
"class": "casualty"
|
||||
}
|
||||
]
|
@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"x": 142.80639735506196,
|
||||
"y": 558.0339801654405,
|
||||
"width": 488.30242510699003,
|
||||
"height": 295.6647791619479,
|
||||
"class": "casualty"
|
||||
}
|
||||
]
|
@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"x": 436.9429980658497,
|
||||
"y": 254.91261424833132,
|
||||
"width": 492.4352331606218,
|
||||
"height": 341.12621359223294,
|
||||
"class": "casualty"
|
||||
}
|
||||
]
|
@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"x": 225.6994747497875,
|
||||
"y": 360.23300615329185,
|
||||
"width": 537.2020725388602,
|
||||
"height": 142.60194174757282,
|
||||
"class": "casualty"
|
||||
}
|
||||
]
|
@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"x": 16.974595112047698,
|
||||
"y": 179.43037974683543,
|
||||
"width": 453.0442105263158,
|
||||
"height": 135.56962025316457,
|
||||
"class": "casualty"
|
||||
}
|
||||
]
|
@ -1,342 +0,0 @@
|
||||
/* 全局样式 */
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #f0f4f8;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: linear-gradient(90deg, #1a3a5f 0%, #2d5f8b 100%);
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
letter-spacing: 0.5px;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 地图样式 */
|
||||
#map {
|
||||
height: 550px;
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
/* 状态面板样式 */
|
||||
.status-panel {
|
||||
background-color: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.status-panel p {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-weight: 600;
|
||||
background-color: #f8fafc;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #e0e6ed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-header i {
|
||||
margin-right: 8px;
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* 按钮样式 */
|
||||
.btn-primary {
|
||||
background: linear-gradient(45deg, #2d5f8b 0%, #3498db 100%);
|
||||
border: none;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
padding: 10px 16px;
|
||||
border-radius: 5px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(45deg, #3498db 0%, #2d5f8b 100%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #f8f9fa;
|
||||
color: #495057;
|
||||
border: 1px solid #ced4da;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #e2e6ea;
|
||||
}
|
||||
|
||||
/* 列表样式 */
|
||||
.casualty-item, .supply-item {
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
border-left: 4px solid #e74c3c;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.supply-item {
|
||||
border-left-color: #2ecc71;
|
||||
}
|
||||
|
||||
.casualty-item:hover, .supply-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 状态指示器 */
|
||||
.status-indicator {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.status-idle {
|
||||
background-color: #95a5a6;
|
||||
}
|
||||
|
||||
.status-in-flight {
|
||||
background-color: #3498db;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
.status-warning {
|
||||
background-color: #f39c12;
|
||||
}
|
||||
|
||||
.status-error {
|
||||
background-color: #e74c3c;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* 任务列表样式 */
|
||||
.mission-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.mission-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.mission-list::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.mission-list::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.mission-item {
|
||||
padding: 12px 15px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 6px;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.mission-pending {
|
||||
border-left: 4px solid #95a5a6;
|
||||
}
|
||||
|
||||
.mission-in-progress {
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.mission-completed {
|
||||
border-left: 4px solid #2ecc71;
|
||||
}
|
||||
|
||||
.mission-failed {
|
||||
border-left: 4px solid #e74c3c;
|
||||
}
|
||||
|
||||
/* 自定义地图标记样式 */
|
||||
.casualty-marker .leaflet-popup-content-wrapper {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #e74c3c;
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.drone-marker .leaflet-popup-content-wrapper {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #3498db;
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 响应式布局调整 */
|
||||
@media (max-width: 768px) {
|
||||
#map {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.col-md-4 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 地图标记样式 */
|
||||
.map-marker {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.casualty-marker {
|
||||
background-color: rgba(231, 76, 60, 0.9);
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
.drone-marker {
|
||||
background-color: rgba(52, 152, 219, 0.9);
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
border: 2px solid white;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
min-width: 200px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.popup-content h6 {
|
||||
margin-bottom: 10px;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.status-counter {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.status-counter:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.status-counter span {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.status-counter p {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 0;
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 警告提示 */
|
||||
.alert {
|
||||
border-radius: 8px;
|
||||
padding: 12px 15px;
|
||||
margin-bottom: 15px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: #e9f7fe;
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
/* 高级表单控件 */
|
||||
.form-label {
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
.form-control, .form-select {
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #dcdfe6;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-control:focus, .form-select:focus {
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.25);
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 6px 10px;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
}
|
@ -1 +0,0 @@
|
||||
This is a placeholder for a real YOLO model file
|
@ -1,9 +0,0 @@
|
||||
# YOLOv5 数据集配置
|
||||
path: src/ui/static/models\dataset
|
||||
train: images/train
|
||||
val: images/val
|
||||
test: # 暂无测试集
|
||||
|
||||
# 类别
|
||||
nc: 1 # 类别数量
|
||||
names: ['casualty'] # 类别名称
|
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 200 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 148 KiB |
Before Width: | Height: | Size: 32 KiB |
@ -1 +0,0 @@
|
||||
0 0.5519446158377372 0.6656271444784629 0.5898801597869507 0.20997920997920996
|
@ -1 +0,0 @@
|
||||
0 0.3789692768297698 0.6640378548895899 0.7073684210526316 0.583596214511041
|
@ -1 +0,0 @@
|
||||
0 0.4583333267449097 0.4874031007751938 0.8847150259067357 0.7732558139534884
|
@ -1 +0,0 @@
|
||||
0 0.4194732231179667 0.844983813832107 0.5582901554404145 0.2776699029126213
|
@ -1 +0,0 @@
|
||||
0 0.6085923941024227 0.8177993478126897 0.555699481865285 0.3242718446601941
|
@ -1 +0,0 @@
|
||||
0 0.4700576412184603 0.8171346182442123 0.38373751783166904 0.36285097192224613
|
@ -1 +0,0 @@
|
||||
0 0.5527965855836527 0.8003020065152091 0.6975748930099858 0.33522083805209507
|
@ -1 +0,0 @@
|
||||
0 0.632556124672371 0.590938501450622 0.455958549222798 0.4737864077669902
|
@ -1 +0,0 @@
|
||||
0 0.4576856583511274 0.599352745870942 0.49740932642487057 0.19805825242718447
|
@ -1 +0,0 @@
|
||||
0 0.5137061189350329 0.7848101265822786 0.9557894736842105 0.4303797468354431
|
@ -1,170 +0,0 @@
|
||||
{
|
||||
"id": "train_1746536145",
|
||||
"model_type": "yolov5s",
|
||||
"epochs": 50,
|
||||
"batch_size": 16,
|
||||
"train_split": 80,
|
||||
"model_name": "casualty_detector_v1",
|
||||
"status": "completed",
|
||||
"created_at": 1746536145.3327265,
|
||||
"model_path": "src/ui/static/models\\casualty_detector_v1.pt",
|
||||
"results": {
|
||||
"epochs": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
39,
|
||||
40,
|
||||
41,
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
45,
|
||||
46,
|
||||
47,
|
||||
48,
|
||||
49,
|
||||
50
|
||||
],
|
||||
"map": [
|
||||
0.2346910205281687,
|
||||
0.2465911830219659,
|
||||
0.2573409338308947,
|
||||
0.283949214087783,
|
||||
0.29985140911865305,
|
||||
0.3133456542772132,
|
||||
0.3414636954072322,
|
||||
0.3404219065408789,
|
||||
0.3394574027871224,
|
||||
0.3660841833106289,
|
||||
0.38276835276908183,
|
||||
0.4164534097991227,
|
||||
0.39504794443286423,
|
||||
0.41696871543371894,
|
||||
0.42355650398958544,
|
||||
0.4620468536652084,
|
||||
0.4581694345192589,
|
||||
0.4889842453771339,
|
||||
0.5100454579134762,
|
||||
0.5024204387235169,
|
||||
0.5141007414159628,
|
||||
0.5403691471891979,
|
||||
0.5301660185073334,
|
||||
0.5760235114161298,
|
||||
0.5861124620696604,
|
||||
0.5734799251193733,
|
||||
0.613563278778363,
|
||||
0.6019781840424621,
|
||||
0.6124835374681992,
|
||||
0.6648062309517518,
|
||||
0.669743039182848,
|
||||
0.6858017072735901,
|
||||
0.6746423708794934,
|
||||
0.7198532262813726,
|
||||
0.7264158744035305,
|
||||
0.7145871815350525,
|
||||
0.7544234524933556,
|
||||
0.7363939195224446,
|
||||
0.7485789500260398,
|
||||
0.7628166138477256,
|
||||
0.8119847496417109,
|
||||
0.8330255544173595,
|
||||
0.8054526755408713,
|
||||
0.8584588222370078,
|
||||
0.8480172179298857,
|
||||
0.8804983162248883,
|
||||
0.8730853946854259,
|
||||
0.8820644293747433,
|
||||
0.890186227403441,
|
||||
0.936543264621003
|
||||
],
|
||||
"loss": [
|
||||
1.4952713683230068,
|
||||
1.6241079711922601,
|
||||
1.4556141314443767,
|
||||
1.536875339018873,
|
||||
1.582250123819695,
|
||||
1.5200034162657587,
|
||||
1.3981947004420512,
|
||||
1.4676312861136551,
|
||||
1.4700722509766602,
|
||||
1.375187501451575,
|
||||
1.3189966252775418,
|
||||
1.2924728482593522,
|
||||
1.3213448817287443,
|
||||
1.3195207299055696,
|
||||
1.3955919437857862,
|
||||
1.231887561345098,
|
||||
1.2721930658066198,
|
||||
1.3068925731122645,
|
||||
1.1826480682879805,
|
||||
1.226094298070738,
|
||||
1.126356833919786,
|
||||
1.2223144647648188,
|
||||
1.201390841319237,
|
||||
1.0854719146621141,
|
||||
1.0159738345019431,
|
||||
1.0444867373351245,
|
||||
1.1365222362152665,
|
||||
1.0991116537142849,
|
||||
0.981271583853104,
|
||||
0.9295018427850593,
|
||||
0.9021050310675309,
|
||||
1.0155725886367946,
|
||||
1.0124356436636157,
|
||||
0.9777325532673871,
|
||||
0.8102139457942824,
|
||||
0.896013791152899,
|
||||
0.8892310752180109,
|
||||
0.8525369026809214,
|
||||
0.7814648301152992,
|
||||
0.7454667157507703,
|
||||
0.7597884851926442,
|
||||
0.7853594166017319,
|
||||
0.833171319042936,
|
||||
0.7362808873599007,
|
||||
0.7006277248923057,
|
||||
0.7026997869166967,
|
||||
0.6064702709140819,
|
||||
0.5530920661720794,
|
||||
0.6786561049801432,
|
||||
0.6553200520346033
|
||||
],
|
||||
"final_map": 0.936543264621003
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 244 KiB |
Before Width: | Height: | Size: 482 KiB |
Before Width: | Height: | Size: 483 KiB |
Before Width: | Height: | Size: 486 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 36 KiB |