1 // NasalPositioned_cppbind.cxx -- expose FGPositioned classes to Nasal
2 //
3 // Port of NasalPositioned.cpp to the new nasal/cppbind helpers. Will replace
4 // old NasalPositioned.cpp once finished.
5 //
6 // Copyright (C) 2013  Thomas Geymayer <tomgey@gmail.com>
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25 
26 #include "NasalPositioned.hxx"
27 
28 #include <algorithm>
29 #include <functional>
30 
31 #include <simgear/misc/ListDiff.hxx>
32 #include <simgear/nasal/cppbind/from_nasal.hxx>
33 #include <simgear/nasal/cppbind/to_nasal.hxx>
34 #include <simgear/nasal/cppbind/NasalHash.hxx>
35 #include <simgear/nasal/cppbind/Ghost.hxx>
36 
37 #include <Airports/airport.hxx>
38 #include <Airports/dynamics.hxx>
39 #include <Airports/pavement.hxx>
40 #include <ATC/CommStation.hxx>
41 #include <Main/globals.hxx>
42 #include <Navaids/NavDataCache.hxx>
43 #include <Navaids/navlist.hxx>
44 #include <Navaids/navrecord.hxx>
45 #include <Navaids/fix.hxx>
46 
47 typedef nasal::Ghost<FGPositionedRef> NasalPositioned;
48 typedef nasal::Ghost<FGRunwayRef> NasalRunway;
49 typedef nasal::Ghost<FGParkingRef> NasalParking;
50 typedef nasal::Ghost<FGAirportRef> NasalAirport;
51 typedef nasal::Ghost<flightgear::CommStationRef> NasalCommStation;
52 typedef nasal::Ghost<FGNavRecordRef> NasalNavRecord;
53 typedef nasal::Ghost<FGRunwayRef> NasalRunway;
54 typedef nasal::Ghost<FGFixRef> NasalFix;
55 
56 //------------------------------------------------------------------------------
to_nasal_helper(naContext c,flightgear::SID * sid)57 naRef to_nasal_helper(naContext c, flightgear::SID* sid)
58 {
59   // TODO SID ghost
60   return nasal::to_nasal(c, sid->ident());
61 }
62 
63 //------------------------------------------------------------------------------
to_nasal_helper(naContext c,flightgear::STAR * star)64 naRef to_nasal_helper(naContext c, flightgear::STAR* star)
65 {
66   // TODO STAR ghost
67   return nasal::to_nasal(c, star->ident());
68 }
69 
70 //------------------------------------------------------------------------------
to_nasal_helper(naContext c,flightgear::Approach * iap)71 naRef to_nasal_helper(naContext c, flightgear::Approach* iap)
72 {
73   // TODO Approach ghost
74   return nasal::to_nasal(c, iap->ident());
75 }
76 
77 //------------------------------------------------------------------------------
f_navaid_course(FGNavRecord & nav,naContext)78 static naRef f_navaid_course(FGNavRecord& nav, naContext)
79 {
80   if( !(  nav.type() == FGPositioned::ILS
81        || nav.type() == FGPositioned::LOC
82        ) )
83     return naNil();
84 
85   double radial = nav.get_multiuse();
86   return naNum(SGMiscd::normalizePeriodic(0.5, 360.5, radial));
87 }
88 
89 //------------------------------------------------------------------------------
f_airport_runway(FGAirport & apt,const std::string & ident)90 static FGRunwayBaseRef f_airport_runway(FGAirport& apt, const std::string& ident)
91 {
92   const std::string Id = simgear::strutils::uppercase (ident);
93 
94   if( apt.hasRunwayWithIdent(Id) )
95     return apt.getRunwayByIdent(Id);
96   else if( apt.hasHelipadWithIdent(Id) )
97     return apt.getHelipadByIdent(Id);
98 
99   return 0;
100 }
101 
102 //------------------------------------------------------------------------------
103 template<class T, class C1, class C2>
extract(const std::vector<C1> & in,T (C2::* getter)()const)104 std::vector<T> extract( const std::vector<C1>& in,
105                         T (C2::*getter)() const )
106 {
107   std::vector<T> ret(in.size());
108   std::transform(in.begin(), in.end(), ret.begin(), [getter](const C1& c)
109                  { return (c->*getter)(); }
110                  );
111   return ret;
112 }
113 
114 //------------------------------------------------------------------------------
f_airport_comms(FGAirport & apt,const nasal::CallContext & ctx)115 static naRef f_airport_comms(FGAirport& apt, const nasal::CallContext& ctx)
116 {
117   FGPositioned::Type comm_type =
118     FGPositioned::typeFromName( ctx.getArg<std::string>(0) );
119 
120   // if we have an explicit type, return a simple vector of frequencies
121   if( comm_type != FGPositioned::INVALID )
122     return ctx.to_nasal
123     (
124       extract( apt.commStationsOfType(comm_type),
125                &flightgear::CommStation::freqMHz )
126     );
127   else
128     // otherwise return a vector of ghosts, one for each comm station.
129     return ctx.to_nasal(apt.commStations());
130 }
131 
132 //------------------------------------------------------------------------------
runwayFromNasalArg(const FGAirport & apt,const nasal::CallContext & ctx,size_t index=0)133 FGRunwayRef runwayFromNasalArg( const FGAirport& apt,
134                               const nasal::CallContext& ctx,
135                               size_t index = 0 )
136 {
137   if( index >= ctx.argc )
138     return FGRunwayRef();
139 
140   try
141   {
142     std::string ident = ctx.getArg<std::string>(index);
143     if( !ident.empty() )
144     {
145       if( !apt.hasRunwayWithIdent(ident) )
146         // TODO warning/exception?
147         return FGRunwayRef();
148 
149       return apt.getRunwayByIdent(ident);
150     }
151   }
152   catch(...)
153   {}
154 
155   // TODO warn/error if no runway?
156   return ctx.from_nasal<FGRunwayRef>(ctx.args[index]);
157 }
158 
159 //------------------------------------------------------------------------------
f_airport_sids(FGAirport & apt,const nasal::CallContext & ctx)160 static naRef f_airport_sids(FGAirport& apt, const nasal::CallContext& ctx)
161 {
162   FGRunway* rwy = runwayFromNasalArg(apt, ctx);
163   return ctx.to_nasal
164   (
165     extract(rwy ? rwy->getSIDs() : apt.getSIDs(), &flightgear::SID::ident)
166   );
167 }
168 
169 //------------------------------------------------------------------------------
f_airport_stars(FGAirport & apt,const nasal::CallContext & ctx)170 static naRef f_airport_stars(FGAirport& apt, const nasal::CallContext& ctx)
171 {
172   FGRunway* rwy = runwayFromNasalArg(apt, ctx);
173   return ctx.to_nasal
174   (
175     extract(rwy ? rwy->getSTARs() : apt.getSTARs(), &flightgear::STAR::ident)
176   );
177 }
178 
179 //------------------------------------------------------------------------------
f_airport_approaches(FGAirport & apt,const nasal::CallContext & ctx)180 static naRef f_airport_approaches(FGAirport& apt, const nasal::CallContext& ctx)
181 {
182   FGRunway* rwy = runwayFromNasalArg(apt, ctx);
183 
184   flightgear::ProcedureType type = flightgear::PROCEDURE_INVALID;
185   const std::string type_str = simgear::strutils::uppercase (ctx.getArg<std::string>(1));
186   if( !type_str.empty() )
187   {
188     if(      type_str == "NDB" ) type = flightgear::PROCEDURE_APPROACH_NDB;
189     else if( type_str == "VOR" ) type = flightgear::PROCEDURE_APPROACH_VOR;
190     else if( type_str == "ILS" ) type = flightgear::PROCEDURE_APPROACH_ILS;
191     else if( type_str == "RNAV") type = flightgear::PROCEDURE_APPROACH_RNAV;
192   }
193 
194   return ctx.to_nasal
195   (
196     extract( rwy ? rwy->getApproaches(type)
197                  // no runway specified, report them all
198                  : apt.getApproaches(type),
199              &flightgear::Approach::ident )
200   );
201 }
202 
203 //------------------------------------------------------------------------------
204 static FGParkingList
f_airport_parking(FGAirport & apt,nasal::CallContext ctx)205 f_airport_parking(FGAirport& apt, nasal::CallContext ctx)
206 {
207   std::string type = ctx.getArg<std::string>(0);
208   bool only_available = ctx.getArg<bool>(1);
209   FGAirportDynamicsRef dynamics = apt.getDynamics();
210   return dynamics->getParkings(only_available, type);
211 }
212 
213 /**
214  * Extract a SGGeod from a nasal function argument list.
215  *
216  * <lat>, <lon>
217  * {"lat": <lat-deg>, "lon": <lon-deg>}
218  * geo.Coord.new() (aka. {"_lat": <lat-rad>, "_lon": <lon-rad>})
219  */
extractGeod(nasal::CallContext & ctx,SGGeod & result)220 static bool extractGeod(nasal::CallContext& ctx, SGGeod& result)
221 {
222   if( !ctx.argc )
223     return false;
224 
225   if( ctx.isGhost(0) )
226   {
227     FGPositionedRef pos =
228       ctx.from_nasal<FGPositionedRef>(ctx.requireArg<naRef>(0));
229 
230     if( pos )
231     {
232       result = pos->geod();
233       ctx.popFront();
234       return true;
235     }
236   }
237   else if( ctx.isHash(0) )
238   {
239     nasal::Hash pos_hash = ctx.requireArg<nasal::Hash>(0);
240 
241     // check for manual latitude / longitude names
242     naRef lat = pos_hash.get("lat"),
243           lon = pos_hash.get("lon");
244     if( naIsNum(lat) && naIsNum(lon) )
245     {
246       result = SGGeod::fromDeg( ctx.from_nasal<double>(lon),
247                                 ctx.from_nasal<double>(lat) );
248       ctx.popFront();
249       return true;
250     }
251 
252     // geo.Coord uses _lat/_lon in radians
253     // TODO should we check if its really a geo.Coord?
254     lat = pos_hash.get("_lat");
255     lon = pos_hash.get("_lon");
256     if( naIsNum(lat) && naIsNum(lon) )
257     {
258       result = SGGeod::fromRad( ctx.from_nasal<double>(lon),
259                                 ctx.from_nasal<double>(lat) );
260       ctx.popFront();
261       return true;
262     }
263   }
264   else if( ctx.isNumeric(0) && ctx.isNumeric(1) )
265   {
266     // lat, lon
267     result = SGGeod::fromDeg( ctx.requireArg<double>(1),
268                               ctx.requireArg<double>(0) );
269     ctx.popFront(2);
270     return true;
271   }
272 
273   return false;
274 }
275 
276 /**
277  * Extract position from ctx or return current aircraft position if not given.
278  */
getPosition(nasal::CallContext & ctx)279 static SGGeod getPosition(nasal::CallContext& ctx)
280 {
281   SGGeod pos;
282   if( !extractGeod(ctx, pos) )
283     pos = globals->get_aircraft_position();
284 
285   return pos;
286 }
287 
288 //------------------------------------------------------------------------------
289 // Returns Nasal ghost for particular or nearest airport of a <type>, or nil
290 // on error.
291 //
292 // airportinfo(<id>);                   e.g. "KSFO"
293 // airportinfo(<type>);                 type := ("airport"|"seaport"|"heliport")
294 // airportinfo()                        same as  airportinfo("airport")
295 // airportinfo(<lat>, <lon> [, <type>]);
f_airportinfo(nasal::CallContext ctx)296 static naRef f_airportinfo(nasal::CallContext ctx)
297 {
298   SGGeod pos = getPosition(ctx);
299 
300   if( ctx.argc > 1 )
301     ctx.runtimeError("airportinfo() with invalid function arguments");
302 
303   // optional type/ident
304   std::string ident("airport");
305   if( ctx.isString(0) )
306     ident = ctx.requireArg<std::string>(0);
307 
308   FGAirport::TypeRunwayFilter filter;
309   if( !filter.fromTypeString(ident) )
310     // user provided an <id>, hopefully
311     return ctx.to_nasal(FGAirport::findByIdent(ident));
312 
313   double maxRange = 10000.0; // expose this? or pick a smaller value?
314   return ctx.to_nasal( FGAirport::findClosest(pos, maxRange, &filter) );
315 }
316 
317 /**
318  * findAirportsWithinRange([<position>,] <range-nm> [, type])
319  */
f_findAirportsWithinRange(nasal::CallContext ctx)320 static naRef f_findAirportsWithinRange(nasal::CallContext ctx)
321 {
322   SGGeod pos = getPosition(ctx);
323   double range_nm = ctx.requireArg<double>(0);
324 
325   FGAirport::TypeRunwayFilter filter; // defaults to airports only
326   filter.fromTypeString( ctx.getArg<std::string>(1) );
327 
328   FGPositionedList apts = FGPositioned::findWithinRange(pos, range_nm, &filter);
329   FGPositioned::sortByRange(apts, pos);
330 
331   return ctx.to_nasal(apts);
332 }
333 
334 /**
335  * findAirportsByICAO(<ident/prefix> [, type])
336  */
f_findAirportsByICAO(nasal::CallContext ctx)337 static naRef f_findAirportsByICAO(nasal::CallContext ctx)
338 {
339   std::string prefix = ctx.requireArg<std::string>(0);
340 
341   FGAirport::TypeRunwayFilter filter; // defaults to airports only
342   filter.fromTypeString( ctx.getArg<std::string>(1) );
343 
344   return ctx.to_nasal( FGPositioned::findAllWithIdent(prefix, &filter, false) );
345 }
346 
347 // Returns vector of data hash for navaid of a <type>, nil on error
348 // navaids sorted by ascending distance
349 // navinfo([<lat>,<lon>],[<type>],[<id>])
350 // lat/lon (numeric): use latitude/longitude instead of ac position
351 // type:              ("fix"|"vor"|"ndb"|"ils"|"dme"|"tacan"|"any")
352 // id:                (partial) id of the fix
353 // examples:
354 // navinfo("vor")     returns all vors
355 // navinfo("HAM")     return all navaids who's name start with "HAM"
356 // navinfo("vor", "HAM") return all vor who's name start with "HAM"
357 //navinfo(34,48,"vor","HAM") return all vor who's name start with "HAM"
358 //                           sorted by distance relative to lat=34, lon=48
f_navinfo(nasal::CallContext ctx)359 static naRef f_navinfo(nasal::CallContext ctx)
360 {
361   SGGeod pos = getPosition(ctx);
362   std::string id = ctx.getArg<std::string>(0);
363 
364   FGNavList::TypeFilter filter;
365   if( filter.fromTypeString(id) )
366     id = ctx.getArg<std::string>(1);
367   else if( ctx.argc > 1 )
368     ctx.runtimeError("navinfo() already got an ident");
369 
370   return ctx.to_nasal( FGNavList::findByIdentAndFreq(pos, id, 0.0, &filter) );
371 }
372 
373 //------------------------------------------------------------------------------
f_findWithinRange(nasal::CallContext ctx)374 static naRef f_findWithinRange(nasal::CallContext ctx)
375 {
376   SGGeod pos = getPosition(ctx);
377   double range_nm = ctx.requireArg<double>(0);
378 
379     std::string typeSpec = ctx.getArg<std::string>(1);
380     FGPositioned::TypeFilter filter(FGPositioned::TypeFilter::fromString(typeSpec));
381 
382   FGPositionedList items = FGPositioned::findWithinRange(pos, range_nm, &filter);
383   FGPositioned::sortByRange(items, pos);
384   return ctx.to_nasal(items);
385 }
386 
f_findByIdent(nasal::CallContext ctx)387 static naRef f_findByIdent(nasal::CallContext ctx)
388 {
389   std::string prefix = ctx.requireArg<std::string>(0);
390   std::string typeSpec = ctx.getArg<std::string>(1);
391   FGPositioned::TypeFilter filter(FGPositioned::TypeFilter::fromString(typeSpec));
392   bool exact = ctx.getArg<bool>(2, false);
393 
394   return ctx.to_nasal( FGPositioned::findAllWithIdent(prefix, &filter, exact) );
395 }
396 
f_findByName(nasal::CallContext ctx)397 static naRef f_findByName(nasal::CallContext ctx)
398 {
399   std::string prefix = ctx.requireArg<std::string>(0);
400   std::string typeSpec = ctx.getArg<std::string>(1);
401   FGPositioned::TypeFilter filter(FGPositioned::TypeFilter::fromString(typeSpec));
402 
403   return ctx.to_nasal( FGPositioned::findAllWithName(prefix, &filter, false) );
404 }
405 
406 //------------------------------------------------------------------------------
407 
f_courseAndDistance(nasal::CallContext ctx)408 static naRef f_courseAndDistance(nasal::CallContext ctx)
409 {
410   SGGeod from = globals->get_aircraft_position(), to, pos;
411   bool ok = extractGeod(ctx, pos);
412   if (!ok) {
413     ctx.runtimeError("invalid arguments to courseAndDistance");
414   }
415 
416   if (extractGeod(ctx, to)) {
417     from = pos; // we parsed both FROM and TO args, so first was FROM
418   } else {
419     to = pos; // only parsed one arg, so FROM is current
420   }
421 
422   double course, course2, d;
423   SGGeodesy::inverse(from, to, course, course2, d);
424 
425   return ctx.to_nasal_vec(course, d * SG_METER_TO_NM);
426 }
427 
f_sortByRange(nasal::CallContext ctx)428 static naRef f_sortByRange(nasal::CallContext ctx)
429 {
430   FGPositionedList items = ctx.requireArg<FGPositionedList>(0);
431   ctx.popFront();
432   FGPositioned::sortByRange(items, getPosition(ctx));
433   return ctx.to_nasal(items);
434 }
435 
436 //------------------------------------------------------------------------------
437 // Get difference between two lists of positioned objects.
438 //
439 // For every element in old_list not in new_list the callback cb_remove is
440 // called with the removed element as single argument. For every element in
441 // new_list not in old_list cb_add is called.
442 //
443 // diff(old_list, new_list, cb_add[, cb_remove])
444 //
445 // example:
446 //   # Print all fixes within a distance of 320 to 640 miles
447 //   diff( findWithinRange(320, "fix"),
448 //         findWithinRange(640, "fix"),
449 //         func(p) print('found fix: ', p.id) );
f_diff(nasal::CallContext ctx)450 static naRef f_diff(nasal::CallContext ctx)
451 {
452   typedef simgear::ListDiff<FGPositionedRef> Diff;
453   Diff::List old_list = ctx.requireArg<FGPositionedList>(0),
454              new_list = ctx.requireArg<FGPositionedList>(1);
455   Diff::Callback cb_add = ctx.requireArg<Diff::Callback>(2),
456                  cb_rm  = ctx.getArg<Diff::Callback>(3);
457 
458   // Note that FGPositionedRef instances are only compared for pointer equality.
459   // As the NavCache caches every queried positioned instance it is guaranteed
460   // that only one instance of every positioned object can exist. Therefore we
461   // can make the comparison faster by just comparing pointers and not also the
462   // guid.
463   // (On my machine the difference is 0.27s vs 0.17s)
464   Diff::inplace(old_list, new_list, cb_add, cb_rm);
465 
466   return naNil();
467 }
468 
469 //------------------------------------------------------------------------------
initNasalPositioned_cppbind(naRef globalsRef,naContext c)470 naRef initNasalPositioned_cppbind(naRef globalsRef, naContext c)
471 {
472   NasalPositioned::init("Positioned")
473     .member("id", &FGPositioned::ident)
474     .member("ident", &FGPositioned::ident) // TODO to we really need id and ident?
475     .member("name", &FGPositioned::name)
476     .member("type", &FGPositioned::typeString)
477     .member("lat", &FGPositioned::latitude)
478     .member("lon", &FGPositioned::longitude)
479     .member("elevation", &FGPositioned::elevationM);
480   NasalRunway::init("Runway")
481     .bases<NasalPositioned>();
482   NasalParking::init("Parking")
483     .bases<NasalPositioned>();
484   NasalCommStation::init("CommStation")
485     .bases<NasalPositioned>()
486     .member("frequency", &flightgear::CommStation::freqMHz);
487   NasalNavRecord::init("Navaid")
488     .bases<NasalPositioned>()
489     .member("frequency", &FGNavRecord::get_freq)
490     .member("range_nm", &FGNavRecord::get_range)
491     .member("course", &f_navaid_course)
492     .member("magvar", &FGNavRecord::get_multiuse)
493     .member("dme", &FGNavRecord::hasDME)
494     .member("vorac", &FGNavRecord::isVORTAC);
495 
496   NasalFix::init("Fix")
497     .bases<NasalPositioned>();
498 
499   NasalAirport::init("FGAirport")
500     .bases<NasalPositioned>()
501     .member("has_metar", &FGAirport::getMetar)
502     .member("runways", &FGAirport::getRunwayMap)
503     .member("helipads", &FGAirport::getHelipadMap)
504     .member("taxiways", &FGAirport::getTaxiways)
505     .member("pavements", &FGAirport::getPavements)
506     .method("runway", &f_airport_runway)
507     .method("helipad", &f_airport_runway)
508     .method("tower", &FGAirport::getTowerLocation)
509     .method("comms", &f_airport_comms)
510     .method("sids", &f_airport_sids)
511     .method("stars", &f_airport_stars)
512     .method("getApproachList", f_airport_approaches)
513     .method("parking", &f_airport_parking)
514     .method("getSid", &FGAirport::findSIDWithIdent)
515     .method("getStar", &FGAirport::findSTARWithIdent)
516     .method("getIAP", &FGAirport::findApproachWithIdent)
517     .method("tostring", &FGAirport::toString);
518 
519   nasal::Hash globals(globalsRef, c),
520               positioned( globals.createHash("positioned") );
521 
522   positioned.set("airportinfo", &f_airportinfo);
523   positioned.set("findAirportsWithinRange", f_findAirportsWithinRange);
524   positioned.set("findAirportsByICAO", &f_findAirportsByICAO);
525   positioned.set("navinfo", &f_navinfo);
526 
527   positioned.set("findWithinRange", &f_findWithinRange);
528   positioned.set("findByIdent", &f_findByIdent);
529   positioned.set("findByName", &f_findByName);
530   positioned.set("courseAndDistance", &f_courseAndDistance);
531   positioned.set("sortByRange", &f_sortByRange);
532 
533   positioned.set("diff", &f_diff);
534 
535   return naNil();
536 }
537