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