11#include " application/LayoutNavigationPanelWidget.h"
22
3+ #include < algorithm>
4+ #include < cmath>
5+ #include < limits>
36#include < utility>
47#include < vector>
58
1013namespace safecrowd ::application {
1114namespace {
1215
16+ constexpr double kGeometryEpsilon = 1e-4 ;
17+
18+ QString floorActionId (const std::string& floorId) {
19+ return QString (" floor:%1" ).arg (QString::fromStdString (floorId));
20+ }
21+
22+ bool matchesFloor (const std::string& elementFloorId, const std::string& floorId) {
23+ return floorId.empty () || elementFloorId.empty () || elementFloorId == floorId;
24+ }
25+
1326QString zoneLabel (const safecrowd::domain::Zone2D& zone) {
1427 const auto id = QString::fromStdString (zone.id );
1528 const auto label = QString::fromStdString (zone.label );
@@ -24,75 +37,166 @@ NavigationTreeNode makeZoneNode(const safecrowd::domain::Zone2D& zone) {
2437 };
2538}
2639
27- NavigationTreeNode makeSection (const QString& label, std::vector<NavigationTreeNode> children) {
40+ QString floorLabel (const safecrowd::domain::Floor2D& floor) {
41+ const auto id = QString::fromStdString (floor.id );
42+ const auto label = QString::fromStdString (floor.label );
43+ return label.isEmpty () || label == id ? id : QString (" %1 - %2" ).arg (label, id);
44+ }
45+
46+ QString connectionLabel (const safecrowd::domain::Connection2D& connection) {
47+ const auto id = QString::fromStdString (connection.id );
48+ return id.isEmpty ()
49+ ? QString (" %1 -> %2" ).arg (QString::fromStdString (connection.fromZoneId ), QString::fromStdString (connection.toZoneId ))
50+ : id;
51+ }
52+
53+ QString barrierLabel (const safecrowd::domain::Barrier2D& barrier) {
54+ return QString::fromStdString (barrier.id );
55+ }
56+
57+ NavigationTreeNode makeSection (const QString& label, std::vector<NavigationTreeNode> children, const QString& id = {}) {
2858 return {
2959 .label = label,
60+ .id = id,
3061 .children = std::move (children),
3162 .expanded = true ,
32- .selectable = false ,
63+ .selectable = !id. isEmpty () ,
3364 };
3465}
3566
36- template <typename Predicate>
37- std::vector<NavigationTreeNode> collectZones (
38- const safecrowd::domain::FacilityLayout2D& layout,
39- Predicate predicate) {
40- std::vector<NavigationTreeNode> nodes;
41- for (const auto & zone : layout.zones ) {
42- if (predicate (zone)) {
43- nodes.push_back (makeZoneNode (zone));
67+ double distancePointToSegment (
68+ const safecrowd::domain::Point2D& point,
69+ const safecrowd::domain::Point2D& start,
70+ const safecrowd::domain::Point2D& end) {
71+ const auto dx = end.x - start.x ;
72+ const auto dy = end.y - start.y ;
73+ const auto lengthSquared = (dx * dx) + (dy * dy);
74+ if (lengthSquared <= kGeometryEpsilon ) {
75+ return std::hypot (point.x - start.x , point.y - start.y );
76+ }
77+
78+ const auto t = std::clamp (
79+ (((point.x - start.x ) * dx) + ((point.y - start.y ) * dy)) / lengthSquared,
80+ 0.0 ,
81+ 1.0 );
82+ return std::hypot (point.x - (start.x + (dx * t)), point.y - (start.y + (dy * t)));
83+ }
84+
85+ double distanceToPolygonBoundary (
86+ const safecrowd::domain::Polygon2D& polygon,
87+ const safecrowd::domain::Point2D& point) {
88+ double best = std::numeric_limits<double >::max ();
89+ const auto checkRing = [&](const std::vector<safecrowd::domain::Point2D>& ring) {
90+ if (ring.size () < 2 ) {
91+ return ;
92+ }
93+ for (std::size_t index = 0 ; index < ring.size (); ++index) {
94+ best = std::min (best, distancePointToSegment (point, ring[index], ring[(index + 1 ) % ring.size ()]));
4495 }
96+ };
97+
98+ checkRing (polygon.outline );
99+ for (const auto & hole : polygon.holes ) {
100+ checkRing (hole);
45101 }
46- return nodes ;
102+ return best ;
47103}
48104
49- std::vector<NavigationTreeNode> buildLayoutTree (const safecrowd::domain::FacilityLayout2D* facilityLayout) {
50- if (facilityLayout == nullptr ) {
51- return {};
105+ bool barrierTouchesZone (const safecrowd::domain::Barrier2D& barrier, const safecrowd::domain::Zone2D& zone) {
106+ for (const auto & point : barrier.geometry .vertices ) {
107+ if (distanceToPolygonBoundary (zone.area , point) <= 0.08 ) {
108+ return true ;
109+ }
52110 }
111+ return false ;
112+ }
53113
54- std::vector<NavigationTreeNode> nodes;
114+ bool isRoomLikeZone (const safecrowd::domain::Zone2D& zone) {
115+ return zone.kind == safecrowd::domain::ZoneKind::Room
116+ || zone.kind == safecrowd::domain::ZoneKind::Intersection
117+ || zone.kind == safecrowd::domain::ZoneKind::Unknown
118+ || zone.kind == safecrowd::domain::ZoneKind::Exit
119+ || zone.kind == safecrowd::domain::ZoneKind::Stair;
120+ }
121+
122+ std::vector<NavigationTreeNode> roomChildren (
123+ const safecrowd::domain::FacilityLayout2D& layout,
124+ const safecrowd::domain::Zone2D& room) {
125+ std::vector<NavigationTreeNode> children;
55126
56- auto rooms = collectZones (*facilityLayout, [](const auto & zone) {
57- return zone.kind == safecrowd::domain::ZoneKind::Room
58- || zone.kind == safecrowd::domain::ZoneKind::Intersection
59- || zone.kind == safecrowd::domain::ZoneKind::Unknown;
60- });
61- if (!rooms.empty ()) {
62- nodes.push_back (makeSection (" Rooms" , std::move (rooms)));
127+ for (const auto & barrier : layout.barriers ) {
128+ if (!matchesFloor (barrier.floorId , room.floorId ) || !barrierTouchesZone (barrier, room)) {
129+ continue ;
130+ }
131+ children.push_back ({
132+ .label = QString (" Wall - %1" ).arg (barrierLabel (barrier)),
133+ .id = QString::fromStdString (barrier.id ),
134+ .detail = QString (" Wall: %1" ).arg (QString::fromStdString (barrier.id )),
135+ });
63136 }
64137
65- auto exits = collectZones (*facilityLayout, [](const auto & zone) {
66- return zone.kind == safecrowd::domain::ZoneKind::Exit
67- || zone.kind == safecrowd::domain::ZoneKind::Stair;
68- });
69- if (!exits.empty ()) {
70- nodes.push_back (makeSection (" Exits" , std::move (exits)));
138+ for (const auto & connection : layout.connections ) {
139+ if (!matchesFloor (connection.floorId , room.floorId )
140+ || (connection.fromZoneId != room.id && connection.toZoneId != room.id )) {
141+ continue ;
142+ }
143+ children.push_back ({
144+ .label = QString (" Door - %1" ).arg (connectionLabel (connection)),
145+ .id = QString::fromStdString (connection.id ),
146+ .detail = QString (" Door: %1" ).arg (QString::fromStdString (connection.id )),
147+ });
71148 }
72149
73- if (!facilityLayout->connections .empty ()) {
74- std::vector<NavigationTreeNode> connections;
75- for (const auto & connection : facilityLayout->connections ) {
76- connections.push_back ({
77- .label = QString (" %1 -> %2" )
78- .arg (QString::fromStdString (connection.fromZoneId ), QString::fromStdString (connection.toZoneId )),
79- .id = QString::fromStdString (connection.id ),
80- .detail = QString (" Connection: %1" ).arg (QString::fromStdString (connection.id )),
81- });
150+ return children;
151+ }
152+
153+ std::vector<safecrowd::domain::Floor2D> layoutFloors (const safecrowd::domain::FacilityLayout2D& layout) {
154+ if (!layout.floors .empty ()) {
155+ return layout.floors ;
156+ }
157+
158+ std::vector<safecrowd::domain::Floor2D> floors;
159+ const auto fallback = layout.levelId .empty () ? std::string{" L1" } : layout.levelId ;
160+ floors.push_back ({.id = fallback, .label = fallback});
161+ return floors;
162+ }
163+
164+ std::vector<NavigationTreeNode> buildLayoutTree (const safecrowd::domain::FacilityLayout2D* facilityLayout) {
165+ if (facilityLayout == nullptr ) {
166+ return {};
167+ }
168+
169+ std::vector<NavigationTreeNode> nodes;
170+ for (const auto & floor : layoutFloors (*facilityLayout)) {
171+ std::vector<NavigationTreeNode> rooms;
172+ for (const auto & zone : facilityLayout->zones ) {
173+ if (!matchesFloor (zone.floorId , floor.id ) || !isRoomLikeZone (zone)) {
174+ continue ;
175+ }
176+ auto roomNode = makeZoneNode (zone);
177+ roomNode.children = roomChildren (*facilityLayout, zone);
178+ rooms.push_back (std::move (roomNode));
82179 }
83- nodes.push_back (makeSection (" Connections " , std::move (connections )));
180+ nodes.push_back (makeSection (floorLabel (floor) , std::move (rooms), floorActionId (floor. id )));
84181 }
85182
86- if (!facilityLayout->barriers .empty ()) {
87- std::vector<NavigationTreeNode> barriers;
88- for (const auto & barrier : facilityLayout->barriers ) {
89- barriers.push_back ({
90- .label = QString::fromStdString (barrier.id ),
91- .id = QString::fromStdString (barrier.id ),
92- .detail = QString (" Wall: %1" ).arg (QString::fromStdString (barrier.id )),
183+ if (nodes.empty ()) {
184+ std::vector<NavigationTreeNode> rooms;
185+ for (const auto & zone : facilityLayout->zones ) {
186+ if (isRoomLikeZone (zone)) {
187+ auto roomNode = makeZoneNode (zone);
188+ roomNode.children = roomChildren (*facilityLayout, zone);
189+ rooms.push_back (std::move (roomNode));
190+ }
191+ }
192+ if (!rooms.empty ()) {
193+ nodes.push_back ({
194+ .label = " Layout" ,
195+ .children = std::move (rooms),
196+ .expanded = true ,
197+ .selectable = false ,
93198 });
94199 }
95- nodes.push_back (makeSection (" Walls" , std::move (barriers)));
96200 }
97201
98202 return nodes;
0 commit comments