Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/application/ProjectPersistence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,54 @@ safecrowd::domain::OperationalEventDraft eventFromJson(const QJsonObject& object
};
}

QJsonObject routeGuidanceToJson(const safecrowd::domain::RouteGuidanceDraft& guidance) {
QJsonObject object;
object["id"] = QString::fromStdString(guidance.id);
object["startSeconds"] = guidance.startSeconds;
object["endSeconds"] = guidance.endSeconds;
QJsonArray periods;
for (const auto& period : guidance.periods) {
QJsonObject periodObject;
periodObject["startSeconds"] = period.startSeconds;
periodObject["endSeconds"] = period.endSeconds;
periods.append(periodObject);
}
object["periods"] = periods;
object["guidedExitZoneId"] = QString::fromStdString(guidance.guidedExitZoneId);
object["installConnectionId"] = QString::fromStdString(guidance.installConnectionId);
object["baseComplianceRate"] = guidance.baseComplianceRate;
object["guidanceStrength"] = guidance.guidanceStrength;
object["maxDetourMeters"] = guidance.maxDetourMeters;
return object;
}

safecrowd::domain::RouteGuidanceDraft routeGuidanceFromJson(const QJsonObject& object) {
safecrowd::domain::RouteGuidanceDraft guidance;
guidance.id = object.value("id").toString().toStdString();
guidance.startSeconds = object.value("startSeconds").toDouble(0.0);
guidance.endSeconds = object.value("endSeconds").toDouble(10.0);
for (const auto& value : object.value("periods").toArray()) {
const auto periodObject = value.toObject();
guidance.periods.push_back({
.startSeconds = periodObject.value("startSeconds").toDouble(0.0),
.endSeconds = periodObject.value("endSeconds").toDouble(0.0),
});
}
if (guidance.periods.empty() && (object.contains("startSeconds") || object.contains("endSeconds"))) {
// Backward compatibility: older projects stored a single scalar period.
guidance.periods.push_back({
.startSeconds = guidance.startSeconds,
.endSeconds = guidance.endSeconds,
});
}
guidance.guidedExitZoneId = object.value("guidedExitZoneId").toString().toStdString();
guidance.installConnectionId = object.value("installConnectionId").toString().toStdString();
guidance.baseComplianceRate = object.value("baseComplianceRate").toDouble(0.5);
guidance.guidanceStrength = object.value("guidanceStrength").toDouble(0.55);
guidance.maxDetourMeters = object.value("maxDetourMeters").toDouble(20.0);
return guidance;
}

QJsonObject connectionBlockIntervalToJson(const safecrowd::domain::ConnectionBlockIntervalDraft& interval) {
QJsonObject object;
object["startSeconds"] = interval.startSeconds;
Expand Down Expand Up @@ -796,6 +844,12 @@ QJsonObject controlPlanToJson(const safecrowd::domain::ControlPlan& control) {
}
object["events"] = events;

QJsonArray routeGuidances;
for (const auto& guidance : control.routeGuidances) {
routeGuidances.append(routeGuidanceToJson(guidance));
}
object["routeGuidances"] = routeGuidances;

QJsonArray connectionBlocks;
for (const auto& block : control.connectionBlocks) {
connectionBlocks.append(connectionBlockToJson(block));
Expand All @@ -809,6 +863,9 @@ safecrowd::domain::ControlPlan controlPlanFromJson(const QJsonObject& object) {
for (const auto& value : object.value("events").toArray()) {
control.events.push_back(eventFromJson(value.toObject()));
}
for (const auto& value : object.value("routeGuidances").toArray()) {
control.routeGuidances.push_back(routeGuidanceFromJson(value.toObject()));
}
for (const auto& value : object.value("connectionBlocks").toArray()) {
control.connectionBlocks.push_back(connectionBlockFromJson(value.toObject()));
}
Expand Down
81 changes: 80 additions & 1 deletion src/application/ScenarioAuthoringWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,69 @@ std::vector<NavigationTreeNode> buildEventsTree(
});
}

const auto& routeGuidances = scenario->draft.control.routeGuidances;
if (!routeGuidances.empty()) {
std::vector<NavigationTreeNode> nodes;
nodes.reserve(routeGuidances.size());
for (const auto& guidance : routeGuidances) {
const auto guidanceId = QString::fromStdString(guidance.id);
const auto doorLabel = guidance.installConnectionId.empty()
? QString{}
: connectionLabelForId(layout, guidance.installConnectionId);
const auto exitLabel = guidance.guidedExitZoneId.empty()
? QStringLiteral("Nearest exit")
: zoneName(layout, guidance.guidedExitZoneId);
QString periodSummary = QStringLiteral("Always");
if (!guidance.periods.empty()) {
periodSummary = blockScheduleSummary(safecrowd::domain::ConnectionBlockDraft{
.intervals = [&]() {
std::vector<safecrowd::domain::ConnectionBlockIntervalDraft> intervals;
intervals.reserve(guidance.periods.size());
for (const auto& period : guidance.periods) {
intervals.push_back({
.startSeconds = std::max(0.0, period.startSeconds),
.endSeconds = std::max(0.0, period.endSeconds),
});
}
return intervals;
}(),
});
}

std::vector<NavigationTreeNode> children;
children.reserve(doorLabel.isEmpty() ? 2u : 3u);
children.push_back({
.label = QString("Exit - %1").arg(exitLabel),
.id = QString("%1/exit").arg(guidanceId),
});
if (!doorLabel.isEmpty()) {
children.push_back({
.label = QString("Door - %1").arg(doorLabel),
.id = QString("%1/door").arg(guidanceId),
});
}
children.push_back({
.label = QString("Period - %1").arg(periodSummary),
.id = QString("%1/period").arg(guidanceId),
});

nodes.push_back({
.label = QString("Guidance - %1").arg(doorLabel.isEmpty() ? exitLabel : doorLabel),
.id = guidanceId,
.detail = QString("Period: %1").arg(periodSummary),
.children = std::move(children),
.expanded = true,
});
}

sections.push_back({
.label = QString("Route Guidance (%1)").arg(static_cast<int>(routeGuidances.size())),
.children = std::move(nodes),
.expanded = true,
.selectable = false,
});
}

const auto& connectionBlocks = scenario->draft.control.connectionBlocks;
if (!connectionBlocks.empty()) {
std::vector<NavigationTreeNode> blocks;
Expand Down Expand Up @@ -558,6 +621,16 @@ void ScenarioAuthoringWidget::refreshCanvas() {
refreshNavigationPanel();
refreshInspector();
});
canvas_->setRouteGuidances(scenario->draft.control.routeGuidances);
canvas_->setRouteGuidancesChangedHandler([this](const std::vector<safecrowd::domain::RouteGuidanceDraft>& guidances) {
auto* current = currentScenario();
if (current == nullptr) {
return;
}
current->draft.control.routeGuidances = guidances;
refreshNavigationPanel();
refreshInspector();
});
if (!selectedLayoutElementId_.isEmpty()) {
canvas_->focusLayoutElement(selectedLayoutElementId_);
} else if (!selectedCrowdElementId_.isEmpty()) {
Expand All @@ -576,13 +649,15 @@ void ScenarioAuthoringWidget::refreshInspector() {
} else {
const int people = totalOccupantCount(*scenario);
const auto blockCount = static_cast<int>(scenario->draft.control.connectionBlocks.size());
scenarioSummaryLabel_->setText(QString("Name: %1\nRole: %2\nPopulation: %3\nStart: %4\nDestination: %5\nEvents: %6\nBlocked exits: %7")
const auto guidanceCount = static_cast<int>(scenario->draft.control.routeGuidances.size());
scenarioSummaryLabel_->setText(QString("Name: %1\nRole: %2\nPopulation: %3\nStart: %4\nDestination: %5\nEvents: %6\nRoute guidance: %7\nBlocked exits: %8")
.arg(
QString::fromStdString(scenario->draft.name),
scenario->draft.role == safecrowd::domain::ScenarioRole::Baseline ? "Baseline" : "Alternative")
.arg(people)
.arg(scenario->startText, scenario->destinationText)
.arg(static_cast<int>(scenario->events.size()))
.arg(guidanceCount)
.arg(blockCount));
}
}
Expand All @@ -599,6 +674,10 @@ void ScenarioAuthoringWidget::refreshInspector() {
changes << QString("Blocked exits: %1 configured")
.arg(static_cast<int>(scenario->draft.control.connectionBlocks.size()));
}
if (!scenario->draft.control.routeGuidances.empty()) {
changes << QString("Route guidance: %1 configured")
.arg(static_cast<int>(scenario->draft.control.routeGuidances.size()));
}
if (changes.isEmpty()) {
changes << "No changed fields yet";
}
Expand Down
Loading