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