FlightGear next
groundnetwork.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: groundnet.cxx
3 * SPDX-FileComment: Implementation of the FlightGear airport ground handling code
4 * SPDX-FileCopyrightText: Copyright (C) 2004 Durk Talsma
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include <config.h>
9
10#include <algorithm>
11#include <cmath>
12#include <fstream>
13#include <iterator>
14#include <map>
15
16#include <simgear/scene/util/OsgMath.hxx>
17#include <simgear/debug/logstream.hxx>
18#include <simgear/math/SGGeometryFwd.hxx>
19#include <simgear/math/SGIntersect.hxx>
20#include <simgear/math/SGLineSegment.hxx>
21#include <simgear/structure/exception.hxx>
22#include <simgear/timing/timestamp.hxx>
23
24#include <Airports/airport.hxx>
25#include <Airports/runways.hxx>
26#include <Scenery/scenery.hxx>
27
28#include "groundnetwork.hxx"
29
30using std::string;
31
32
33/***************************************************************************
34 * FGTaxiSegment
35 **************************************************************************/
36
37FGTaxiSegment::FGTaxiSegment(FGTaxiNode* aStart, FGTaxiNode* aEnd) : startNode(aStart),
38 endNode(aEnd),
39 isActive(0),
40 index(0),
41 oppositeDirection(0)
42{
43 if (!aStart || !aEnd) {
44 throw sg_exception("Missing node arguments creating FGTaxiSegment");
45 }
46}
47
49{
50 FGTaxiNode *start(getStart()), *end(getEnd());
51 double heading, length, az2;
52 SGGeodesy::inverse(start->geod(), end->geod(), heading, az2, length);
53 return SGGeodesy::direct(start->geod(), heading, length * 0.5);
54}
55
57{
58 return const_cast<FGTaxiNode*>(endNode);
59}
60
62{
63 return const_cast<FGTaxiNode*>(startNode);
64}
65
67{
68 return dist(getStart()->cart(), getEnd()->cart());
69}
70
72{
73 return SGGeodesy::courseDeg(getStart()->geod(), getEnd()->geod());
74}
75
76void FGTaxiSegment::block(int id, time_t blockTime, time_t now)
77{
78 BlockListIterator i = blockTimes.begin();
79 while (i != blockTimes.end()) {
80 if (i->getId() == id)
81 break;
82 i++;
83 }
84 if (i == blockTimes.end()) {
85 blockTimes.push_back(Block(id, blockTime, now));
86 sort(blockTimes.begin(), blockTimes.end());
87 } else {
88 i->updateTimeStamps(blockTime, now);
89 }
90}
91
92// The segment has a block if any of the block times listed in the block list is
93// smaller than the current time.
95{
96 for (BlockListIterator i = blockTimes.begin(); i != blockTimes.end(); i++) {
97 if (i->getBlockTime() < now)
98 return true;
99 }
100 return false;
101}
102
104{
105 if (blockTimes.empty())
106 return;
107
108 if (blockTimes.front().getTimeStamp() < (now - 30)) {
109 blockTimes.erase(blockTimes.begin());
110 }
111}
112
113/***************************************************************************
114 * FGTaxiRoute
115 **************************************************************************/
116
117FGTaxiRoute::FGTaxiRoute(const FGTaxiNodeVector& nds, const intVec& rts, double dist, int /* dpth */) : nodes(nds),
118 routes(rts),
119 distance(dist)
120{
121 currNode = nodes.begin();
122 currRoute = routes.begin();
123
124 if (nodes.size() != (routes.size()) + 1) {
125 SG_LOG(SG_GENERAL, SG_ALERT, "ALERT: Misconfigured TaxiRoute : " << nodes.size() << " " << routes.size());
126 }
127};
128
129bool FGTaxiRoute::next(FGTaxiNodeRef& node, int* rte)
130{
131 if (nodes.size() != (routes.size()) + 1) {
132 throw sg_range_exception("Misconfigured taxi route");
133 }
134
135 if (currNode == nodes.end())
136 return false;
137 node = *(currNode);
138 if (currNode != nodes.begin()) {
139 // work-around for FLIGHTGEAR-NJN: throw an exception here
140 // instead of crashing, to aid debugging
141 if (currRoute == routes.end()) {
142 throw sg_range_exception("Misconfigured taxi route");
143 }
144
145 *rte = *(currRoute);
146 ++currRoute;
147 } else {
148 // Handle special case for the first node.
149 *rte = -1 * *(currRoute);
150 }
151 ++currNode;
152 return true;
153};
154
155/***************************************************************************
156 * FGGroundNetwork()
157 **************************************************************************/
158
160{
161 hasNetwork = false;
162 version = 0;
163 networkInitialized = false;
164}
165
167{
168 for (auto seg : segments) {
169 delete seg;
170 }
171}
172
174{
175 if (networkInitialized) {
176 SG_LOG(SG_GENERAL, SG_WARN, "duplicate ground-network init");
177 return;
178 }
179
180 hasNetwork = true;
181 int index = 1;
182
183 // establish pairing of segments
184 for (auto segment : segments) {
185 segment->setIndex(index++);
186
187 if (segment->oppositeDirection) {
188 continue; // already established
189 }
190
191 FGTaxiSegment* opp = findSegment(segment->endNode, segment->startNode);
192 if (opp) {
193 assert(opp->oppositeDirection == NULL);
194 segment->oppositeDirection = opp;
195 opp->oppositeDirection = segment;
196 }
197
198 // establish node -> segment end cache
199 m_segmentsEndingAtNodeMap.insert(NodeFromSegmentMap::value_type{segment->getEnd(), segment});
200 }
201
202 networkInitialized = true;
203}
204
206{
207 double d = DBL_MAX;
208 SGVec3d cartPos = SGVec3d::fromGeod(aGeod);
209 FGTaxiNodeRef result;
210
211 FGTaxiNodeVector::const_iterator it;
212 for (it = m_nodes.begin(); it != m_nodes.end(); ++it) {
213 double localDistanceSqr = distSqr(cartPos, (*it)->cart());
214 if (localDistanceSqr < d) {
215 d = localDistanceSqr;
216 result = *it;
217 }
218 }
219
220 return result;
221}
222
223FGTaxiNodeRef FGGroundNetwork::findNearestNodeOffRunway(const SGGeod& aGeod, FGRunway* rwy, double marginM) const
224{
225 FGTaxiNodeVector nodes;
226 const SGLineSegmentd runwayLine(rwy->cart(), SGVec3d::fromGeod(rwy->end()));
227 const double marginMSqr = marginM * marginM;
228 const SGVec3d cartPos = SGVec3d::fromGeod(aGeod);
229
230 std::copy_if(m_nodes.begin(), m_nodes.end(), std::back_inserter(nodes),
231 [runwayLine, cartPos, marginMSqr](const FGTaxiNodeRef& a) {
232 if (a->getIsOnRunway()) return false;
233
234 // exclude parking positions from consideration. This helps to
235 // exclude airports whose ground nets only list parking positions,
236 // since these typically produce bad results. See discussion in
237 // https://sourceforge.net/p/flightgear/codetickets/2110/
238 if (a->type() == FGPositioned::PARKING) return false;
239
240 return (distSqr(runwayLine, a->cart()) >= marginMSqr);
241 });
242
243
244 // find closest of matching nodes
245 auto node = std::min_element(nodes.begin(), nodes.end(),
246 [cartPos](const FGTaxiNodeRef& a, const FGTaxiNodeRef& b) { return distSqr(cartPos, a->cart()) < distSqr(cartPos, b->cart()); });
247
248 if (node == nodes.end()) {
249 return FGTaxiNodeRef();
250 }
251
252 return *node;
253}
254
256{
257 double d = DBL_MAX;
258 SGVec3d cartPos = SGVec3d::fromGeod(aGeod);
259 FGTaxiNodeRef result;
260 for (auto it = m_nodes.begin(); it != m_nodes.end(); ++it) {
261 if (!(*it)->getIsOnRunway())
262 continue;
263 double localDistanceSqr = distSqr(cartPos, (*it)->cart());
264 if (localDistanceSqr < d) {
265 SG_LOG(SG_AI, SG_BULK, "findNearestNodeOnRunway from Threshold " << localDistanceSqr);
266 d = localDistanceSqr;
267 result = *it;
268 }
269 }
270
271 return result;
272}
273
282{
283 double d = DBL_MAX;
284 SGVec3d cartPos = SGVec3d::fromGeod(aGeod);
285 FGTaxiNodeRef result = 0;
286 FGTaxiNodeVector::const_iterator it;
287 if (aRunway) {
288 SG_LOG(SG_AI, SG_BULK, "findNearestNodeOnRunwayExit " << aRunway->ident() << " " << aRunway->headingDeg());
289 for (it = m_nodes.begin(); it != m_nodes.end(); ++it) {
290 if (!(*it)->getIsOnRunway())
291 continue;
292 double localDistanceSqr = distSqr(cartPos, (*it)->cart());
293 double headingTowardsExit = SGGeodesy::courseDeg(aGeod, (*it)->geod());
294 double diff = fabs(SGMiscd::normalizePeriodic(-180, 180, aRunway->headingDeg() - headingTowardsExit));
295 SG_LOG(SG_AI, SG_BULK, "findNearestNodeOnRunwayExit Diff : " << diff << " Id : " << (*it)->getIndex());
296 if (diff > 10) {
297 // Only ahead
298 continue;
299 }
300 FGTaxiNodeVector exitSegments = findSegmentsFrom((*it));
301 // Some kind of star
302 if (exitSegments.size() > 2) {
303 continue;
304 }
305 // two segments and next points are on runway, too. Must be a segment before end
306 // single runway point not at end is ok
307 if (exitSegments.size() == 2 &&
308 ((*exitSegments.at(0)).getIsOnRunway() || (*exitSegments.at(0)).getIsOnRunway())) {
309 continue;
310 }
311 if (exitSegments.empty()) {
312 SG_LOG(SG_AI, SG_ALERT, "findNearestNodeOnRunwayExit broken node :" << diff << " Node Id : " << (*it)->getIndex() << " Apt : " << aRunway->airport()->getId());
313 continue;
314 }
315 double exitHeading = SGGeodesy::courseDeg((*it)->geod(),
316 (exitSegments.back())->geod());
317 diff = fabs(SGMiscd::normalizePeriodic(-180, 180, aRunway->headingDeg() - exitHeading));
318 SG_LOG(SG_AI, SG_BULK, "findNearestNodeOnRunwayExit2 Diff :" << diff << " Id : " << (*it)->getIndex());
319 if (diff > 70) {
320 // Only exits going in our direction
321 continue;
322 }
323 if (localDistanceSqr < d) {
324 SG_LOG(SG_AI, SG_BULK, "findNearestNodeOnRunwayExit3 " << localDistanceSqr << " " << (*it)->getIndex());
325 d = localDistanceSqr;
326 result = *it;
327 }
328 }
329 } else {
330 SG_LOG(SG_AI, SG_ALERT, "No Runway findNearestNodeOnRunwayExit");
331 }
332 if (result) {
333 SG_LOG(SG_AI, SG_BULK, "findNearestNodeOnRunwayExit found :" << result->getIndex());
334 return result;
335 }
336 // Ok then fallback only exits ahead
337 for (it = m_nodes.begin(); it != m_nodes.end(); ++it) {
338 if (!(*it)->getIsOnRunway())
339 continue;
340 double localDistanceSqr = distSqr(cartPos, (*it)->cart());
341 if (aRunway) {
342 double headingTowardsExit = SGGeodesy::courseDeg(aGeod, (*it)->geod());
343 double diff = fabs(aRunway->headingDeg() - headingTowardsExit);
344 SG_LOG(SG_AI, SG_BULK, "findNearestNodeOnRunwayExitFallback1 " << aRunway->headingDeg() << " "
345 << " Diff : " << diff << " " << (*it)->getIndex());
346 if (diff > 10) {
347 // Only ahead
348 continue;
349 }
350 }
351 if (localDistanceSqr < d) {
352 SG_LOG(SG_AI, SG_BULK, "findNearestNodeOnRunwayExitFallback1 " << localDistanceSqr);
353 d = localDistanceSqr;
354 result = *it;
355 }
356 }
357 if (result) {
358 return result;
359 }
360 // Ok then fallback only exits
361 for (it = m_nodes.begin(); it != m_nodes.end(); ++it) {
362 if (!(*it)->getIsOnRunway())
363 continue;
364 double localDistanceSqr = distSqr(cartPos, (*it)->cart());
365 if (localDistanceSqr < d) {
366 SG_LOG(SG_AI, SG_BULK, "findNearestNodeOnRunwayExitFallback2 " << localDistanceSqr);
367 d = localDistanceSqr;
368 result = *it;
369 }
370 }
371 if (result) {
372 return result;
373 } else {
374 SG_LOG(SG_AI, SG_WARN, "No runway exit found " << aRunway->airport()->getId() << "/" << aRunway->name());
375 return 0;
376 }
377}
378
380{
381 FGTaxiSegment* seg = findSegment(index);
382 if (!seg)
383 return NULL;
384 return seg->opposite();
385}
386
388{
389 return m_parkings;
390}
391
393{
394 if ((idx > 0) && (idx <= segments.size()))
395 return segments[idx - 1];
396 else {
397 //cerr << "Alert: trying to find invalid segment " << idx << endl;
398 return 0;
399 }
400}
401
403{
404 if (from == 0) {
405 return NULL;
406 }
407
408 // completely boring linear search of segments. Can be improved if/when
409 // this ever becomes a hot-spot
410 for (auto seg : segments) {
411 if (seg->startNode != from) {
412 continue;
413 }
414
415 if ((to == 0) || (seg->endNode == to)) {
416 return seg;
417 }
418 }
419
420 return NULL; // not found
421}
422
423static int edgePenalty(FGTaxiNode* tn)
424{
425 return (tn->type() == FGPositioned::PARKING ? 10000 : 0) +
426 (tn->getIsOnRunway() ? 1000 : 0);
427}
428
430{
431public:
433 {
434 }
435
436 double score;
438};
439
441{
442 if (!start || !end) {
443 throw sg_exception("Bad arguments to findShortestRoute");
444 }
445 //implements Dijkstra's algorithm to find shortest distance route from start to end
446 //taken from http://en.wikipedia.org/wiki/Dijkstra's_algorithm
447 FGTaxiNodeVector unvisited(m_nodes);
448 std::map<FGTaxiNode*, ShortestPathData> searchData;
449
450 searchData[start].score = 0.0;
451
452 while (!unvisited.empty()) {
453 // find lowest scored unvisited
454 FGTaxiNodeRef best = unvisited.front();
455 for (auto i : unvisited) {
456 if (searchData[i].score < searchData[best].score) {
457 best = i;
458 }
459 }
460
461 // remove 'best' from the unvisited set
463 remove(unvisited.begin(), unvisited.end(), best);
464 unvisited.erase(newend, unvisited.end());
465
466 if (best == end) { // found route or best not connected
467 break;
468 }
469
470 for (auto target : findSegmentsFrom(best)) {
471 double edgeLength = dist(best->cart(), target->cart());
472 double alt = searchData[best].score + edgeLength + edgePenalty(target);
473 if (alt < searchData[target].score) { // Relax (u,v)
474 searchData[target].score = alt;
475 searchData[target].previousNode = best;
476 }
477 } // of outgoing arcs/segments from current best node iteration
478 } // of unvisited nodes remaining
479
480 if (searchData[end].score == HUGE_VAL) {
481 // no valid route found
482 if (fullSearch && start && end) {
483 SG_LOG(SG_GENERAL, SG_ALERT,
484 "Failed to find route from waypoint " << start->getIndex() << " to "
485 << end->getIndex() << " at " << parent->getId());
486 }
487
488 return FGTaxiRoute();
489 }
490
491 // assemble route from backtrace information
492 FGTaxiNodeVector nodes;
493 intVec routes;
494 FGTaxiNode* bt = end;
495
496 while (searchData[bt].previousNode != 0) {
497 nodes.push_back(bt);
498 FGTaxiSegment* segment = findSegment(searchData[bt].previousNode, bt);
499 int idx = segment->getIndex();
500 routes.push_back(idx);
501 bt = searchData[bt].previousNode;
502 }
503 nodes.push_back(start);
504 reverse(nodes.begin(), nodes.end());
505 reverse(routes.begin(), routes.end());
506 return FGTaxiRoute(nodes, routes, searchData[end].score, 0);
507}
508
510{
511 for (auto& seg : segments) {
512 seg->unblock(now);
513 }
514}
515
516void FGGroundNetwork::blockSegmentsEndingAt(const FGTaxiSegment* seg, int blockId, time_t blockTime, time_t now)
517{
518 if (!seg) {
519 throw sg_exception("Passed invalid segment");
520 }
521
522 long i = 0;
523 const auto range = m_segmentsEndingAtNodeMap.equal_range(seg->getEnd());
524 for (auto it = range.first; it != range.second; ++it) {
525 // our inbound segment will be included, so skip it
526 if (it->second == seg)
527 continue;
528
529 i++;
530 it->second->block(blockId, blockTime, now);
531 }
532 SG_LOG(SG_ATC, SG_BULK, "blockSegmentsEndingAt " << "\t" << i << "\t" << seg->getIndex() << "\t" << blockId);
533}
534
535FGTaxiNodeRef FGGroundNetwork::findNodeByIndex(int index) const
536{
537 FGTaxiNodeVector::const_iterator it;
538 for (it = m_nodes.begin(); it != m_nodes.end(); ++it) {
539 if ((*it)->getIndex() == index) {
540 return *it;
541 }
542 }
543
544 return FGTaxiNodeRef();
545}
546
548{
549 FGTaxiNodeRef tn = findNodeByIndex(index);
550 if (!tn.valid() || (tn->type() != FGPositioned::PARKING)) {
551 return FGParkingRef();
552 }
553
554 return FGParkingRef(static_cast<FGParking*>(tn.ptr()));
555}
556
558{
559 auto it = std::find_if(m_parkings.begin(), m_parkings.end(), [name](const FGParkingRef& p) {
560 return p->ident() == name;
561 });
562
563 if (it == m_parkings.end())
564 return nullptr;
565
566 return *it;
567}
568
569void FGGroundNetwork::addSegment(const FGTaxiNodeRef& from, const FGTaxiNodeRef& to)
570{
571 FGTaxiSegment* seg = new FGTaxiSegment(from, to);
572
573 segments.push_back(seg);
574
575 FGTaxiNodeVector::iterator it = std::find(m_nodes.begin(), m_nodes.end(), from);
576 if (it == m_nodes.end()) {
577 m_nodes.push_back(from);
578 }
579
580 it = std::find(m_nodes.begin(), m_nodes.end(), to);
581 if (it == m_nodes.end()) {
582 m_nodes.push_back(to);
583 }
584}
585
586void FGGroundNetwork::addParking(const FGParkingRef& park)
587{
588 m_parkings.push_back(park);
589
590
591 FGTaxiNodeVector::iterator it = std::find(m_nodes.begin(), m_nodes.end(), park);
592 if (it == m_nodes.end()) {
593 m_nodes.push_back(park);
594 }
595}
596
598{
599 FGTaxiNodeVector result;
600 FGTaxiSegmentVector::const_iterator it;
601 for (it = segments.begin(); it != segments.end(); ++it) {
602 if ((*it)->getStart() == from) {
603 result.push_back((*it)->getEnd());
604 }
605 }
606
607 return result;
608}
609
610FGTaxiSegment* FGGroundNetwork::findSegmentByHeading(const FGTaxiNode* from, const double heading) const
611{
612 if (from == 0) {
613 return NULL;
614 }
615
616 FGTaxiSegment* best = nullptr;
617
618 // completely boring linear search of segments. Can be improved if/when
619 // this ever becomes a hot-spot
620 for (auto seg : segments) {
621 if (seg->startNode != from) {
622 continue;
623 }
624
625 if (!best || fabs(best->getHeading() - heading) > fabs(seg->getHeading() - heading)) {
626 best = seg;
627 }
628 }
629
630 return best; // not found
631}
632
634{
635 return freqApproach;
636}
637
639{
640 return freqTower;
641}
642
644{
645 return freqGround;
646}
#define p(x)
#define i(x)
BlockList::iterator BlockListIterator
FGTaxiNodeVector::iterator FGTaxiNodeVectorIterator
SGSharedPtr< FGTaxiNode > FGTaxiNodeRef
std::vector< FGParkingRef > FGParkingList
std::vector< FGTaxiNodeRef > FGTaxiNodeVector
SGSharedPtr< FGParking > FGParkingRef
FGTaxiNodeRef findNearestNodeOffRunway(const SGGeod &aGeod, FGRunway *aRunway, double distanceM) const
FGGroundNetwork(FGAirport *pr)
FGTaxiNodeRef findNearestNodeOnRunwayExit(const SGGeod &aGeod, FGRunway *aRunway=NULL) const
Returns the nearest node in that is in direction of runway heading.
FGTaxiNodeVector findSegmentsFrom(const FGTaxiNodeRef &from) const
Find the segments connected to the node.
FGTaxiSegment * findSegmentByHeading(const FGTaxiNode *from, const double heading) const
Find the taxiway segment best matching the heading.
FGAirport * airport() const
FGParkingRef findParkingByName(const std::string &name) const
const intVec & getApproachFrequencies() const
FGTaxiNodeRef findNearestNodeOnRunwayEntry(const SGGeod &aGeod) const
const intVec & getGroundFrequencies() const
FGParkingRef getParkingByIndex(unsigned int index) const
void unblockAllSegments(time_t now)
FGTaxiSegment * findSegment(const FGTaxiNode *from, const FGTaxiNode *to) const
Find the taxiway segment joining two (ground-net) nodes.
FGTaxiNodeRef findNearestNode(const SGGeod &aGeod) const
virtual ~FGGroundNetwork()
FGTaxiSegment * findOppositeSegment(unsigned int index) const
FGTaxiRoute findShortestRoute(FGTaxiNode *start, FGTaxiNode *end, bool fullSearch=true)
void blockSegmentsEndingAt(const FGTaxiSegment *seg, int blockId, time_t blockTime, time_t now)
const intVec & getTowerFrequencies() const
const FGParkingList & allParkings() const
virtual const std::string & name() const
Return the name of this positioned.
virtual const SGGeod & geod() const
virtual const SGVec3d & cart() const
The cartesian position associated with this object.
@ PARKING
parking position - might be a gate, or stand
Type type() const
const std::string & ident() const
double headingDeg() const
Runway heading in degrees.
FGAirportRef airport() const
SGGeod end() const
Get the 'far' end - this is equivalent to calling pointOnCenterline(lengthFt());.
Definition runways.cxx:94
int getIndex() const
Definition gnnode.cxx:55
bool getIsOnRunway() const
Definition gnnode.hxx:34
bool next(FGTaxiNodeRef &nde, int *rte)
FGTaxiSegment(FGTaxiNode *start, FGTaxiNode *end)
FGTaxiNodeRef getEnd() const
double getLength() const
bool hasBlock(time_t now)
FGTaxiSegment * opposite()
void unblock(time_t now)
int getIndex() const
FGTaxiNodeRef getStart() const
void block(int id, time_t blockTime, time_t now)
double getHeading() const
SGGeod getCenter() const
FGTaxiNodeRef previousNode
const char * name
static int edgePenalty(FGTaxiNode *tn)
std::vector< int > intVec