Compare commits

...

19 Commits

Author SHA1 Message Date
p5i4afnyx 56e5047d00 Merge pull request '1' (#25) from chenxiaofu into main
2 months ago
cxf f999592f8d 修复了区域覆盖规划功能
2 months ago
pg9nrcf7t 0d05315919 Merge pull request '任务决策页面调整' (#24) from lcf_branch into main
2 months ago
linchengfu a2c6c6ff54 任务决策页面更新
2 months ago
pg9nrcf7t 7d7413ebea Merge pull request '代码调整' (#23) from lcf_branch into main
2 months ago
linchengfu 6ce2faa4da 代码调整
2 months ago
linchengfu 725640927b 代码结构调整
2 months ago
pg9nrcf7t c44f8c8e61 Merge pull request 'v1.0' (#22) from main into lcf_branch
2 months ago
pp3xivymc efa054a2d4 11
2 months ago
pp3xivymc 0b3bf3fb11 解决合并冲突并移除冗余函数visualizeCoverageMultiPaths
2 months ago
pp3xivymc a682dc7f8b 解决合并冲突:保留路径规划功能改动~
2 months ago
pp3xivymc e361621785 try commit
2 months ago
p5i4afnyx c0287c4743 Merge pull request '1' (#21) from cxf_branch into main
2 months ago
chenxiaofu 7fee3e6080 本次修改了区域搜索功能~
2 months ago
p5i4afnyx 34875c1180 Merge pull request '合并' (#20) from main into cxf_branch
2 months ago
pg9nrcf7t 415b4093e6 Merge pull request '版本0.8' (#19) from main into lcf_branch
2 months ago
pp3xivymc 2288c03787 完善功能
2 months ago
p5i4afnyx c3cd6641de Merge pull request '123' (#17) from main into cxf_branch
2 months ago
pg9nrcf7t 0b44b3afe6 Merge pull request '整理仓库' (#16) from main into lcf_branch
2 months ago

Binary file not shown.

@ -0,0 +1,14 @@
{
"coverage": "覆盖范围: 100.0%",
"droneCount": 1,
"searchTime": "预计搜索时间: 58.7min",
"tableData": [
{
"coverage": "100.0%",
"droneId": "Drone 1",
"path": "覆盖路径 (957个点)",
"time": "58.7min"
}
],
"timestamp": "2025-11-07 20:02:28"
}

@ -0,0 +1,10 @@
[
[
116.391394,
39.902759
],
[
116.458341,
39.894594
]
]

@ -29,6 +29,9 @@
#include <limits>
#include <functional>
#include <algorithm>
#include <QFile> // 已存在或添加
#include <QJsonDocument> // 添加以修复QJsonParseError和QJsonDocument
#include <QFileDialog>
MapPage::MapPage(QWidget *parent) : QWidget(parent),
bridge_(nullptr),
@ -594,44 +597,6 @@ void MapPage::clearCoverageOverlays() {
qDebug() << "区域覆盖覆盖物清除完成";
}
void MapPage::visualizeCoverageMultiPaths(const QString& multiPathsJson) {
// 多路径与覆盖圆可视化
QString js = QString(R"(
if (window.map) {
var items = JSON.parse('%1');
// 清理旧覆盖
if (!window.multiPolylines) window.multiPolylines = [];
if (!window.multiCircles) window.multiCircles = [];
for (var i=0;i<window.multiPolylines.length;i++){ if (window.multiPolylines[i]) window.map.remove(window.multiPolylines[i]); }
for (var i=0;i<window.multiCircles.length;i++){ if (window.multiCircles[i]) window.map.remove(window.multiCircles[i]); }
window.multiPolylines = [];
window.multiCircles = [];
var all = [];
for (var k=0;k<items.length;k++){
var pathCol = items[k].pathColor || '#1E88E5';
var circleCol = items[k].circleColor || '#1E88E5';
var path = items[k].path || [];
var radius = items[k].radius || 100.0;
if (path.length > 0){
var poly = new AMap.Polyline({path: path, strokeColor: pathCol, strokeOpacity: 0.9, strokeWeight: 4});
poly.setMap(window.map);
window.multiPolylines.push(poly);
all.push(poly);
// 每隔若干点画覆盖圆
for (var j=0;j<path.length;j+=Math.max(1, Math.floor(path.length/20))){
var c = new AMap.Circle({center: new AMap.LngLat(path[j][0], path[j][1]), radius: radius, fillColor: circleCol, fillOpacity: 0.12, strokeColor: circleCol, strokeWeight: 1});
c.setMap(window.map);
window.multiCircles.push(c);
all.push(c);
}
}
}
if (all.length>0) window.map.setFitView(all, {padding:[40,40,40,40]});
}
)").arg(multiPathsJson);
runMapJavaScript(js);
}
void MapPage::visualizeCoverageAreaCircle(double centerLng, double centerLat, double radiusKm) {
@ -776,9 +741,11 @@ void MapPage::removeClickListener() {
runMapJavaScript(js);
}
void MapPage::visualizePath(const QString& pathData) {
void MapPage::visualizePath(const QString& pathData, bool clearOld) {
if (clearOld) {
clearPathOverlays();
}
if (isMapReady_) {
// 直接执行JS
QString js = QString(R"(
if (window.map) {
var pathCoords = JSON.parse("%1");
@ -841,14 +808,13 @@ void MapPage::visualizePath(const QString& pathData) {
}
)").arg(pathData, pathOverlayId_);
runMapJavaScript(js);
qDebug() << "路径可视化完成";
qDebug() << "路径可视化完成 (clearOld:" << clearOld << ")";
} else {
// 等待就绪
connect(this, &MapPage::mapReady, [this, pathData](){
visualizePath(pathData); // 递归调用一次
disconnect(this, &MapPage::mapReady, nullptr, nullptr); // 清理
connect(this, &MapPage::mapReady, [this, pathData, clearOld](){
visualizePath(pathData, clearOld);
disconnect(this, &MapPage::mapReady, nullptr, nullptr);
});
qDebug() << "等待地图就绪后可视化路径";
qDebug() << "等待地图就绪后可视化路径 (clearOld:" << clearOld << ")";
}
}
@ -887,8 +853,8 @@ void MapPage::onSearchMapClicked() {
}
// Implement ThreatAreaDialog methods
ThreatAreaDialog::ThreatAreaDialog(QWidget *parent, MapPage* mapPage) : QDialog(parent), mapPage_(mapPage), drawingPoints_() {
setWindowTitle("设置威胁区域");
ThreatAreaDialog::ThreatAreaDialog(QWidget *parent, MapPage* mapPage, const QString& title) : QDialog(parent), mapPage_(mapPage), drawingPoints_() {
setWindowTitle(title);
setMinimumSize(600, 500);
auto* layout = new QVBoxLayout(this);
layout->setSpacing(15);
@ -1771,8 +1737,8 @@ void PathPlanningDialog::onMapClick(double lng, double lat) {
}
}
AreaCoverageDialog::AreaCoverageDialog(QWidget* parent, MapPage* mapPage) : QDialog(parent), mapPage_(mapPage), coveragePathData_("") {
setWindowTitle("区域覆盖路径规划");
AreaCoverageDialog::AreaCoverageDialog(QWidget* parent, MapPage* mapPage, const QString& title) : QDialog(parent), mapPage_(mapPage), coveragePathData_(""), dialogTitle_(title) {
setWindowTitle(title);
setMinimumSize(700, 600);
auto* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(20, 20, 20, 20);
@ -1801,11 +1767,16 @@ AreaCoverageDialog::AreaCoverageDialog(QWidget* parent, MapPage* mapPage) : QDia
onDroneCountChanged(droneCountSpin_->value());
// 威胁区域选择
auto* threatBox = new QGroupBox("选择威胁区域(用于覆盖规划)");
QString threatBoxTitle = dialogTitle_.contains("任务决策") ? "选择搜索区域(用于覆盖规划)" : "选择威胁区域(用于覆盖规划)";
auto* threatBox = new QGroupBox(threatBoxTitle);
auto* threatLayout = new QVBoxLayout(threatBox);
threatSelectTable_ = new QTableWidget();
threatSelectTable_->setColumnCount(4);
threatSelectTable_->setHorizontalHeaderLabels({"选择", "类型", "形状", "详情"});
QStringList headers = {"选择", "类型", "形状", "详情"};
if (dialogTitle_.contains("任务决策")) {
headers[1] = "区域类型";
}
threatSelectTable_->setHorizontalHeaderLabels(headers);
threatSelectTable_->setSelectionMode(QAbstractItemView::NoSelection);
threatSelectTable_->setEditTriggers(QAbstractItemView::NoEditTriggers);
threatLayout->addWidget(threatSelectTable_);
@ -1940,7 +1911,8 @@ void AreaCoverageDialog::planCoveragePath() {
}
}
if (selectedIdx.isEmpty()) {
QMessageBox::warning(this, "提示", "请至少选择一个威胁区域");
QString warningMsg = dialogTitle_.contains("任务决策") ? "请至少选择一个搜索区域" : "请至少选择一个威胁区域";
QMessageBox::warning(this, "提示", warningMsg);
return;
}
QList<QVariantMap> chosen;
@ -2082,7 +2054,7 @@ void AreaCoverageDialog::planCoveragePath() {
}
multiJson += "]";
// 可视化多路径
// 可视化多路径使用visualizeCoverageMultiPaths
mapPage_->visualizeCoverageMultiPaths(multiJson);
// 计算统计信息
@ -2158,7 +2130,8 @@ void AreaCoverageDialog::planCoveragePath() {
statsLines.append("<b>=== 总体统计 ===</b><br>");
statsLines.append(QString("<b>总飞行距离:</b> %1 米<br>").arg(int(totalDistance)));
statsLines.append(QString("<b>总覆盖面积:</b> %1 平方米<br>").arg(int(totalCoverageArea)));
statsLines.append(QString("<b>威胁区域面积:</b> %1 平方米<br>").arg(int(threatAreaSize)));
QString areaLabel = dialogTitle_.contains("任务决策") ? "搜索区域面积" : "威胁区域面积";
statsLines.append(QString("<b>%1:</b> %2 平方米<br>").arg(areaLabel).arg(int(threatAreaSize)));
statsLines.append(QString("<b>覆盖率:</b> %1%<br>").arg(QString::number(coverageRate, 'f', 1)));
statsLines.append(QString("<b>最长飞行时间:</b> %1 分钟<br>").arg(QString::number(maxFlightTime, 'f', 1)));
@ -2189,7 +2162,10 @@ void AreaCoverageDialog::planCoveragePath() {
coveragePathData_ = multiJson;
// 完成提示
QMessageBox::information(this, "成功", QString("威胁区域覆盖路径已生成(%1 架无人机,覆盖率 %2%").arg(droneCount).arg(QString::number(coverageRate, 'f', 1)));
QString successMsg = dialogTitle_.contains("任务决策") ?
QString("搜索区域覆盖路径已生成(%1 架无人机,覆盖率 %2%").arg(droneCount).arg(QString::number(coverageRate, 'f', 1)) :
QString("威胁区域覆盖路径已生成(%1 架无人机,覆盖率 %2%").arg(droneCount).arg(QString::number(coverageRate, 'f', 1));
QMessageBox::information(this, "成功", successMsg);
return;
}
}
@ -2295,7 +2271,8 @@ void AreaCoverageDialog::onSelectNoneThreats() {
void AreaCoverageDialog::onRefreshThreats() {
populateThreatList();
QMessageBox::information(this, "成功", "威胁区域列表已刷新");
QString refreshMsg = dialogTitle_.contains("任务决策") ? "搜索区域列表已刷新" : "威胁区域列表已刷新";
QMessageBox::information(this, "成功", refreshMsg);
}
void AreaCoverageDialog::onPathDroneChanged(int index) {
@ -2379,9 +2356,6 @@ void AreaCoverageDialog::onPlayAnimation() {
)");
mapPage_->runMapJavaScript(animationJs);
// 自动关闭对话框,便于观看动画
this->hide();
}
void AreaCoverageDialog::onStopAnimation() {
@ -2601,18 +2575,71 @@ void MapPage::onMapReadyFromJS() {
emit mapReady();
}
void MapPage::setThreatButtonText(const QString& text) {
if (setThreatBtn_) {
setThreatBtn_->setText(text);
}
}
void MapPage::loadSavedPath() {
QFile file("task_path.json");
if (file.open(QIODevice::ReadOnly)) {
QString pathData = file.readAll();
file.close();
if (!pathData.isEmpty()) {
visualizePath(pathData);
QMessageBox::information(this, "成功", "已加载并展示保存的路径");
} else {
QMessageBox::warning(this, "错误", "保存路径为空");
}
} else {
QMessageBox::warning(this, "错误", "无法打开保存文件");
QString fileName = QFileDialog::getOpenFileName(this, "打开保存路径", "", "JSON Files (*.json)");
if (fileName.isEmpty()) return;
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, "错误", "无法打开文件: " + fileName);
return;
}
QString pathData = file.readAll();
file.close();
// 验证JSON
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(pathData.toUtf8(), &err);
if (err.error != QJsonParseError::NoError || !doc.isArray()) {
QMessageBox::warning(this, "错误", "无效的JSON格式: " + err.errorString());
return;
}
visualizePath(pathData, true); // 加载时清除旧路径
QMessageBox::information(this, "成功", "已加载并展示路径: " + fileName);
}
// Add the new function implementation here
void MapPage::visualizeCoverageMultiPaths(const QString& multiPathsJson) {
QString js = QString(R"(
if (window.map) {
var items = JSON.parse('%1');
// 清理旧覆盖
if (!window.multiPolylines) window.multiPolylines = [];
if (!window.multiCircles) window.multiCircles = [];
for (var i=0;i<window.multiPolylines.length;i++){ if (window.multiPolylines[i]) window.map.remove(window.multiPolylines[i]); }
for (var i=0;i<window.multiCircles.length;i++){ if (window.multiCircles[i]) window.map.remove(window.multiCircles[i]); }
window.multiPolylines = [];
window.multiCircles = [];
var all = [];
for (var k=0;k<items.length;k++){
var pathCol = items[k].pathColor || '#1E88E5';
var circleCol = items[k].circleColor || '#1E88E5';
var path = items[k].path || [];
var radius = items[k].radius || 100.0;
if (path.length > 0){
var poly = new AMap.Polyline({path: path, strokeColor: pathCol, strokeOpacity: 0.9, strokeWeight: 4});
poly.setMap(window.map);
window.multiPolylines.push(poly);
all.push(poly);
// 每隔若干点画覆盖圆
for (var j=0;j<path.length;j+=Math.max(1, Math.floor(path.length/20))){
var c = new AMap.Circle({center: new AMap.LngLat(path[j][0], path[j][1]), radius: radius, fillColor: circleCol, fillOpacity: 0.12, strokeColor: circleCol, strokeWeight: 1});
c.setMap(window.map);
window.multiCircles.push(c);
all.push(c);
}
}
}
if (all.length>0) window.map.setFitView(all, {padding:[40,40,40,40]});
}
)").arg(multiPathsJson);
runMapJavaScript(js);
}

@ -57,7 +57,7 @@ class AreaSearchDialog;
class ThreatAreaDialog : public QDialog {
Q_OBJECT
public:
ThreatAreaDialog(QWidget* parent = nullptr, MapPage* mapPage = nullptr);
ThreatAreaDialog(QWidget* parent = nullptr, MapPage* mapPage = nullptr, const QString& title = "设置威胁区域");
private slots:
void addArea();
void updateThreatStats();
@ -128,7 +128,7 @@ private:
class AreaCoverageDialog : public QDialog {
Q_OBJECT
public:
AreaCoverageDialog(QWidget* parent = nullptr, MapPage* mapPage = nullptr);
AreaCoverageDialog(QWidget* parent = nullptr, MapPage* mapPage = nullptr, const QString& title = "区域覆盖规划");
QString getCoveragePathData() const { return coveragePathData_; }
private slots:
void planCoveragePath();
@ -144,6 +144,7 @@ private slots:
void onResetAnimation();
private:
void populateThreatList();
QString dialogTitle_;
QSpinBox* droneCountSpin_;
QTableWidget* droneParamsTable_;
QTableWidget* threatSelectTable_;
@ -206,6 +207,9 @@ public:
QComboBox* getHeightCombo() const { return heightCombo_; }
QPushButton* getDownloadMapBtn() const { return downloadMapBtn_; }
// 设置威胁区域按钮文本的方法
void setThreatButtonText(const QString& text);
signals:
void heightChanged(const QString& height);
@ -235,14 +239,14 @@ public slots:
void addClickListener();
void removeClickListener();
void handleMapClick(double lng, double lat);
void visualizePath(const QString& pathData);
void visualizeCoverageMultiPaths(const QString& multiPathsJson);
void visualizePath(const QString& pathData, bool clearOld = false);
void clearPathOverlays();
void runMapJavaScript(const QString& js);
double parseLng(const QString& coord) const;
double parseLat(const QString& coord) const;
void showMarker(double lng, double lat, const QString& label, const QString& color, int index);
void visualizeCoveragePath(const QString& pathData);
void visualizeCoverageMultiPaths(const QString& multiPathsJson);
void clearCoverageOverlays();
void visualizeCoverageAreaCircle(double centerLng, double centerLat, double radiusKm);
void visualizeCoverageAreaPolygon(const QList<QPair<double,double>>& vertices);

@ -3,18 +3,12 @@
#include "mappage.h" // For PathPlanningDialog and MapPage
#include <QFile> // Added for file operations
#include <QDateTime> // Added for saving paths
#include <QHeaderView>
#include <QMenu>
#include <QInputDialog>
#include <QDialog>
#include <QFormLayout>
#include <QDialogButtonBox>
#include <QComboBox>
#include <QSpinBox>
#include <QLineEdit>
#include <QTextEdit>
#include <QGroupBox>
#include <QCheckBox>
#include <QJsonDocument> // Added for JSON formatting
#include <QJsonObject> // Added for QJsonObject
#include <QJsonArray> // Added for QJsonArray
#include <QDialogButtonBox> // Added for dialog buttons
#include <QDir> // Added for QDir
#include <cmath> // Added for math functions (sin, cos, sqrt, atan2, M_PI)
TaskDecisionPage::TaskDecisionPage(QWidget* parent)
: QWidget(parent)
@ -38,19 +32,33 @@ TaskDecisionPage::TaskDecisionPage(QWidget* parent)
connect(targetManager_, &TargetManager::targetStatusChanged, this, &TaskDecisionPage::updateTargetTable);
connect(targetManager_, &TargetManager::targetPriorityChanged, this, &TaskDecisionPage::updateTargetTable);
connect(targetManager_, &TargetManager::targetStrikeMethodChanged, this, &TaskDecisionPage::updateTargetTable);
taskData_ = new TaskDataObject();
// Remove: QMessageBox and initTaskData
}
void TaskDecisionPage::setupUI() {
auto* mainLayout = new QHBoxLayout(this);
auto* mainLayout = new QVBoxLayout(this); // Change to VBox for stacking
mainLayout->setContentsMargins(10, 10, 10, 10);
stack_ = new QStackedWidget();
mainLayout->addWidget(stack_);
// Page 0: Welcome page with new task button
stack_->addWidget(createWelcomeWidget());
// Page 1: Main decision UI
QWidget* decisionWidget = new QWidget();
auto* decisionLayout = new QHBoxLayout(decisionWidget);
// Add existing UI to decisionLayout
// 左侧步骤导航
stepList_ = new QListWidget();
stepList_->setFixedWidth(200);
stepList_->addItems({"1. 目标位置确认", "2. 路径规划", "3. 区域搜索策略", "4. 打击目标清单", "5. 任务预览与生成"});
connect(stepList_, &QListWidget::currentRowChanged, this, &TaskDecisionPage::onStepChanged);
mainLayout->addWidget(stepList_);
decisionLayout->addWidget(stepList_);
// 中间内容区
auto* contentLayout = new QVBoxLayout();
contentStack_ = new QStackedWidget();
@ -60,7 +68,7 @@ void TaskDecisionPage::setupUI() {
contentStack_->addWidget(createStep4Widget());
contentStack_->addWidget(createStep5Widget());
contentLayout->addWidget(contentStack_, 1);
// 底部导航
auto* bottomLayout = new QHBoxLayout();
prevBtn_ = new QPushButton("上一步");
@ -75,10 +83,12 @@ void TaskDecisionPage::setupUI() {
bottomLayout->addStretch(1);
bottomLayout->addWidget(generateBtn_);
contentLayout->addLayout(bottomLayout);
mainLayout->addLayout(contentLayout, 1);
decisionLayout->addLayout(contentLayout, 1);
stack_->addWidget(decisionWidget);
// 初始化
stack_->setCurrentIndex(0); // Start with welcome page
onStepChanged(0);
}
@ -134,16 +144,13 @@ QWidget* TaskDecisionPage::createStep2Widget() {
MapPage* previewMap = new MapPage();
previewMap->setWindowTitle("路径预览");
previewMap->resize(800, 600);
// 等待页面加载完成后可视化路径
connect(previewMap->getMapView()->page(), &QWebEnginePage::loadFinished, [previewMap, this](bool ok){
if (ok) {
previewMap->visualizePath(pathData_);
previewMap->visualizePath(pathData_, true); // 清除旧路径
} else {
QMessageBox::warning(this, "错误", "地图加载失败,无法预览路径");
}
});
previewMap->show();
}
});
@ -170,7 +177,7 @@ QWidget* TaskDecisionPage::createStep2Widget() {
histPreview->setWindowTitle("历史路径预览");
histPreview->resize(800, 600);
connect(histPreview->getMapView()->page(), &QWebEnginePage::loadFinished, [histPreview, histPath](bool ok){
if (ok) histPreview->visualizePath(histPath);
if (ok) histPreview->visualizePath(histPath, true);
});
histPreview->show();
}
@ -185,26 +192,22 @@ QWidget* TaskDecisionPage::createStep3Widget() {
layout->addWidget(new QLabel("步骤3: 规划区域搜索策略"));
areaSize_ = new QLineEdit();
areaSize_->setPlaceholderText("区域大小/形状 (e.g., 圆形,半径5km)");
layout->addWidget(areaSize_);
droneCount_ = new QSpinBox();
droneCount_->setRange(1, 10);
layout->addWidget(droneCount_);
auto* recBtn = new QPushButton("推荐无人机数量");
connect(recBtn, &QPushButton::clicked, this, &TaskDecisionPage::onRecommendDrones);
layout->addWidget(recBtn);
auto* replanBtn = new QPushButton("重新规划");
connect(replanBtn, &QPushButton::clicked, this, &TaskDecisionPage::onReplanSearch);
layout->addWidget(replanBtn);
// 新增区域覆盖规划按钮
auto* areaCoverageBtn = new QPushButton("区域覆盖规划");
areaCoverageBtn->setProperty("primary", true);
connect(areaCoverageBtn, &QPushButton::clicked, this, &TaskDecisionPage::onAreaCoveragePlanning);
layout->addWidget(areaCoverageBtn);
searchTable_ = new QTableWidget(0, 4);
searchTable_->setHorizontalHeaderLabels({"无人机ID", "搜索路径", "时间", "范围占比"});
searchTable_->setHorizontalHeaderLabels({"无人机ID", "搜索路径", "时间", "覆盖率"});
layout->addWidget(searchTable_);
// 新增保存数据按钮
auto* saveDataBtn = new QPushButton("保存数据");
saveDataBtn->setProperty("secondary", true);
connect(saveDataBtn, &QPushButton::clicked, this, &TaskDecisionPage::onSaveSearchData);
layout->addWidget(saveDataBtn);
searchTime_ = new QLabel("预计搜索时间: N/A");
coverage_ = new QLabel("覆盖范围: N/A");
layout->addWidget(searchTime_);
@ -286,6 +289,7 @@ void TaskDecisionPage::onStepChanged(int step) {
prevBtn_->setEnabled(step > 0);
nextBtn_->setEnabled(step < 4);
generateBtn_->setEnabled(step == 4);
generateBtn_->setVisible(step == 4); // Show only in last step
// 调试信息
qDebug() << "Step changed to:" << step << "Stack index:" << contentStack_->currentIndex();
@ -342,25 +346,191 @@ void TaskDecisionPage::onGeneratePath() {
});
}
void TaskDecisionPage::onRecommendDrones() {
// 模拟推荐(基于区域大小)
droneCount_->setValue(3); // 示例
QMessageBox::information(this, "推荐", "推荐使用3架无人机");
void TaskDecisionPage::onAreaCoveragePlanning() {
// 创建独立的地图画布窗口
MapPage* coverageMap = new MapPage(this);
coverageMap->setWindowTitle("区域覆盖规划 - 任务决策");
coverageMap->resize(1200, 800);
// 设置威胁区域按钮文本为"设置搜索区域"
coverageMap->setThreatButtonText("设置搜索区域");
coverageMap->show();
// 等待地图加载完成后打开区域覆盖规划对话框
connect(coverageMap->getMapView()->page(), &QWebEnginePage::loadFinished, [this, coverageMap](bool ok) {
if (ok) {
// 创建区域覆盖规划对话框
AreaCoverageDialog* coverageDialog = new AreaCoverageDialog(this, coverageMap, "区域覆盖规划 - 任务决策");
coverageDialog->show();
// 连接对话框关闭信号,清理资源
connect(coverageDialog, &QDialog::finished, [this, coverageMap, coverageDialog](int result) {
if (result == QDialog::Accepted) {
// 获取覆盖路径数据
QString coveragePathData = coverageDialog->getCoveragePathData();
if (!coveragePathData.isEmpty()) {
// 更新搜索数据
searchData_ = coveragePathData;
// 解析覆盖路径数据并更新搜索表格
updateSearchTableFromCoverageData(coveragePathData);
QMessageBox::information(coverageDialog, "成功", "区域覆盖规划已完成!");
}
}
coverageMap->close();
coverageMap->deleteLater();
coverageDialog->deleteLater();
});
}
});
}
void TaskDecisionPage::onSaveSearchData() {
// 检查是否有搜索数据
if (searchData_.isEmpty()) {
QMessageBox::warning(this, "警告", "没有可保存的搜索数据!请先进行区域覆盖规划。");
return;
}
// 创建data_area目录相对于项目根目录
QString projectRoot = QDir::currentPath();
QDir dataDir(projectRoot + "/Src/data_area");
if (!dataDir.exists()) {
if (!dataDir.mkpath(".")) {
QMessageBox::critical(this, "错误", "无法创建data_area目录");
return;
}
}
// 生成文件名(包含时间戳)
QDateTime currentTime = QDateTime::currentDateTime();
QString fileName = QString("search_data_%1.json").arg(currentTime.toString("yyyyMMdd_hhmmss"));
QString filePath = dataDir.absoluteFilePath(fileName);
// 准备保存的数据
QJsonObject searchDataObj;
searchDataObj["timestamp"] = currentTime.toString("yyyy-MM-dd hh:mm:ss");
searchDataObj["coverage"] = coverage_->text();
searchDataObj["searchTime"] = searchTime_->text();
// 添加表格数据
QJsonArray tableData;
for (int i = 0; i < searchTable_->rowCount(); ++i) {
QJsonObject rowData;
rowData["droneId"] = searchTable_->item(i, 0) ? searchTable_->item(i, 0)->text() : "";
rowData["path"] = searchTable_->item(i, 1) ? searchTable_->item(i, 1)->text() : "";
rowData["time"] = searchTable_->item(i, 2) ? searchTable_->item(i, 2)->text() : "";
rowData["coverage"] = searchTable_->item(i, 3) ? searchTable_->item(i, 3)->text() : "";
tableData.append(rowData);
}
searchDataObj["droneCount"] = tableData.size(); // Use table data size as drone count
searchDataObj["tableData"] = tableData;
// 保存到文件
QJsonDocument doc(searchDataObj);
QFile file(filePath);
if (file.open(QIODevice::WriteOnly)) {
file.write(doc.toJson());
file.close();
QMessageBox::information(this, "成功", QString("搜索数据已保存到:\n%1").arg(filePath));
} else {
QMessageBox::critical(this, "错误", "无法保存文件!");
}
}
void TaskDecisionPage::onReplanSearch() {
// 模拟规划
searchTable_->setRowCount(droneCount_->value());
for (int i = 0; i < droneCount_->value(); ++i) {
void TaskDecisionPage::updateSearchTableFromCoverageData(const QString& coveragePathData) {
// 解析JSON格式的覆盖路径数据
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(coveragePathData.toUtf8(), &error);
if (error.error != QJsonParseError::NoError) {
qDebug() << "JSON解析错误:" << error.errorString();
return;
}
QJsonArray droneArray = doc.array();
int droneCount = droneArray.size();
// 更新搜索表格
if (searchTable_ != nullptr) {
searchTable_->setRowCount(droneCount);
} else {
qDebug() << "searchTable_ is null, cannot update.";
}
double totalDistance = 0.0;
double totalCoverageArea = 0.0;
double maxFlightTime = 0.0;
QList<double> droneCoverageAreas; // 存储每台无人机的覆盖面积
for (int i = 0; i < droneCount; ++i) {
QJsonObject droneObj = droneArray[i].toObject();
QJsonArray pathArray = droneObj["path"].toArray();
// 无人机ID
searchTable_->setItem(i, 0, new QTableWidgetItem(QString("Drone %1").arg(i+1)));
searchTable_->setItem(i, 1, new QTableWidgetItem("路径描述"));
searchTable_->setItem(i, 2, new QTableWidgetItem("5min"));
searchTable_->setItem(i, 3, new QTableWidgetItem("30%"));
// 搜索路径描述
QString pathDesc = QString("覆盖路径 (%1个点)").arg(pathArray.size());
searchTable_->setItem(i, 1, new QTableWidgetItem(pathDesc));
// 计算路径长度和飞行时间
double pathDistance = 0.0;
for (int j = 1; j < pathArray.size(); ++j) {
QJsonArray point1 = pathArray[j-1].toArray();
QJsonArray point2 = pathArray[j].toArray();
double lng1 = point1[0].toDouble();
double lat1 = point1[1].toDouble();
double lng2 = point2[0].toDouble();
double lat2 = point2[1].toDouble();
// 使用Haversine公式计算距离
double dLat = (lat2 - lat1) * M_PI / 180.0;
double dLng = (lng2 - lng1) * M_PI / 180.0;
double a = sin(dLat/2) * sin(dLat/2) + cos(lat1 * M_PI / 180.0) * cos(lat2 * M_PI / 180.0) * sin(dLng/2) * sin(dLng/2);
double c = 2 * atan2(sqrt(a), sqrt(1-a));
pathDistance += 6371000.0 * c; // 地球半径米
}
totalDistance += pathDistance;
// 假设飞行速度为15m/s
double flightTime = pathDistance / 15.0 / 60.0; // 转换为分钟
if (flightTime > maxFlightTime) maxFlightTime = flightTime;
searchTable_->setItem(i, 2, new QTableWidgetItem(QString("%1min").arg(QString::number(flightTime, 'f', 1))));
// 计算每台无人机的覆盖面积
double radius = droneObj["radius"].toDouble();
double droneCoverageArea = pathArray.size() * M_PI * radius * radius;
totalCoverageArea += droneCoverageArea;
// 存储每台无人机的覆盖面积,用于后续计算相对覆盖率
droneCoverageAreas.append(droneCoverageArea);
}
searchTime_->setText("预计搜索时间: 15min");
coverage_->setText("覆盖范围: 90%");
searchData_ = "搜索策略生成";
QMessageBox::information(this, "规划", "搜索策略已重新规划!");
// 重新计算每台无人机的相对覆盖率使总和为100%
for (int i = 0; i < droneCount; ++i) {
double relativeCoveragePercent = (droneCoverageAreas[i] / totalCoverageArea) * 100.0;
searchTable_->setItem(i, 3, new QTableWidgetItem(QString("%1%").arg(QString::number(relativeCoveragePercent, 'f', 1))));
}
// 计算总覆盖率并更新统计信息
double totalCoveragePercent = 100.0;
double overlapAdjustment = 0.0; // 简单重叠调整例如假设10%重叠
for (int i = 0; i < droneCount; ++i) {
QString coverageStr = droneArray[i].toObject()["coverage"].toString();
double droneCoverage = coverageStr.remove("%").toDouble();
totalCoveragePercent += droneCoverage;
if (i > 0) overlapAdjustment += droneCoverage * 0.1; // 假设每对无人机重叠10%
}
totalCoveragePercent -= overlapAdjustment;
totalCoveragePercent = std::max(0.0, std::min(100.0, totalCoveragePercent));
searchTime_->setText(QString("预计搜索时间: %1min").arg(QString::number(maxFlightTime, 'f', 1)));
coverage_->setText(QString("覆盖范围: %1%").arg(QString::number(totalCoveragePercent, 'f', 1)));
}
void TaskDecisionPage::onAddTargetType() {
@ -369,36 +539,40 @@ void TaskDecisionPage::onAddTargetType() {
}
void TaskDecisionPage::onGenerateTask() {
// 汇总数据生成预览
// Validation
if (pathData_.isEmpty() && searchData_.isEmpty() && targetTable_->rowCount() == 0) {
QMessageBox::warning(this, "警告", "所有数据为空,无法生成任务包。请完成至少一个步骤。");
return;
}
// 汇总数据生成预览(模拟)
QString preview = "任务摘要:\n";
preview += QString("目标明确: %1\n").arg(isTargetClear_ ? "" : "");
preview += "路径: " + pathData_ + "\n";
if (!isTargetClear_) preview += "搜索策略: " + searchData_ + "\n";
preview += "打击清单:\n";
auto targets = targetManager_->getTargetsSortedByPriority();
for (auto* target : targets) {
preview += QString("- %1 (%2) - 优先级: %3, 打击方式: %4, 状态: %5\n")
.arg(target->getName())
.arg(target->getTypeString())
.arg(target->getPriority())
.arg(target->getStrikeMethodString())
.arg(target->getStatusString());
for (int i = 0; i < targetTable_->rowCount(); ++i) {
auto* type = qobject_cast<QComboBox*>(targetTable_->cellWidget(i, 0));
auto* pri = qobject_cast<QSpinBox*>(targetTable_->cellWidget(i, 1));
auto* way = qobject_cast<QComboBox*>(targetTable_->cellWidget(i, 2));
preview += QString("%1 (优先级 %2, 方式 %3)\n").arg(type->currentText()).arg(pri->value()).arg(way->currentText());
}
preview += QString("\n统计信息:\n");
preview += QString("目标总数: %1\n").arg(targetManager_->getTargetCount());
preview += QString("高优先级目标: %1\n").arg(targetManager_->getHighPriorityTargets().size());
taskPreview_->setText(preview);
// 生成任务包模拟保存JSON
QFile file("task_path.json");
if (file.open(QIODevice::WriteOnly)) {
file.write(pathData_.toUtf8());
file.close();
// 生成任务包保存到data_paths目录
if (!pathData_.isEmpty()) {
QDir dataDir;
if (!dataDir.exists("Src/data_paths")) {
dataDir.mkdir("Src/data_paths");
}
QFile file("Src/data_paths/task_path.json");
if (file.open(QIODevice::WriteOnly)) {
file.write(pathData_.toUtf8());
file.close();
}
}
QMessageBox::information(this, "生成", "任务数据包已生成路径保存到task_path.json");
saveTaskData();
QMessageBox::information(this, "生成", "任务数据包已生成!路径保存到 Src/data_paths/task_path.json");
}
void TaskDecisionPage::onNextStep() {
@ -434,11 +608,21 @@ void TaskDecisionPage::saveCurrentPath() {
QMessageBox::warning(this, "错误", "无当前路径可保存");
return;
}
QFile file(QString("saved_path_%1.json").arg(QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss")));
// 确保data_paths目录存在
QDir dataDir;
if (!dataDir.exists("Src/data_paths")) {
dataDir.mkdir("Src/data_paths");
}
QJsonDocument doc = QJsonDocument::fromJson(pathData_.toUtf8());
QString formatted = doc.toJson(QJsonDocument::Indented); // 添加换行/缩进
QString fileName = QString("Src/data_paths/saved_path_%1.json").arg(QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss"));
QFile file(fileName);
if (file.open(QIODevice::WriteOnly)) {
file.write(pathData_.toUtf8());
file.write(formatted.toUtf8());
file.close();
QMessageBox::information(this, "成功", "当前路径已保存到文件");
QMessageBox::information(this, "成功", QString("当前路径已保存到: %1").arg(fileName));
} else {
QMessageBox::warning(this, "错误", "保存失败");
}
@ -868,3 +1052,62 @@ void TaskDecisionPage::showBatchUpdateDialog() {
dialog.exec();
}
void TaskDecisionPage::initTaskData(bool isNew) {
if (isNew) {
taskData_->pathData = "";
taskData_->searchData = "";
taskData_->strikeList = QJsonArray();
} else {
// Optionally load from file, but for now, assume new
initTaskData(true);
}
}
void TaskDecisionPage::saveTaskData() {
// Update strikeList from table
taskData_->strikeList = QJsonArray();
for (int i = 0; i < targetTable_->rowCount(); ++i) {
QJsonObject item;
item["type"] = qobject_cast<QComboBox*>(targetTable_->cellWidget(i, 0))->currentText();
item["priority"] = qobject_cast<QSpinBox*>(targetTable_->cellWidget(i, 1))->value();
item["method"] = qobject_cast<QComboBox*>(targetTable_->cellWidget(i, 2))->currentText();
taskData_->strikeList.append(item);
}
// Save to file
QJsonDocument doc(taskData_->toJson());
QFile file("task_data.json");
if (file.open(QIODevice::WriteOnly)) {
file.write(doc.toJson());
file.close();
}
}
void TaskDecisionPage::viewTaskData() {
QJsonDocument doc(taskData_->toJson());
QMessageBox::information(this, "任务数据", doc.toJson(QJsonDocument::Indented));
}
void TaskDecisionPage::onNewTaskClicked() {
QMessageBox::StandardButton reply = QMessageBox::question(this, "新建任务", "是否新建一个任务?", QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
initTaskData(true);
stack_->setCurrentIndex(1); // Switch to main decision page
onStepChanged(0); // Proceed to step 1
}
}
QWidget* TaskDecisionPage::createWelcomeWidget() {
QWidget* welcome = new QWidget();
auto* layout = new QVBoxLayout(welcome);
layout->setAlignment(Qt::AlignCenter);
QLabel* label = new QLabel("欢迎进入任务决策页面");
layout->addWidget(label);
newTaskBtn_ = new QPushButton("新建任务");
connect(newTaskBtn_, &QPushButton::clicked, this, &TaskDecisionPage::onNewTaskClicked);
layout->addWidget(newTaskBtn_);
return welcome;
}

@ -23,6 +23,29 @@
#include <QInputDialog>
#include <QMessageBox>
#include "../models/targetdata.h"
#include <QJsonObject>
#include <QJsonArray>
class TaskDataObject {
public:
QString pathData;
QString searchData;
QJsonArray strikeList; // Array of objects: {type, priority, method}
QJsonObject toJson() const {
QJsonObject obj;
obj["pathData"] = pathData;
obj["searchData"] = searchData;
obj["strikeList"] = strikeList;
return obj;
}
void fromJson(const QJsonObject& obj) {
pathData = obj["pathData"].toString();
searchData = obj["searchData"].toString();
strikeList = obj["strikeList"].toArray();
}
};
class TaskDecisionPage : public QWidget {
Q_OBJECT
@ -34,8 +57,8 @@ private slots:
void onStepChanged(int step);
void onTargetClearChanged(bool clear);
void onGeneratePath();
void onRecommendDrones();
void onReplanSearch();
void onAreaCoveragePlanning();
void onSaveSearchData();
void onAddTargetType();
void onGenerateTask();
void onNextStep();
@ -53,8 +76,10 @@ private slots:
void onBatchUpdatePriority();
void onBatchUpdateStrikeMethod();
void onClearAllTargets();
void onNewTaskClicked(); // New slot for new task confirmation
private:
QWidget* createWelcomeWidget();
void setupUI();
QWidget* createStep1Widget(); // 目标位置确认
QWidget* createStep2Widget(); // 路径规划
@ -73,6 +98,10 @@ private:
void updateTargetInTable(TargetData* target);
void showTargetEditDialog(TargetData* target = nullptr);
void showBatchUpdateDialog();
void updateSearchTableFromCoverageData(const QString& coveragePathData);
void initTaskData(bool isNew);
void saveTaskData();
void viewTaskData();
// 成员变量
QStackedWidget* contentStack_;
@ -82,6 +111,8 @@ private:
QPushButton* generateBtn_;
QPushButton* previewBtn_;
QListWidget* historyList_; // 新增:历史路径列表
QPushButton* newTaskBtn_; // New button for new task
QStackedWidget* stack_; // To switch between welcome and main page
// 步骤1
QRadioButton* clearYes_;
@ -94,7 +125,6 @@ private:
QTextEdit* pathResult_;
// 步骤3
QLineEdit* areaSize_;
QSpinBox* droneCount_;
QTableWidget* searchTable_;
QLabel* searchTime_;
@ -125,6 +155,7 @@ private:
// 目标管理
TargetManager* targetManager_;
QMap<QString, int> targetRowMap_; // 目标ID到表格行的映射
TaskDataObject* taskData_; // New member
};

@ -272,7 +272,7 @@ void VisionModelPage::runYOLOv5Detection(const QString& imagePath) {
progressTimer_->start(100);
// 构建Python命令
QString pythonScript = QDir::currentPath() + "/vision_models/services/yolov5_detection_service_simple.py";
QString pythonScript = "/home/linchengfu/桌面/project/Drone_project/Src/vision_models/services/yolov5_detection_service_simple.py";
QStringList arguments;
arguments << pythonScript << "--image" << imagePath;

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save