]> git.mxchange.org Git - flightgear.git/blob - src/Navaids/positioned.cxx
PLIB net removed from FlightGear
[flightgear.git] / src / Navaids / positioned.cxx
1 // positioned.cxx - base class for objects which are positioned 
2 //
3 // Copyright (C) 2008 James Turner
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 //
19 // $Id$
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24
25 #include "positioned.hxx"
26
27 #include <map>
28 #include <set>
29 #include <algorithm> // for sort
30 #include <queue>
31
32 #include <boost/algorithm/string/case_conv.hpp>
33 #include <boost/algorithm/string/predicate.hpp>
34
35 #include <simgear/timing/timestamp.hxx>
36 #include <simgear/debug/logstream.hxx>
37 #include <simgear/structure/exception.hxx>
38 #include <simgear/math/SGGeometry.hxx>
39
40
41
42 typedef std::multimap<std::string, FGPositioned*> NamedPositionedIndex;
43 typedef std::pair<NamedPositionedIndex::const_iterator, NamedPositionedIndex::const_iterator> NamedIndexRange;
44
45 using std::lower_bound;
46 using std::upper_bound;
47
48 static NamedPositionedIndex global_identIndex;
49 static NamedPositionedIndex global_nameIndex;
50
51 //////////////////////////////////////////////////////////////////////////////
52
53 namespace Octree
54 {
55
56 const double LEAF_SIZE = SG_NM_TO_METER * 8.0;
57 const double LEAF_SIZE_SQR = LEAF_SIZE * LEAF_SIZE;
58
59 /**
60  * Decorate an object with a double value, and use that value to order 
61  * items, for the purpoises of the STL algorithms
62  */
63 template <class T>
64 class Ordered
65 {
66 public:
67     Ordered(const T& v, double x) :
68         _order(x),
69         _inner(v)
70     {
71     }
72     
73     Ordered(const Ordered<T>& a) :
74         _order(a._order),
75         _inner(a._inner)
76     {
77     }
78     
79     Ordered<T>& operator=(const Ordered<T>& a)
80     {
81         _order = a._order;
82         _inner = a._inner;
83         return *this;
84     }
85     
86     bool operator<(const Ordered<T>& other) const
87     {
88         return _order < other._order;
89     }
90     
91     bool operator>(const Ordered<T>& other) const
92     {
93         return _order > other._order;
94     }
95     
96     const T& get() const
97         { return _inner; }
98     
99     double order() const
100         { return _order; }
101     
102 private:    
103     double _order;
104     T _inner;
105 };
106
107 class Node;
108 typedef Ordered<Node*> OrderedNode;
109 typedef std::greater<OrderedNode> FNPQCompare; 
110
111 /**
112  * the priority queue is fundamental to our search algorithm. When searching,
113  * we know the front of the queue is the nearest unexpanded node (to the search
114  * location). The default STL pqueue returns the 'largest' item from top(), so
115  * to get the smallest, we need to replace the default Compare functor (less<>)
116  * with greater<>.
117  */
118 typedef std::priority_queue<OrderedNode, std::vector<OrderedNode>, FNPQCompare> FindNearestPQueue;
119
120 typedef Ordered<FGPositioned*> OrderedPositioned;
121 typedef std::vector<OrderedPositioned> FindNearestResults;
122
123 Node* global_spatialOctree = NULL;
124
125 /**
126  * Octree node base class, tracks its bounding box and provides various
127  * queries relating to it
128  */
129 class Node
130 {
131 public:
132     bool contains(const SGVec3d& aPos) const
133     {
134         return intersects(aPos, _box);
135     }
136
137     double distSqrToNearest(const SGVec3d& aPos) const
138     {
139         return distSqr(aPos, _box.getClosestPoint(aPos));
140     }
141     
142     virtual void insert(FGPositioned* aP) = 0;
143     
144     virtual void visit(const SGVec3d& aPos, double aCutoff, 
145       FGPositioned::Filter* aFilter, 
146       FindNearestResults& aResults, FindNearestPQueue&) = 0;
147 protected:
148     Node(const SGBoxd &aBox) :
149         _box(aBox)
150     {
151     }
152     
153     const SGBoxd _box;
154 };
155
156 class Leaf : public Node
157 {
158 public:
159     Leaf(const SGBoxd& aBox) :
160         Node(aBox)
161     {
162     }
163     
164     const FGPositioned::List& members() const
165     { return _members; }
166     
167     virtual void insert(FGPositioned* aP)
168     {
169         _members.push_back(aP);
170     }
171     
172     virtual void visit(const SGVec3d& aPos, double aCutoff, 
173       FGPositioned::Filter* aFilter, 
174       FindNearestResults& aResults, FindNearestPQueue&)
175     {
176         int previousResultsSize = aResults.size();
177         int addedCount = 0;
178         
179         for (unsigned int i=0; i<_members.size(); ++i) {
180             FGPositioned* p = _members[i];
181             double d2 = distSqr(aPos, p->cart());
182             if (d2 > aCutoff) {
183                 continue;
184             }
185             
186             if (aFilter) {
187               if (aFilter->hasTypeRange() && !aFilter->passType(p->type())) {
188                 continue;
189               }
190       
191               if (!aFilter->pass(p)) {
192                 continue;
193               }
194             } // of have a filter
195
196             ++addedCount;
197             aResults.push_back(OrderedPositioned(p, d2));
198         }
199         
200         if (addedCount == 0) {
201           return;
202         }
203         
204       // keep aResults sorted
205         // sort the new items, usually just one or two items
206         std::sort(aResults.begin() + previousResultsSize, aResults.end());
207         
208         // merge the two sorted ranges together - in linear time
209         std::inplace_merge(aResults.begin(), 
210           aResults.begin() + previousResultsSize, aResults.end());
211       }
212 private:
213     FGPositioned::List _members;
214 };
215
216 class Branch : public Node
217 {
218 public:
219     Branch(const SGBoxd& aBox) :
220         Node(aBox)
221     {
222         memset(children, 0, sizeof(Node*) * 8);
223     }
224     
225     virtual void insert(FGPositioned* aP)
226     {
227         SGVec3d cart(aP->cart());
228         assert(contains(cart));
229         int childIndex = 0;
230         
231         SGVec3d center(_box.getCenter());
232     // tests must match indices in SGbox::getCorner
233         if (cart.x() < center.x()) {
234             childIndex += 1;
235         }
236         
237         if (cart.y() < center.y()) {
238             childIndex += 2;
239         }
240         
241         if (cart.z() < center.z()) {
242             childIndex += 4;
243         }
244         
245         Node* child = children[childIndex];
246         if (!child) { // lazy building of children
247             SGBoxd cb(boxForChild(childIndex));            
248             double d2 = dot(cb.getSize(), cb.getSize());
249             if (d2 < LEAF_SIZE_SQR) {
250                 child = new Leaf(cb);
251             } else {
252                 child = new Branch(cb);
253             }
254             
255             children[childIndex] = child; 
256         }
257         
258         child->insert(aP);
259     }
260     
261     virtual void visit(const SGVec3d& aPos, double aCutoff, 
262       FGPositioned::Filter*, 
263       FindNearestResults&, FindNearestPQueue& aQ)
264     {    
265         for (unsigned int i=0; i<8; ++i) {
266             if (!children[i]) {
267                 continue;
268             }
269             
270             double d2 = children[i]->distSqrToNearest(aPos);
271             if (d2 > aCutoff) {
272                 continue; // exceeded cutoff
273             }
274             
275             aQ.push(Ordered<Node*>(children[i], d2));
276         } // of child iteration
277     }
278     
279     
280 private:
281     /**
282      * Return the box for a child touching the specified corner
283      */
284     SGBoxd boxForChild(unsigned int aCorner) const
285     {
286         SGBoxd r(_box.getCenter());
287         r.expandBy(_box.getCorner(aCorner));
288         return r;
289     }
290     
291     Node* children[8];
292 };
293
294 void findNearestN(const SGVec3d& aPos, unsigned int aN, double aCutoffM, FGPositioned::Filter* aFilter, FGPositioned::List& aResults)
295 {
296     aResults.clear();
297     FindNearestPQueue pq;
298     FindNearestResults results;
299     pq.push(Ordered<Node*>(global_spatialOctree, 0));
300     double cut = aCutoffM * aCutoffM;
301     
302     while (!pq.empty()) {
303         if (!results.empty()) {
304           // terminate the search if we have sufficent results, and we are
305           // sure no node still on the queue contains a closer match
306           double furthestResultOrder = results.back().order();
307           if ((results.size() >= aN) && (furthestResultOrder < pq.top().order())) {
308             break;
309           }
310         }
311         
312         Node* nd = pq.top().get();
313         pq.pop();
314         
315         nd->visit(aPos, cut, aFilter, results, pq);
316     } // of queue iteration
317     
318     // depending on leaf population, we may have (slighty) more results
319     // than requested
320     unsigned int numResults = std::min((unsigned int) results.size(), aN);
321   // copy results out
322     aResults.resize(numResults);
323     for (unsigned int r=0; r<numResults; ++r) {
324       aResults[r] = results[r].get();
325     }
326 }
327
328 void findAllWithinRange(const SGVec3d& aPos, double aRangeM, FGPositioned::Filter* aFilter, FGPositioned::List& aResults)
329 {
330     aResults.clear();
331     FindNearestPQueue pq;
332     FindNearestResults results;
333     pq.push(Ordered<Node*>(global_spatialOctree, 0));
334     double rng = aRangeM * aRangeM;
335     
336     while (!pq.empty()) {
337         Node* nd = pq.top().get();
338         pq.pop();
339         
340         nd->visit(aPos, rng, aFilter, results, pq);
341     } // of queue iteration
342     
343     unsigned int numResults = results.size();
344   // copy results out
345     aResults.resize(numResults);
346     for (unsigned int r=0; r<numResults; ++r) {
347       aResults[r] = results[r].get();
348     }
349 }
350
351 } // of namespace Octree
352
353 //////////////////////////////////////////////////////////////////////////////
354
355 static void
356 addToIndices(FGPositioned* aPos)
357 {
358   assert(aPos);
359   if (!aPos->ident().empty()) {
360     std::string u(boost::to_upper_copy(aPos->ident()));
361     
362     global_identIndex.insert(global_identIndex.begin(), 
363       std::make_pair(u, aPos));
364   }
365   
366   if (!aPos->name().empty()) {
367     std::string u(boost::to_upper_copy(aPos->name()));
368     
369     global_nameIndex.insert(global_nameIndex.begin(), 
370                              std::make_pair(u, aPos));
371   }
372
373   if (!Octree::global_spatialOctree) {
374     double RADIUS_EARTH_M = 7000 * 1000.0; // 7000km is plenty
375     SGVec3d earthExtent(RADIUS_EARTH_M, RADIUS_EARTH_M, RADIUS_EARTH_M);
376     Octree::global_spatialOctree = new Octree::Branch(SGBox<double>(-earthExtent, earthExtent));
377   }
378   Octree::global_spatialOctree->insert(aPos);
379 }
380
381 static void
382 removeFromIndices(FGPositioned* aPos)
383 {
384   assert(aPos);
385   
386   if (!aPos->ident().empty()) {
387     std::string u(boost::to_upper_copy(aPos->ident()));
388     NamedPositionedIndex::iterator it = global_identIndex.find(u);
389     while (it != global_identIndex.end() && (it->first == u)) {
390       if (it->second == aPos) {
391         global_identIndex.erase(it);
392         break;
393       }
394       
395       ++it;
396     } // of multimap walk
397   }
398   
399   if (!aPos->name().empty()) {
400     std::string u(boost::to_upper_copy(aPos->name()));
401     NamedPositionedIndex::iterator it = global_nameIndex.find(u);
402     while (it != global_nameIndex.end() && (it->first == u)) {
403       if (it->second == aPos) {
404         global_nameIndex.erase(it);
405         break;
406       }
407       
408       ++it;
409     } // of multimap walk
410   }
411 }
412
413 //////////////////////////////////////////////////////////////////////////////
414
415 class OrderByName
416 {
417 public:
418   bool operator()(FGPositioned* a, FGPositioned* b) const
419   {
420     return a->name() < b->name();
421   }
422 };
423
424 void findInIndex(NamedPositionedIndex& aIndex, const std::string& aFind, std::vector<FGPositioned*>& aResult)
425 {
426   NamedPositionedIndex::const_iterator it = aIndex.begin();
427   NamedPositionedIndex::const_iterator end = aIndex.end();
428
429   bool haveFilter = !aFind.empty();
430
431   for (; it != end; ++it) {
432     FGPositioned::Type ty = it->second->type();
433     if ((ty < FGPositioned::AIRPORT) || (ty > FGPositioned::SEAPORT)) {
434       continue;
435     }
436     
437     if (haveFilter && it->first.find(aFind) == std::string::npos) {
438       continue;
439     }
440     
441     aResult.push_back(it->second);
442   } // of index iteration
443 }
444
445 /**
446  * A special purpose helper (imported by FGAirport::searchNamesAndIdents) to
447  * implement the AirportList dialog. It's unfortunate that it needs to reside
448  * here, but for now it's least ugly solution.
449  */
450 char** searchAirportNamesAndIdents(const std::string& aFilter)
451 {
452 // note this is a vector of raw pointers, not smart pointers, because it
453 // may get very large and smart-pointer-atomicity-locking then becomes a
454 // bottleneck for this case.
455   std::vector<FGPositioned*> matches;
456   if (!aFilter.empty()) {
457     std::string filter = boost::to_upper_copy(aFilter);
458     findInIndex(global_identIndex, filter, matches);
459     findInIndex(global_nameIndex, filter, matches);
460   } else {
461     
462     findInIndex(global_identIndex, std::string(), matches);
463   }
464   
465 // sort alphabetically on name
466   std::sort(matches.begin(), matches.end(), OrderByName());
467   
468 // convert results to format comptible with puaList
469   unsigned int numMatches = matches.size();
470   char** result = new char*[numMatches + 1];
471   result[numMatches] = NULL; // end-of-list marker
472   
473   // nasty code to avoid excessive string copying and allocations.
474   // We format results as follows (note whitespace!):
475   //   ' name-of-airport-chars   (ident)'
476   // so the total length is:
477   //    1 + strlen(name) + 4 + strlen(icao) + 1 + 1 (for the null)
478   // which gives a grand total of 7 + name-length + icao-length.
479   // note the ident can be three letters (non-ICAO local strip), four
480   // (default ICAO) or more (extended format ICAO)
481   for (unsigned int i=0; i<numMatches; ++i) {
482     int nameLength = matches[i]->name().size();
483     int icaoLength = matches[i]->ident().size();
484     char* entry = new char[7 + nameLength + icaoLength];
485     char* dst = entry;
486     *dst++ = ' ';
487     memcpy(dst, matches[i]->name().c_str(), nameLength);
488     dst += nameLength;
489     *dst++ = ' ';
490     *dst++ = ' ';
491     *dst++ = ' ';
492     *dst++ = '(';
493     memcpy(dst, matches[i]->ident().c_str(), icaoLength);
494     dst += icaoLength;
495     *dst++ = ')';
496     *dst++ = 0;
497     result[i] = entry;
498   }
499   
500   return result;
501 }
502
503 ///////////////////////////////////////////////////////////////////////////////
504
505 bool
506 FGPositioned::Filter::hasTypeRange() const
507 {
508   assert(minType() <= maxType());
509   return (minType() != INVALID) && (maxType() != INVALID);
510 }
511
512 bool
513 FGPositioned::Filter::passType(Type aTy) const
514 {
515   assert(hasTypeRange());
516   return (minType() <= aTy) && (maxType() >= aTy);
517 }
518
519 static FGPositioned::List 
520 findAll(const NamedPositionedIndex& aIndex, 
521                              const std::string& aName,
522                              FGPositioned::Filter* aFilter,
523                              bool aExact)
524 {
525   FGPositioned::List result;
526   if (aName.empty()) {
527     return result;
528   }
529   
530   std::string name = boost::to_upper_copy(aName);
531   NamedPositionedIndex::const_iterator upperBound;
532   
533   if (aExact) {
534     upperBound = aIndex.upper_bound(name);
535   } else {
536     std::string upperBoundId = name;
537     upperBoundId[upperBoundId.size()-1]++;
538     upperBound = aIndex.lower_bound(upperBoundId);
539   }
540   
541   NamedPositionedIndex::const_iterator it = aIndex.lower_bound(name);
542   
543   for (; it != upperBound; ++it) {
544     FGPositionedRef candidate = it->second;
545     if (aFilter) {
546       if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
547         continue;
548       }
549       
550       if (!aFilter->pass(candidate)) {
551         continue;
552       }
553     }
554     
555     result.push_back(candidate);
556   }
557   
558   return result;
559 }
560
561 ///////////////////////////////////////////////////////////////////////////////
562
563 FGPositioned::FGPositioned(Type ty, const std::string& aIdent, const SGGeod& aPos) :
564   mPosition(aPos),
565   mType(ty),
566   mIdent(aIdent)
567 {  
568 }
569
570 void FGPositioned::init(bool aIndexed)
571 {
572   SGReferenced::get(this); // hold an owning ref, for the moment
573   mCart = SGVec3d::fromGeod(mPosition);
574   
575   if (aIndexed) {
576     assert(mType != TAXIWAY && mType != PAVEMENT);
577     addToIndices(this);
578   }
579 }
580
581 FGPositioned::~FGPositioned()
582 {
583   //std::cout << "destroying:" << mIdent << "/" << nameForType(mType) << std::endl;
584   removeFromIndices(this);
585 }
586
587 FGPositioned*
588 FGPositioned::createUserWaypoint(const std::string& aIdent, const SGGeod& aPos)
589 {
590   FGPositioned* wpt = new FGPositioned(WAYPOINT, aIdent, aPos);
591   wpt->init(true);
592   return wpt;
593 }
594
595 const SGVec3d&
596 FGPositioned::cart() const
597 {
598   return mCart;
599 }
600
601 FGPositioned::Type FGPositioned::typeFromName(const std::string& aName)
602 {
603   if (aName.empty() || (aName == "")) {
604     return INVALID;
605   }
606
607   typedef struct {
608     const char* _name;
609     Type _ty;
610   } NameTypeEntry;
611   
612   const NameTypeEntry names[] = {
613     {"airport", AIRPORT},
614     {"vor", VOR},
615     {"ndb", NDB},
616     {"wpt", WAYPOINT},
617     {"fix", FIX},
618     {"tacan", TACAN},
619     {"dme", DME},
620   // aliases
621     {"waypoint", WAYPOINT},
622     {"apt", AIRPORT},
623     {"arpt", AIRPORT},
624     {"any", INVALID},
625     {"all", INVALID},
626     
627     {NULL, INVALID}
628   };
629   
630   std::string lowerName(boost::to_lower_copy(aName));
631   
632   for (const NameTypeEntry* n = names; (n->_name != NULL); ++n) {
633     if (::strcmp(n->_name, lowerName.c_str()) == 0) {
634       return n->_ty;
635     }
636   }
637   
638   SG_LOG(SG_GENERAL, SG_WARN, "FGPositioned::typeFromName: couldn't match:" << aName);
639   return INVALID;
640 }
641
642 const char* FGPositioned::nameForType(Type aTy)
643 {
644  switch (aTy) {
645  case RUNWAY: return "runway";
646  case TAXIWAY: return "taxiway";
647  case PAVEMENT: return "pavement";
648  case PARK_STAND: return "parking stand";
649  case FIX: return "fix";
650  case VOR: return "VOR";
651  case NDB: return "NDB";
652  case ILS: return "ILS";
653  case LOC: return "localiser";
654  case GS: return "glideslope";
655  case OM: return "outer-marker";
656  case MM: return "middle-marker";
657  case IM: return "inner-marker";
658  case AIRPORT: return "airport";
659  case HELIPORT: return "heliport";
660  case SEAPORT: return "seaport";
661  case WAYPOINT: return "waypoint";
662  case DME: return "dme";
663  case TACAN: return "tacan";
664  default:
665   return "unknown";
666  }
667 }
668
669 ///////////////////////////////////////////////////////////////////////////////
670 // search / query functions
671
672 FGPositionedRef
673 FGPositioned::findClosestWithIdent(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
674 {
675   FGPositioned::List r(findAll(global_identIndex, aIdent, aFilter, true));
676   if (r.empty()) {
677     return FGPositionedRef();
678   }
679   
680   sortByRange(r, aPos);
681   return r.front();
682 }
683
684 FGPositioned::List
685 FGPositioned::findWithinRange(const SGGeod& aPos, double aRangeNm, Filter* aFilter)
686 {
687   List result;
688   Octree::findAllWithinRange(SGVec3d::fromGeod(aPos), 
689     aRangeNm * SG_NM_TO_METER, aFilter, result);
690   return result;
691 }
692
693 FGPositioned::List
694 FGPositioned::findAllWithIdent(const std::string& aIdent, Filter* aFilter, bool aExact)
695 {
696   return findAll(global_identIndex, aIdent, aFilter, aExact);
697 }
698
699 FGPositioned::List
700 FGPositioned::findAllWithName(const std::string& aName, Filter* aFilter, bool aExact)
701 {
702   return findAll(global_nameIndex, aName, aFilter, aExact);
703 }
704
705 FGPositionedRef
706 FGPositioned::findClosest(const SGGeod& aPos, double aCutoffNm, Filter* aFilter)
707 {
708    List l(findClosestN(aPos, 1, aCutoffNm, aFilter));
709    if (l.empty()) {
710       return NULL;
711    }
712    
713    assert(l.size() == 1);
714    return l.front();
715 }
716
717 FGPositioned::List
718 FGPositioned::findClosestN(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter)
719 {
720   List result;
721   Octree::findNearestN(SGVec3d::fromGeod(aPos), aN, aCutoffNm * SG_NM_TO_METER, aFilter, result);
722   return result;
723 }
724
725 FGPositionedRef
726 FGPositioned::findNextWithPartialId(FGPositionedRef aCur, const std::string& aId, Filter* aFilter)
727 {
728   if (aId.empty()) {
729     return NULL;
730   }
731   
732   std::string id(boost::to_upper_copy(aId));
733
734   // It is essential to bound our search, to avoid iterating all the way to the end of the database.
735   // Do this by generating a second ID with the final character incremented by 1.
736   // e.g., if the partial ID is "KI", we wish to search "KIxxx" but not "KJ".
737   std::string upperBoundId = id;
738   upperBoundId[upperBoundId.size()-1]++;
739   NamedPositionedIndex::const_iterator upperBound = global_identIndex.lower_bound(upperBoundId);
740
741   NamedIndexRange range = global_identIndex.equal_range(id);
742   while (range.first != upperBound) {
743     for (; range.first != range.second; ++range.first) {
744       FGPositionedRef candidate = range.first->second;
745       if (aCur == candidate) {
746         aCur = NULL; // found our start point, next match will pass
747         continue;
748       }
749
750       if (aFilter) {
751         if (aFilter->hasTypeRange() && !aFilter->passType(candidate->type())) {
752           continue;
753         }
754
755         if (!aFilter->pass(candidate)) {
756           continue;
757         }
758       }
759
760       if (!aCur) {
761         return candidate;
762       }
763     }
764
765     // Unable to match the filter with this range - try the next range.
766     range = global_identIndex.equal_range(range.second->first);
767   }
768
769   return NULL; // Reached the end of the valid sequence with no match.  
770 }
771   
772 void
773 FGPositioned::sortByRange(List& aResult, const SGGeod& aPos)
774 {
775   SGVec3d cartPos(SGVec3d::fromGeod(aPos));
776 // computer ordering values
777   Octree::FindNearestResults r;
778   List::iterator it = aResult.begin(), lend = aResult.end();
779   for (; it != lend; ++it) {
780     double d2 = distSqr((*it)->cart(), cartPos);
781     r.push_back(Octree::OrderedPositioned(*it, d2));
782   }
783   
784 // sort
785   std::sort(r.begin(), r.end());
786   
787 // convert to a plain list
788   unsigned int count = aResult.size();
789   for (unsigned int i=0; i<count; ++i) {
790     aResult[i] = r[i].get();
791   }
792 }