1 // navlist.cxx -- navaids management class
2 //
3 // Written by Curtis Olson, started April 2000.
4 //
5 // Copyright (C) 2000  Curtis L. Olson - http://www.flightgear.org/~curt
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22 
23 
24 #ifdef HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27 
28 #include <cassert>
29 #include <algorithm>
30 
31 #include <simgear/debug/logstream.hxx>
32 #include <simgear/math/sg_geodesy.hxx>
33 #include <simgear/sg_inlines.h>
34 
35 #include "navlist.hxx"
36 
37 #include <Airports/runways.hxx>
38 #include <Navaids/NavDataCache.hxx>
39 #include <Navaids/navrecord.hxx>
40 
41 using std::string;
42 
43 namespace { // anonymous
44 
45 class NavRecordDistanceSortPredicate
46 {
47 public:
NavRecordDistanceSortPredicate(const SGGeod & position)48   NavRecordDistanceSortPredicate( const SGGeod & position ) :
49   _position(SGVec3d::fromGeod(position)) {}
50 
operator ()(const nav_rec_ptr & n1,const nav_rec_ptr & n2)51   bool operator()( const nav_rec_ptr & n1, const nav_rec_ptr & n2 )
52   {
53     if( n1 == NULL || n2 == NULL ) return false;
54     return distSqr(n1->cart(), _position) < distSqr(n2->cart(), _position);
55   }
56 private:
57   SGVec3d _position;
58 
59 };
60 
61 // discount navids if they conflict with another on the same frequency
62 // this only applies to navids associated with opposite ends of a runway,
63 // with matching frequencies.
navidUsable(FGNavRecord * aNav,const SGGeod & aircraft)64 bool navidUsable(FGNavRecord* aNav, const SGGeod &aircraft)
65 {
66   FGRunway* r(aNav->runway());
67   if (!r || !r->reciprocalRunway()) {
68     return true;
69   }
70 
71   // check if the runway frequency is paired
72   FGNavRecord* locA = r->ILS();
73   FGNavRecord* locB = r->reciprocalRunway()->ILS();
74 
75   if (!locA || !locB || (locA->get_freq() != locB->get_freq())) {
76     return true; // not paired, ok
77   }
78 
79   // okay, both ends have an ILS, and they're paired. We need to select based on
80   // aircraft position. What we're going to use is *runway* (not navid) position,
81   // ie whichever runway end we are closer too. This makes back-course / missed
82   // approach behaviour incorrect, but that's the price we accept.
83   double crs = SGGeodesy::courseDeg(aircraft, r->geod());
84   double hdgDiff = crs - r->headingDeg();
85   SG_NORMALIZE_RANGE(hdgDiff, -180.0, 180.0);
86   return (fabs(hdgDiff) < 90.0);
87 }
88 
89 } // of anonymous namespace
90 
91 // FGNavList ------------------------------------------------------------------
92 
93 
94 //------------------------------------------------------------------------------
TypeFilter(const FGPositioned::Type type)95 FGNavList::TypeFilter::TypeFilter(const FGPositioned::Type type)
96 {
97   if (type == FGPositioned::INVALID) {
98     _mintype = FGPositioned::NDB;
99     _maxtype = FGPositioned::GS;
100   } else {
101     _mintype = _maxtype = type;
102   }
103 }
104 
105 //------------------------------------------------------------------------------
TypeFilter(const FGPositioned::Type minType,const FGPositioned::Type maxType)106 FGNavList::TypeFilter::TypeFilter( const FGPositioned::Type minType,
107                                    const FGPositioned::Type maxType ):
108   _mintype(minType),
109   _maxtype(maxType)
110 {
111 }
112 
113 //------------------------------------------------------------------------------
fromTypeString(const std::string & type)114 bool FGNavList::TypeFilter::fromTypeString(const std::string& type)
115 {
116   FGPositioned::Type t;
117   if(      type == "any"  ) t = FGPositioned::INVALID;
118   else if( type == "fix"  ) t = FGPositioned::FIX;
119   else if( type == "vor"  ) t = FGPositioned::VOR;
120   else if( type == "ndb"  ) t = FGPositioned::NDB;
121   else if( type == "ils"  ) t = FGPositioned::ILS;
122   else if( type == "dme"  ) t = FGPositioned::DME;
123   else if( type == "tacan") t = FGPositioned::TACAN;
124   else                      return false;
125 
126   _mintype = _maxtype = t;
127 
128   return true;
129 }
130 
131 /**
132  * Filter returning Tacan stations. Checks for both pure TACAN stations
133  * but also co-located VORTACs. This is done by searching for DMEs whose
134  * name indicates they are a TACAN or VORTAC; not a great solution.
135  */
136 class TacanFilter : public FGNavList::TypeFilter
137 {
138 public:
TacanFilter()139   TacanFilter() :
140     TypeFilter(FGPositioned::DME, FGPositioned::TACAN)
141   {
142   }
143 
pass(FGPositioned * pos) const144   virtual bool pass(FGPositioned* pos) const
145   {
146     if (pos->type() == FGPositioned::TACAN) {
147       return true;
148     }
149 
150     assert(pos->type() == FGPositioned::DME);
151     string::size_type loc1 = pos->name().find( "TACAN" );
152     string::size_type loc2 = pos->name().find( "VORTAC" );
153     return (loc1 != string::npos) || (loc2 != string::npos);
154   }
155 };
156 
locFilter()157 FGNavList::TypeFilter* FGNavList::locFilter()
158 {
159   static TypeFilter tf(FGPositioned::ILS, FGPositioned::LOC);
160   return &tf;
161 }
162 
ndbFilter()163 FGNavList::TypeFilter* FGNavList::ndbFilter()
164 {
165   static TypeFilter tf(FGPositioned::NDB);
166   return &tf;
167 }
168 
navFilter()169 FGNavList::TypeFilter* FGNavList::navFilter()
170 {
171   static TypeFilter tf(FGPositioned::VOR, FGPositioned::LOC);
172   return &tf;
173 }
174 
tacanFilter()175 FGNavList::TypeFilter* FGNavList::tacanFilter()
176 {
177   static TacanFilter tf;
178   return &tf;
179 }
180 
mobileTacanFilter()181 FGNavList::TypeFilter* FGNavList::mobileTacanFilter()
182 {
183   static TypeFilter tf(FGPositioned::MOBILE_TACAN);
184   return &tf;
185 }
186 
findByFreq(double freq,const SGGeod & position,TypeFilter * filter)187 FGNavRecordRef FGNavList::findByFreq( double freq,
188                                       const SGGeod& position,
189                                       TypeFilter* filter )
190 {
191   flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
192   int freqKhz = static_cast<int>(freq * 100 + 0.5);
193   PositionedIDVec stations(cache->findNavaidsByFreq(freqKhz, position, filter));
194   if (stations.empty()) {
195     return NULL;
196   }
197 
198 // now walk the (sorted) results list to find a usable, in-range navaid
199   SGVec3d acCart(SGVec3d::fromGeod(position));
200   double min_dist
201     = FG_NAV_MAX_RANGE*SG_NM_TO_METER*FG_NAV_MAX_RANGE*SG_NM_TO_METER;
202 
203   for (auto id : stations) {
204     FGNavRecordRef station = FGPositioned::loadById<FGNavRecord>(id);
205     if (filter && !filter->pass(station)) {
206       continue;
207     }
208 
209     double d2 = distSqr(station->cart(), acCart);
210     if (d2 > min_dist) {
211     // since results are sorted by proximity, as soon as we pass the
212     // distance cutoff we're done - fall out and return NULL
213       break;
214     }
215 
216     if (navidUsable(station, position)) {
217       return station;
218     }
219   }
220 
221 // fell out of the loop, no usable match
222   return NULL;
223 }
224 
findByFreq(double freq,TypeFilter * filter)225 FGNavRecordRef FGNavList::findByFreq(double freq, TypeFilter* filter)
226 {
227   flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
228   int freqKhz = static_cast<int>(freq * 100 + 0.5);
229   PositionedIDVec stations(cache->findNavaidsByFreq(freqKhz, filter));
230   if (stations.empty()) {
231     return NULL;
232   }
233 
234   for (auto id : stations) {
235     FGNavRecordRef station = FGPositioned::loadById<FGNavRecord>(id);
236     if (filter->pass(station)) {
237       return station;
238     }
239   }
240 
241   return NULL;
242 }
243 
findAllByFreq(double freq,const SGGeod & position,TypeFilter * filter)244 nav_list_type FGNavList::findAllByFreq( double freq, const SGGeod& position,
245                                        TypeFilter* filter)
246 {
247   nav_list_type stations;
248 
249   flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
250     // note this frequency is passed in 'database units', which depend on the
251     // type of navaid being requested
252   int f = static_cast<int>(freq * 100 + 0.5);
253   PositionedIDVec ids(cache->findNavaidsByFreq(f, position, filter));
254 
255   for (PositionedID id : ids) {
256     FGNavRecordRef station = FGPositioned::loadById<FGNavRecord>(id);
257     if (!filter->pass(station)) {
258       continue;
259     }
260 
261     stations.push_back(station);
262   }
263 
264   return stations;
265 }
266 
findByIdentAndFreq(const string & ident,const double freq,TypeFilter * filter)267 nav_list_type FGNavList::findByIdentAndFreq(const string& ident, const double freq,
268                                             TypeFilter* filter)
269 {
270   nav_list_type reply;
271   int f = (int)(freq*100.0 + 0.5);
272 
273   FGPositionedList stations = FGPositioned::findAllWithIdent(ident, filter);
274   for (auto ref : stations) {
275     FGNavRecord* nav = static_cast<FGNavRecord*>(ref.ptr());
276     if ( f <= 0.0 || nav->get_freq() == f) {
277       reply.push_back( nav );
278     }
279   }
280 
281   return reply;
282 }
283 
284 // Given an Ident and optional frequency and type ,
285 // return a list of matching stations sorted by distance to the given position
findByIdentAndFreq(const SGGeod & position,const std::string & ident,const double freq,TypeFilter * filter)286 nav_list_type FGNavList::findByIdentAndFreq( const SGGeod & position,
287         const std::string& ident, const double freq,
288                                             TypeFilter* filter)
289 {
290     nav_list_type reply = findByIdentAndFreq( ident, freq, filter );
291     NavRecordDistanceSortPredicate sortPredicate( position );
292     std::sort( reply.begin(), reply.end(), sortPredicate );
293 
294     return reply;
295 }
296 
297 // FGTACANList ----------------------------------------------------------------
298 
FGTACANList(void)299 FGTACANList::FGTACANList( void )
300 {
301 }
302 
303 
~FGTACANList(void)304 FGTACANList::~FGTACANList( void )
305 {
306 }
307 
308 
init()309 bool FGTACANList::init()
310 {
311     return true;
312 }
313 
314 
315 // add an entry to the lists
add(FGTACANRecord * c)316 bool FGTACANList::add( FGTACANRecord *c )
317 {
318     ident_channels[c->get_channel()].push_back(c);
319     return true;
320 }
321 
322 /*
323  * ref: 070031b3c01f64a44433e8cce742373f4c76b7ac
324  * The ground reply frequencies are used to keep channels 1-16 and 60-69 out of the VOR frequency range as those channels
325  * don't have VORs associated with them.
326  * - this method will therefore only be able to locate channels 17 to 59.
327  */
findByFrequency(int frequency_kHz)328 FGTACANRecord *FGTACANList::findByFrequency(int frequency_kHz)
329 {
330     //029Y    10925 (encoded) = 109.25 = 109250khz - so we divide the input by 10
331     int tfreq = frequency_kHz / 10;
332     for (tacan_ident_map_type::iterator it = ident_channels.begin(); it != ident_channels.end(); it++)
333     {
334         for (tacan_list_type::iterator lit = it->second.begin(); lit != it->second.end(); lit++)
335             if ((*lit)->get_freq() == tfreq)
336                 return (*lit);
337     }
338     return nullptr;
339 }
340 // Given a TACAN Channel return the first matching frequency
findByChannel(const string & channel)341 FGTACANRecord *FGTACANList::findByChannel( const string& channel )
342 {
343     const tacan_list_type& stations = ident_channels[channel];
344     SG_LOG( SG_NAVAID, SG_DEBUG, "findByChannel " << channel<< " size " << stations.size() );
345 
346     if (!stations.empty()) {
347         return stations[0];
348     }
349     return NULL;
350 }
351