1 // NasalPositioned_cppbind.cxx -- expose FGPositioned classes to Nasal
3 // Port of NasalPositioned.cpp to the new nasal/cppbind helpers. Will replace
4 // old NasalPositioned.cpp once finished.
6 // Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
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.
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.
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.
26 #include "NasalPositioned.hxx"
31 #include <boost/foreach.hpp>
32 #include <boost/algorithm/string/case_conv.hpp>
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>
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>
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;
59 //------------------------------------------------------------------------------
60 naRef to_nasal_helper(naContext c, flightgear::SID* sid)
63 return nasal::to_nasal(c, sid->ident());
66 //------------------------------------------------------------------------------
67 naRef to_nasal_helper(naContext c, flightgear::STAR* star)
70 return nasal::to_nasal(c, star->ident());
73 //------------------------------------------------------------------------------
74 naRef to_nasal_helper(naContext c, flightgear::Approach* iap)
76 // TODO Approach ghost
77 return nasal::to_nasal(c, iap->ident());
80 //------------------------------------------------------------------------------
81 static naRef f_navaid_course(FGNavRecord& nav, naContext)
83 if( !( nav.type() == FGPositioned::ILS
84 || nav.type() == FGPositioned::LOC
88 double radial = nav.get_multiuse();
89 return naNum(SGMiscd::normalizePeriodic(0.5, 360.5, radial));
92 //------------------------------------------------------------------------------
93 static FGRunwayBaseRef f_airport_runway(FGAirport& apt, std::string ident)
95 boost::to_upper(ident);
97 if( apt.hasRunwayWithIdent(ident) )
98 return apt.getRunwayByIdent(ident);
99 else if( apt.hasHelipadWithIdent(ident) )
100 return apt.getHelipadByIdent(ident);
105 //------------------------------------------------------------------------------
106 template<class T, class C1, class C2>
107 std::vector<T> extract( const std::vector<C1>& in,
108 T (C2::*getter)() const )
110 std::vector<T> ret(in.size());
111 std::transform(in.begin(), in.end(), ret.begin(), std::mem_fun(getter));
115 //------------------------------------------------------------------------------
116 static naRef f_airport_comms(FGAirport& apt, const nasal::CallContext& ctx)
118 FGPositioned::Type comm_type =
119 FGPositioned::typeFromName( ctx.getArg<std::string>(0) );
121 // if we have an explicit type, return a simple vector of frequencies
122 if( comm_type != FGPositioned::INVALID )
125 extract( apt.commStationsOfType(comm_type),
126 &flightgear::CommStation::freqMHz )
129 // otherwise return a vector of ghosts, one for each comm station.
130 return ctx.to_nasal(apt.commStations());
133 //------------------------------------------------------------------------------
134 FGRunwayRef runwayFromNasalArg( const FGAirport& apt,
135 const nasal::CallContext& ctx,
138 if( index >= ctx.argc )
139 return FGRunwayRef();
143 std::string ident = ctx.getArg<std::string>(index);
146 if( !apt.hasRunwayWithIdent(ident) )
147 // TODO warning/exception?
148 return FGRunwayRef();
150 return apt.getRunwayByIdent(ident);
156 // TODO warn/error if no runway?
157 return ctx.from_nasal<FGRunwayRef>(ctx.args[index]);
160 //------------------------------------------------------------------------------
161 static naRef f_airport_sids(FGAirport& apt, const nasal::CallContext& ctx)
163 FGRunway* rwy = runwayFromNasalArg(apt, ctx);
166 extract(rwy ? rwy->getSIDs() : apt.getSIDs(), &flightgear::SID::ident)
170 //------------------------------------------------------------------------------
171 static naRef f_airport_stars(FGAirport& apt, const nasal::CallContext& ctx)
173 FGRunway* rwy = runwayFromNasalArg(apt, ctx);
176 extract(rwy ? rwy->getSTARs() : apt.getSTARs(), &flightgear::STAR::ident)
180 //------------------------------------------------------------------------------
181 static naRef f_airport_approaches(FGAirport& apt, const nasal::CallContext& ctx)
183 FGRunway* rwy = runwayFromNasalArg(apt, ctx);
185 flightgear::ProcedureType type = flightgear::PROCEDURE_INVALID;
186 std::string type_str = ctx.getArg<std::string>(1);
187 if( !type_str.empty() )
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;
198 extract( rwy ? rwy->getApproaches(type)
199 // no runway specified, report them all
200 : apt.getApproaches(type),
201 &flightgear::Approach::ident )
205 //------------------------------------------------------------------------------
207 f_airport_parking(FGAirport& apt, nasal::CallContext ctx)
209 std::string type = ctx.getArg<std::string>(0);
210 bool only_available = ctx.getArg<bool>(1);
211 FGAirportDynamicsRef dynamics = apt.getDynamics();
212 return dynamics->getParkings(only_available, type);
216 * Extract a SGGeod from a nasal function argument list.
219 * {"lat": <lat-deg>, "lon": <lon-deg>}
220 * geo.Coord.new() (aka. {"_lat": <lat-rad>, "_lon": <lon-rad>})
222 static bool extractGeod(nasal::CallContext& ctx, SGGeod& result)
229 FGPositionedRef pos =
230 ctx.from_nasal<FGPositionedRef>(ctx.requireArg<naRef>(0));
234 result = pos->geod();
239 else if( ctx.isHash(0) )
241 nasal::Hash pos_hash = ctx.requireArg<nasal::Hash>(0);
243 // check for manual latitude / longitude names
244 naRef lat = pos_hash.get("lat"),
245 lon = pos_hash.get("lon");
246 if( naIsNum(lat) && naIsNum(lon) )
248 result = SGGeod::fromDeg( ctx.from_nasal<double>(lon),
249 ctx.from_nasal<double>(lat) );
254 // geo.Coord uses _lat/_lon in radians
255 // TODO should we check if its really a geo.Coord?
256 lat = pos_hash.get("_lat");
257 lon = pos_hash.get("_lon");
258 if( naIsNum(lat) && naIsNum(lon) )
260 result = SGGeod::fromRad( ctx.from_nasal<double>(lon),
261 ctx.from_nasal<double>(lat) );
266 else if( ctx.isNumeric(0) && ctx.isNumeric(1) )
269 result = SGGeod::fromDeg( ctx.requireArg<double>(1),
270 ctx.requireArg<double>(0) );
279 * Extract position from ctx or return current aircraft position if not given.
281 static SGGeod getPosition(nasal::CallContext& ctx)
284 if( !extractGeod(ctx, pos) )
285 pos = globals->get_aircraft_position();
290 //------------------------------------------------------------------------------
291 // Returns Nasal ghost for particular or nearest airport of a <type>, or nil
294 // airportinfo(<id>); e.g. "KSFO"
295 // airportinfo(<type>); type := ("airport"|"seaport"|"heliport")
296 // airportinfo() same as airportinfo("airport")
297 // airportinfo(<lat>, <lon> [, <type>]);
298 static naRef f_airportinfo(nasal::CallContext ctx)
300 SGGeod pos = getPosition(ctx);
303 naRuntimeError(ctx.c, "airportinfo() with invalid function arguments");
305 // optional type/ident
306 std::string ident("airport");
307 if( ctx.isString(0) )
308 ident = ctx.requireArg<std::string>(0);
310 FGAirport::TypeRunwayFilter filter;
311 if( !filter.fromTypeString(ident) )
312 // user provided an <id>, hopefully
313 return ctx.to_nasal(FGAirport::findByIdent(ident));
315 double maxRange = 10000.0; // expose this? or pick a smaller value?
316 return ctx.to_nasal( FGAirport::findClosest(pos, maxRange, &filter) );
320 * findAirportsWithinRange([<position>,] <range-nm> [, type])
322 static naRef f_findAirportsWithinRange(nasal::CallContext ctx)
324 SGGeod pos = getPosition(ctx);
325 double range_nm = ctx.requireArg<double>(0);
327 FGAirport::TypeRunwayFilter filter; // defaults to airports only
328 filter.fromTypeString( ctx.getArg<std::string>(1) );
330 FGPositionedList apts = FGPositioned::findWithinRange(pos, range_nm, &filter);
331 FGPositioned::sortByRange(apts, pos);
333 return ctx.to_nasal(apts);
337 * findAirportsByICAO(<ident/prefix> [, type])
339 static naRef f_findAirportsByICAO(nasal::CallContext ctx)
341 std::string prefix = ctx.requireArg<std::string>(0);
343 FGAirport::TypeRunwayFilter filter; // defaults to airports only
344 filter.fromTypeString( ctx.getArg<std::string>(1) );
346 return ctx.to_nasal( FGPositioned::findAllWithIdent(prefix, &filter, false) );
349 // Returns vector of data hash for navaid of a <type>, nil on error
350 // navaids sorted by ascending distance
351 // navinfo([<lat>,<lon>],[<type>],[<id>])
352 // lat/lon (numeric): use latitude/longitude instead of ac position
353 // type: ("fix"|"vor"|"ndb"|"ils"|"dme"|"tacan"|"any")
354 // id: (partial) id of the fix
356 // navinfo("vor") returns all vors
357 // navinfo("HAM") return all navaids who's name start with "HAM"
358 // navinfo("vor", "HAM") return all vor who's name start with "HAM"
359 //navinfo(34,48,"vor","HAM") return all vor who's name start with "HAM"
360 // sorted by distance relative to lat=34, lon=48
361 static naRef f_navinfo(nasal::CallContext ctx)
363 SGGeod pos = getPosition(ctx);
364 std::string id = ctx.getArg<std::string>(0);
366 FGNavList::TypeFilter filter;
367 if( filter.fromTypeString(id) )
368 id = ctx.getArg<std::string>(1);
369 else if( ctx.argc > 1 )
370 naRuntimeError(ctx.c, "navinfo() already got an ident");
372 return ctx.to_nasal( FGNavList::findByIdentAndFreq(pos, id, 0.0, &filter) );
375 //------------------------------------------------------------------------------
376 static naRef f_findWithinRange(nasal::CallContext ctx)
378 SGGeod pos = getPosition(ctx);
379 double range_nm = ctx.requireArg<double>(0);
381 std::string typeSpec = ctx.getArg<std::string>(1);
382 FGPositioned::TypeFilter filter(FGPositioned::TypeFilter::fromString(typeSpec));
384 FGPositionedList items = FGPositioned::findWithinRange(pos, range_nm, &filter);
385 FGPositioned::sortByRange(items, pos);
386 return ctx.to_nasal(items);
389 static naRef f_findByIdent(nasal::CallContext ctx)
391 std::string prefix = ctx.requireArg<std::string>(0);
392 std::string typeSpec = ctx.getArg<std::string>(1);
393 FGPositioned::TypeFilter filter(FGPositioned::TypeFilter::fromString(typeSpec));
394 bool exact = ctx.getArg<bool>(2, false);
396 return ctx.to_nasal( FGPositioned::findAllWithIdent(prefix, &filter, exact) );
399 static naRef f_findByName(nasal::CallContext ctx)
401 std::string prefix = ctx.requireArg<std::string>(0);
402 std::string typeSpec = ctx.getArg<std::string>(1);
403 FGPositioned::TypeFilter filter(FGPositioned::TypeFilter::fromString(typeSpec));
405 return ctx.to_nasal( FGPositioned::findAllWithName(prefix, &filter, false) );
408 //------------------------------------------------------------------------------
410 static naRef f_courseAndDistance(nasal::CallContext ctx)
412 SGGeod from = globals->get_aircraft_position(), to, pos;
413 bool ok = extractGeod(ctx, pos);
415 naRuntimeError(ctx.c, "invalid arguments to courseAndDistance");
418 if (extractGeod(ctx, to)) {
419 from = pos; // we parsed both FROM and TO args, so first was FROM
421 to = pos; // only parsed one arg, so FROM is current
424 double course, course2, d;
425 SGGeodesy::inverse(from, to, course, course2, d);
427 naRef result = naNewVector(ctx.c);
428 naVec_append(result, naNum(course));
429 naVec_append(result, naNum(d * SG_METER_TO_NM));
433 static naRef f_sortByRange(nasal::CallContext ctx)
435 FGPositionedList items = ctx.requireArg<FGPositionedList>(0);
437 FGPositioned::sortByRange(items, getPosition(ctx));
438 return ctx.to_nasal(items);
441 //------------------------------------------------------------------------------
442 // Get difference between two lists of positioned objects.
444 // For every element in old_list not in new_list the callback cb_remove is
445 // called with the removed element as single argument. For every element in
446 // new_list not in old_list cb_add is called.
448 // diff(old_list, new_list, cb_add[, cb_remove])
451 // # Print all fixes within a distance of 320 to 640 miles
452 // diff( findWithinRange(320, "fix"),
453 // findWithinRange(640, "fix"),
454 // func(p) print('found fix: ', p.id) );
455 static naRef f_diff(nasal::CallContext ctx)
457 typedef simgear::ListDiff<FGPositionedRef> Diff;
458 Diff::List old_list = ctx.requireArg<FGPositionedList>(0),
459 new_list = ctx.requireArg<FGPositionedList>(1);
460 Diff::Callback cb_add = ctx.requireArg<Diff::Callback>(2),
461 cb_rm = ctx.getArg<Diff::Callback>(3);
463 // Note that FGPositionedRef instances are only compared for pointer equality.
464 // As the NavCache caches every queried positioned instance it is guaranteed
465 // that only one instance of every positioned object can exist. Therefore we
466 // can make the comparison faster by just comparing pointers and not also the
468 // (On my machine the difference is 0.27s vs 0.17s)
469 Diff::inplace(old_list, new_list, cb_add, cb_rm);
474 //------------------------------------------------------------------------------
475 naRef initNasalPositioned_cppbind(naRef globalsRef, naContext c)
477 NasalPositioned::init("Positioned")
478 .member("id", &FGPositioned::ident)
479 .member("ident", &FGPositioned::ident) // TODO to we really need id and ident?
480 .member("name", &FGPositioned::name)
481 .member("type", &FGPositioned::typeString)
482 .member("lat", &FGPositioned::latitude)
483 .member("lon", &FGPositioned::longitude)
484 .member("elevation", &FGPositioned::elevationM);
485 NasalRunway::init("Runway")
486 .bases<NasalPositioned>();
487 NasalParking::init("Parking")
488 .bases<NasalPositioned>();
489 NasalCommStation::init("CommStation")
490 .bases<NasalPositioned>()
491 .member("frequency", &flightgear::CommStation::freqMHz);
492 NasalNavRecord::init("Navaid")
493 .bases<NasalPositioned>()
494 .member("frequency", &FGNavRecord::get_freq)
495 .member("range_nm", &FGNavRecord::get_range)
496 .member("course", &f_navaid_course);
498 NasalFix::init("Fix")
499 .bases<NasalPositioned>();
501 NasalAirport::init("FGAirport")
502 .bases<NasalPositioned>()
503 .member("has_metar", &FGAirport::getMetar)
504 .member("runways", &FGAirport::getRunwayMap)
505 .member("helipads", &FGAirport::getHelipadMap)
506 .member("taxiways", &FGAirport::getTaxiways)
507 .member("pavements", &FGAirport::getPavements)
508 .method("runway", &f_airport_runway)
509 .method("helipad", &f_airport_runway)
510 .method("tower", &FGAirport::getTowerLocation)
511 .method("comms", &f_airport_comms)
512 .method("sids", &f_airport_sids)
513 .method("stars", &f_airport_stars)
514 .method("getApproachList", f_airport_approaches)
515 .method("parking", &f_airport_parking)
516 .method("getSid", &FGAirport::findSIDWithIdent)
517 .method("getStar", &FGAirport::findSTARWithIdent)
518 .method("getIAP", &FGAirport::findApproachWithIdent)
519 .method("tostring", &FGAirport::toString);
521 nasal::Hash globals(globalsRef, c),
522 positioned( globals.createHash("positioned") );
524 positioned.set("airportinfo", &f_airportinfo);
525 positioned.set("findAirportsWithinRange", f_findAirportsWithinRange);
526 positioned.set("findAirportsByICAO", &f_findAirportsByICAO);
527 positioned.set("navinfo", &f_navinfo);
529 positioned.set("findWithinRange", &f_findWithinRange);
530 positioned.set("findByIdent", &f_findByIdent);
531 positioned.set("findByName", &f_findByName);
532 positioned.set("courseAndDistance", &f_courseAndDistance);
533 positioned.set("sortByRange", &f_sortByRange);
535 positioned.set("diff", &f_diff);