]> git.mxchange.org Git - flightgear.git/blob - src/Scripting/NasalPositioned.cxx
9127e149cbc87974ce3e7f2a348973d3cdf7b2af
[flightgear.git] / src / Scripting / NasalPositioned.cxx
1 // NasalPositioned.cxx -- expose FGPositioned classes to Nasal
2 //
3 // Written by James Turner, started 2012.
4 //
5 // Copyright (C) 2012 James Turner
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 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24
25 #include <string.h>
26
27 #include "NasalPositioned.hxx"
28
29 #include <boost/foreach.hpp>
30
31 #include <simgear/sg_inlines.h>
32 #include <simgear/scene/material/mat.hxx>
33 #include <simgear/magvar/magvar.hxx>
34 #include <simgear/timing/sg_time.hxx>
35 #include <simgear/bucket/newbucket.hxx>
36
37 #include <Airports/runways.hxx>
38 #include <Airports/simple.hxx>
39 #include <Airports/dynamics.hxx>
40 #include <Airports/parking.hxx>
41 #include <Navaids/navlist.hxx>
42 #include <Navaids/procedure.hxx>
43 #include <Main/globals.hxx>
44 #include <Main/fg_props.hxx>
45 #include <Scenery/scenery.hxx>
46 #include <ATC/CommStation.hxx>
47 #include <Navaids/route.hxx>
48 #include <Autopilot/route_mgr.hxx>
49 #include <Navaids/procedure.hxx>
50
51 static void positionedGhostDestroy(void* g);
52 static void wayptGhostDestroy(void* g);
53 naGhostType PositionedGhostType = { positionedGhostDestroy, "positioned" };
54
55 static const char* airportGhostGetMember(naContext c, void* g, naRef field, naRef* out);
56 naGhostType AirportGhostType = { positionedGhostDestroy, "airport", airportGhostGetMember, 0 };
57
58 static const char* navaidGhostGetMember(naContext c, void* g, naRef field, naRef* out);
59 naGhostType NavaidGhostType = { positionedGhostDestroy, "navaid", navaidGhostGetMember, 0 };
60
61 static const char* runwayGhostGetMember(naContext c, void* g, naRef field, naRef* out);
62 naGhostType RunwayGhostType = { positionedGhostDestroy, "runway", runwayGhostGetMember, 0 };
63
64 static const char* wayptGhostGetMember(naContext c, void* g, naRef field, naRef* out);
65
66 naGhostType WayptGhostType = { wayptGhostDestroy, 
67   "waypoint",
68   wayptGhostGetMember,
69   0};
70
71 static void hashset(naContext c, naRef hash, const char* key, naRef val)
72 {
73   naRef s = naNewString(c);
74   naStr_fromdata(s, (char*)key, strlen(key));
75   naHash_set(hash, s, val);
76 }
77
78 static naRef stringToNasal(naContext c, const std::string& s)
79 {
80     return naStr_fromdata(naNewString(c),
81                    const_cast<char *>(s.c_str()), 
82                    s.length());
83 }
84
85 static FGPositioned* positionedGhost(naRef r)
86 {
87     if (naGhost_type(r) == &PositionedGhostType)
88         return (FGPositioned*) naGhost_ptr(r);
89     return 0;
90 }
91
92 static FGAirport* airportGhost(naRef r)
93 {
94   if (naGhost_type(r) == &AirportGhostType)
95     return (FGAirport*) naGhost_ptr(r);
96   return 0;
97 }
98
99 static FGNavRecord* navaidGhost(naRef r)
100 {
101   if (naGhost_type(r) == &NavaidGhostType)
102     return (FGNavRecord*) naGhost_ptr(r);
103   return 0;
104 }
105
106 static FGRunway* runwayGhost(naRef r)
107 {
108   if (naGhost_type(r) == &RunwayGhostType)
109     return (FGRunway*) naGhost_ptr(r);
110   return 0;
111 }
112
113 static void positionedGhostDestroy(void* g)
114 {
115     FGPositioned* pos = (FGPositioned*)g;
116     if (!FGPositioned::put(pos)) // unref
117         delete pos;
118 }
119
120 static flightgear::Waypt* wayptGhost(naRef r)
121 {
122   if (naGhost_type(r) == &WayptGhostType)
123     return (flightgear::Waypt*) naGhost_ptr(r);
124   return 0;
125 }
126
127 static void wayptGhostDestroy(void* g)
128 {
129   flightgear::Waypt* wpt = (flightgear::Waypt*)g;
130   if (!flightgear::Waypt::put(wpt)) // unref
131     delete wpt;
132 }
133
134 static naRef airportPrototype;
135 static naRef routePrototype;
136 static naRef waypointPrototype;
137 static naRef geoCoordClass;
138
139 naRef ghostForPositioned(naContext c, const FGPositioned* pos)
140 {
141     if (!pos) {
142         return naNil();
143     }
144     
145     FGPositioned::get(pos); // take a ref
146     return naNewGhost(c, &PositionedGhostType, (void*) pos);
147 }
148
149 naRef ghostForAirport(naContext c, const FGAirport* apt)
150 {
151   if (!apt) {
152     return naNil();
153   }
154   
155   FGPositioned::get(apt); // take a ref
156   return naNewGhost2(c, &AirportGhostType, (void*) apt);
157 }
158
159 naRef ghostForNavaid(naContext c, const FGNavRecord* n)
160 {
161   if (!n) {
162     return naNil();
163   }
164   
165   FGPositioned::get(n); // take a ref
166   return naNewGhost2(c, &NavaidGhostType, (void*) n);
167 }
168
169 naRef ghostForRunway(naContext c, const FGRunway* r)
170 {
171   if (!r) {
172     return naNil();
173   }
174   
175   FGPositioned::get(r); // take a ref
176   return naNewGhost2(c, &RunwayGhostType, (void*) r);
177 }
178
179 naRef ghostForWaypt(naContext c, const flightgear::Waypt* wpt)
180 {
181   if (!wpt) {
182     return naNil();
183   }
184   
185   flightgear::Waypt::get(wpt); // take a ref
186   return naNewGhost2(c, &WayptGhostType, (void*) wpt);
187 }
188
189 static const char* airportGhostGetMember(naContext c, void* g, naRef field, naRef* out)
190 {
191   const char* fieldName = naStr_data(field);
192   FGAirport* apt = (FGAirport*) g;
193   
194   if (!strcmp(fieldName, "parents")) {
195     *out = naNewVector(c);
196     naVec_append(*out, airportPrototype);
197   } else if (!strcmp(fieldName, "id")) *out = stringToNasal(c, apt->ident());
198   else if (!strcmp(fieldName, "name")) *out = stringToNasal(c, apt->name());
199   else if (!strcmp(fieldName, "lat")) *out = naNum(apt->getLatitude());
200   else if (!strcmp(fieldName, "lon")) *out = naNum(apt->getLongitude());
201   else if (!strcmp(fieldName, "elevation")) {
202     *out = naNum(apt->getElevation() * SG_FEET_TO_METER);
203   } else if (!strcmp(fieldName, "has_metar")) {
204     *out = naNum(apt->getMetar());
205   } else if (!strcmp(fieldName, "runways")) {
206     *out = naNewHash(c);
207     for(unsigned int r=0; r<apt->numRunways(); ++r) {
208       FGRunway* rwy(apt->getRunwayByIndex(r));
209       naRef rwyid = stringToNasal(c, rwy->ident());
210       naRef rwydata = ghostForRunway(c, rwy);
211       naHash_set(*out, rwyid, rwydata);
212     }
213
214   } else {
215     return 0;
216   }
217   
218   return "";
219 }
220
221 static const char* wayptGhostGetMember(naContext c, void* g, naRef field, naRef* out)
222 {
223   const char* fieldName = naStr_data(field);
224   flightgear::Waypt* wpt = (flightgear::Waypt*) g;
225
226   if (!strcmp(fieldName, "parents")) {
227     *out = naNewVector(c);
228     naVec_append(*out, waypointPrototype);
229   } else if (!strcmp(fieldName, "wp_name")) *out =stringToNasal(c, wpt->ident());
230   else if (!strcmp(fieldName, "wp_type")) *out = stringToNasal(c, wpt->type());
231   else if (!strcmp(fieldName, "wp_lat")) *out = naNum(wpt->position().getLatitudeDeg());
232   else if (!strcmp(fieldName, "wp_lon")) *out = naNum(wpt->position().getLongitudeDeg());
233   else if (!strcmp(fieldName, "wp_parent_name")) {
234     flightgear::Procedure* proc = dynamic_cast<flightgear::Procedure*>(wpt->owner());
235     *out = proc ? stringToNasal(c, proc->ident()) : naNil();
236   } else if (!strcmp(fieldName, "fly_type")) {
237     if (wpt->type() == "hold") {
238       *out = stringToNasal(c, "Hold");
239     } else {
240       *out = stringToNasal(c, wpt->flag(flightgear::WPT_OVERFLIGHT) ? "flyOver" : "flyBy");
241     }
242   } else if (!strcmp(fieldName, "alt_cstr")) *out = naNum(wpt->altitudeFt());
243   else if (!strcmp(fieldName, "speed_cstr")) {
244     double s = (wpt->speedRestriction() == flightgear::SPEED_RESTRICT_MACH) 
245       ? wpt->speedMach() : wpt->speedKts();
246     *out = naNum(s);
247   } else if (!strcmp(fieldName, "leg_distance")) {
248     return "please implement me";
249   } else if (!strcmp(fieldName, "leg_bearing")) {
250     return "please implement me";
251   } else {
252     return NULL; // member not found
253   }
254   
255   return ""; // success
256 }
257
258 static const char* runwayGhostGetMember(naContext c, void* g, naRef field, naRef* out)
259 {
260   const char* fieldName = naStr_data(field);
261   FGRunway* rwy = (FGRunway*) g;
262   
263   if (!strcmp(fieldName, "id")) *out = stringToNasal(c, rwy->ident());
264   else if (!strcmp(fieldName, "lat")) *out = naNum(rwy->latitude());
265   else if (!strcmp(fieldName, "lon")) *out = naNum(rwy->longitude());
266   else if (!strcmp(fieldName, "heading")) *out = naNum(rwy->headingDeg());
267   else if (!strcmp(fieldName, "length")) *out = naNum(rwy->lengthM());
268   else if (!strcmp(fieldName, "width")) *out = naNum(rwy->widthM());
269   else if (!strcmp(fieldName, "threshold")) *out = naNum(rwy->displacedThresholdM());
270   else if (!strcmp(fieldName, "stopway")) *out = naNum(rwy->stopwayM());
271   else if (!strcmp(fieldName, "ils_frequency_mhz")) {
272     *out = rwy->ILS() ? naNum(rwy->ILS()->get_freq() / 100.0) : naNil();
273   } else if (!strcmp(fieldName, "ils")) {
274     *out = ghostForNavaid(c, rwy->ILS());
275   } else {
276     return 0;
277   }
278   
279   return "";
280 }
281
282 static const char* navaidGhostGetMember(naContext c, void* g, naRef field, naRef* out)
283 {
284   const char* fieldName = naStr_data(field);
285   FGNavRecord* nav = (FGNavRecord*) g;
286   
287   if (!strcmp(fieldName, "id")) *out = stringToNasal(c, nav->ident());
288   else if (!strcmp(fieldName, "name")) *out = stringToNasal(c, nav->name());
289   else if (!strcmp(fieldName, "lat")) *out = naNum(nav->get_lat());
290   else if (!strcmp(fieldName, "lon")) *out = naNum(nav->get_lon());
291   else if (!strcmp(fieldName, "elevation")) {
292     *out = naNum(nav->get_elev_ft() * SG_FEET_TO_METER);
293   } else if (!strcmp(fieldName, "type")) {
294     *out = stringToNasal(c, nav->nameForType(nav->type()));
295   } else if (!strcmp(fieldName, "frequency")) {
296     *out = naNum(nav->get_freq()); 
297   } else if (!strcmp(fieldName, "course")) {
298     if ((nav->type() == FGPositioned::ILS) || (nav->type() == FGPositioned::LOC)) {
299       double radial = nav->get_multiuse();
300       SG_NORMALIZE_RANGE(radial, 0.0, 360.0);
301       *out = naNum(radial);
302     } else {
303       *out = naNil();
304     }
305   } else {
306     return 0;
307   }
308   
309   return "";
310 }
311
312 static bool hashIsCoord(naRef h)
313 {
314   naRef parents = naHash_cget(h, (char*) "parents");
315   if (!naIsVector(parents)) {
316     return false;
317   }
318   
319   return naEqual(naVec_get(parents, 0), geoCoordClass);
320 }
321
322 bool geodFromHash(naRef ref, SGGeod& result)
323 {
324   if (!naIsHash(ref)) {
325     return false;
326   }
327   
328 // first, see if the hash contains a FGPositioned ghost - in which case
329 // we can read off its position directly
330   naRef posGhost = naHash_cget(ref, (char*) "_positioned");
331   if (!naIsNil(posGhost)) {
332     FGPositioned* pos = positionedGhost(posGhost);
333     result = pos->geod();
334     return true;
335   }
336   
337 // then check for manual latitude / longitude names
338   naRef lat = naHash_cget(ref, (char*) "lat");
339   naRef lon = naHash_cget(ref, (char*) "lon");
340   if (naIsNum(lat) && naIsNum(lon)) {
341     result = SGGeod::fromDeg(naNumValue(lon).num, naNumValue(lat).num);
342     return true;
343   }
344   
345   if (hashIsCoord(ref)) {
346     naRef lat = naHash_cget(ref, (char*) "_lat");
347     naRef lon = naHash_cget(ref, (char*) "_lon");
348     if (naIsNum(lat) && naIsNum(lon)) {
349       result = SGGeod::fromRad(naNumValue(lon).num, naNumValue(lat).num);
350       return true;
351     }
352   }
353     
354 // check for any synonyms?
355     // latitude + longitude?
356   
357   return false;
358 }
359
360 static int geodFromArgs(naRef* args, int offset, int argc, SGGeod& result)
361 {
362   if (offset >= argc) {
363     return 0;
364   }
365   
366   if (naIsGhost(args[offset])) {
367     naGhostType* gt = naGhost_type(args[offset]);
368     if (gt == &AirportGhostType) {
369       result = airportGhost(args[offset])->geod();
370       return 1;
371     }
372     
373     if (gt == &NavaidGhostType) {
374       result = navaidGhost(args[offset])->geod();
375       return 1;
376     }
377     
378     if (gt == &RunwayGhostType) {
379       result = runwayGhost(args[offset])->geod();
380       return 1;
381     }
382     
383     if (gt == &WayptGhostType) {
384       result = wayptGhost(args[offset])->position();
385       return 1;
386     }
387   }
388   
389   if (geodFromHash(args[offset], result)) {
390     return 1;
391   }
392   
393   if (((argc - offset) >= 2) && naIsNum(args[offset]) && naIsNum(args[offset + 1])) {
394     double lat = naNumValue(args[0]).num,
395     lon = naNumValue(args[1]).num;
396     result = SGGeod::fromDeg(lon, lat);
397     return 2;
398   }
399   
400   return 0;
401 }
402
403 // Convert a cartesian point to a geodetic lat/lon/altitude.
404 static naRef f_carttogeod(naContext c, naRef me, int argc, naRef* args)
405 {
406   double lat, lon, alt, xyz[3];
407   if(argc != 3) naRuntimeError(c, "carttogeod() expects 3 arguments");
408   for(int i=0; i<3; i++)
409     xyz[i] = naNumValue(args[i]).num;
410   sgCartToGeod(xyz, &lat, &lon, &alt);
411   lat *= SG_RADIANS_TO_DEGREES;
412   lon *= SG_RADIANS_TO_DEGREES;
413   naRef vec = naNewVector(c);
414   naVec_append(vec, naNum(lat));
415   naVec_append(vec, naNum(lon));
416   naVec_append(vec, naNum(alt));
417   return vec;
418 }
419
420 // Convert a geodetic lat/lon/altitude to a cartesian point.
421 static naRef f_geodtocart(naContext c, naRef me, int argc, naRef* args)
422 {
423   if(argc != 3) naRuntimeError(c, "geodtocart() expects 3 arguments");
424   double lat = naNumValue(args[0]).num * SG_DEGREES_TO_RADIANS;
425   double lon = naNumValue(args[1]).num * SG_DEGREES_TO_RADIANS;
426   double alt = naNumValue(args[2]).num;
427   double xyz[3];
428   sgGeodToCart(lat, lon, alt, xyz);
429   naRef vec = naNewVector(c);
430   naVec_append(vec, naNum(xyz[0]));
431   naVec_append(vec, naNum(xyz[1]));
432   naVec_append(vec, naNum(xyz[2]));
433   return vec;
434 }
435
436 // For given geodetic point return array with elevation, and a material data
437 // hash, or nil if there's no information available (tile not loaded). If
438 // information about the material isn't available, then nil is returned instead
439 // of the hash.
440 static naRef f_geodinfo(naContext c, naRef me, int argc, naRef* args)
441 {
442 #define HASHSET(s,l,n) naHash_set(matdata, naStr_fromdata(naNewString(c),s,l),n)
443   if(argc < 2 || argc > 3)
444     naRuntimeError(c, "geodinfo() expects 2 or 3 arguments: lat, lon [, maxalt]");
445   double lat = naNumValue(args[0]).num;
446   double lon = naNumValue(args[1]).num;
447   double elev = argc == 3 ? naNumValue(args[2]).num : 10000;
448   const SGMaterial *mat;
449   SGGeod geod = SGGeod::fromDegM(lon, lat, elev);
450   if(!globals->get_scenery()->get_elevation_m(geod, elev, &mat))
451     return naNil();
452   naRef vec = naNewVector(c);
453   naVec_append(vec, naNum(elev));
454   naRef matdata = naNil();
455   if(mat) {
456     matdata = naNewHash(c);
457     naRef names = naNewVector(c);
458     BOOST_FOREACH(const std::string& n, mat->get_names())
459       naVec_append(names, stringToNasal(c, n));
460       
461     HASHSET("names", 5, names);
462     HASHSET("solid", 5, naNum(mat->get_solid()));
463     HASHSET("friction_factor", 15, naNum(mat->get_friction_factor()));
464     HASHSET("rolling_friction", 16, naNum(mat->get_rolling_friction()));
465     HASHSET("load_resistance", 15, naNum(mat->get_load_resistance()));
466     HASHSET("bumpiness", 9, naNum(mat->get_bumpiness()));
467     HASHSET("light_coverage", 14, naNum(mat->get_light_coverage()));
468   }
469   naVec_append(vec, matdata);
470   return vec;
471 #undef HASHSET
472 }
473
474
475 class AirportInfoFilter : public FGAirport::AirportFilter
476 {
477 public:
478   AirportInfoFilter() : type(FGPositioned::AIRPORT) {
479   }
480   
481   bool fromArg(naRef arg)
482   {
483     const char *s = naStr_data(arg);
484     if(!strcmp(s, "airport")) type = FGPositioned::AIRPORT;
485     else if(!strcmp(s, "seaport")) type = FGPositioned::SEAPORT;
486     else if(!strcmp(s, "heliport")) type = FGPositioned::HELIPORT;
487     else
488       return false;
489     
490     return true;
491   }
492   
493   virtual FGPositioned::Type minType() const {
494     return type;
495   }
496   
497   virtual FGPositioned::Type maxType() const {
498     return type;
499   }
500   
501   FGPositioned::Type type;
502 };
503
504 // Returns data hash for particular or nearest airport of a <type>, or nil
505 // on error.
506 //
507 // airportinfo(<id>);                   e.g. "KSFO"
508 // airportinfo(<type>);                 type := ("airport"|"seaport"|"heliport")
509 // airportinfo()                        same as  airportinfo("airport")
510 // airportinfo(<lat>, <lon> [, <type>]);
511 static naRef f_airportinfo(naContext c, naRef me, int argc, naRef* args)
512 {
513   SGGeod pos = globals->get_aircraft_position();
514   FGAirport* apt = NULL;
515   
516   if(argc >= 2 && naIsNum(args[0]) && naIsNum(args[1])) {
517     pos = SGGeod::fromDeg(args[1].num, args[0].num);
518     args += 2;
519     argc -= 2;
520   }
521   
522   double maxRange = 10000.0; // expose this? or pick a smaller value?
523   
524   AirportInfoFilter filter; // defaults to airports only
525   
526   if(argc == 0) {
527     // fall through and use AIRPORT
528   } else if(argc == 1 && naIsString(args[0])) {
529     if (filter.fromArg(args[0])) {
530       // done!
531     } else {
532       // user provided an <id>, hopefully
533       apt = FGAirport::findByIdent(naStr_data(args[0]));
534       if (!apt) {
535         // return nil here, but don't raise a runtime error; this is a
536         // legitamate way to validate an ICAO code, for example in a
537         // dialog box or similar.
538         return naNil();
539       }
540     }
541   } else {
542     naRuntimeError(c, "airportinfo() with invalid function arguments");
543     return naNil();
544   }
545   
546   if(!apt) {
547     apt = FGAirport::findClosest(pos, maxRange, &filter);
548     if(!apt) return naNil();
549   }
550   
551   return ghostForAirport(c, apt);
552 }
553
554 static naRef f_findAirportsWithinRange(naContext c, naRef me, int argc, naRef* args)
555 {
556   int argOffset = 0;
557   SGGeod pos = globals->get_aircraft_position();
558   argOffset += geodFromArgs(args, 0, argc, pos);
559   
560   if (!naIsNum(args[argOffset])) {
561     naRuntimeError(c, "findAirportsWithinRange expected range (in nm) as arg %d", argOffset);
562   }
563   
564   AirportInfoFilter filter; // defaults to airports only
565   double rangeNm = args[argOffset++].num;
566   if (argOffset < argc) {
567     filter.fromArg(args[argOffset++]);
568   }
569   
570   naRef r = naNewVector(c);
571   
572   FGPositioned::List apts = FGPositioned::findWithinRange(pos, rangeNm, &filter);
573   FGPositioned::sortByRange(apts, pos);
574   
575   BOOST_FOREACH(FGPositionedRef a, apts) {
576     FGAirport* apt = (FGAirport*) a.get();
577     naVec_append(r, ghostForAirport(c, apt));
578   }
579   
580   return r;
581 }
582
583 static naRef f_findAirportsByICAO(naContext c, naRef me, int argc, naRef* args)
584 {
585   if (!naIsString(args[0])) {
586     naRuntimeError(c, "findAirportsByICAO expects string as arg 0");
587   }
588   
589   int argOffset = 0;
590   string prefix(naStr_data(args[argOffset++]));
591   AirportInfoFilter filter; // defaults to airports only
592   if (argOffset < argc) {
593     filter.fromArg(args[argOffset++]);
594   }
595   
596   naRef r = naNewVector(c);
597   
598   FGPositioned::List apts = FGPositioned::findAllWithIdent(prefix, &filter, false);
599   
600   BOOST_FOREACH(FGPositionedRef a, apts) {
601     FGAirport* apt = (FGAirport*) a.get();
602     naVec_append(r, ghostForAirport(c, apt));
603   }
604   
605   return r;
606 }
607
608 static naRef f_airport_tower(naContext c, naRef me, int argc, naRef* args)
609 {
610     FGAirport* apt = airportGhost(me);
611     if (!apt) {
612       naRuntimeError(c, "airport.tower called on non-airport object");
613     }
614   
615     // build a hash for the tower position    
616     SGGeod towerLoc = apt->getTowerLocation();
617     naRef tower = naNewHash(c);
618     hashset(c, tower, "lat", naNum(towerLoc.getLatitudeDeg()));
619     hashset(c, tower, "lon", naNum(towerLoc.getLongitudeDeg()));
620     hashset(c, tower, "elevation", naNum(towerLoc.getElevationM()));
621     return tower;
622 }
623
624 static naRef f_airport_comms(naContext c, naRef me, int argc, naRef* args)
625 {
626     FGAirport* apt = airportGhost(me);
627     if (!apt) {
628       naRuntimeError(c, "airport.comms called on non-airport object");
629     }
630     naRef comms = naNewVector(c);
631     
632 // if we have an explicit type, return a simple vector of frequencies
633     if (argc > 0 && naIsScalar(args[0])) {
634         std::string commName = naStr_data(args[0]);
635         FGPositioned::Type commType = FGPositioned::typeFromName(commName);
636         
637         BOOST_FOREACH(flightgear::CommStation* comm, apt->commStationsOfType(commType)) {
638             naVec_append(comms, naNum(comm->freqMHz()));
639         }
640     } else {
641 // otherwise return a vector of hashes, one for each comm station.
642         BOOST_FOREACH(flightgear::CommStation* comm, apt->commStations()) {
643             naRef commHash = naNewHash(c);
644             hashset(c, commHash, "frequency", naNum(comm->freqMHz()));
645             hashset(c, commHash, "ident", stringToNasal(c, comm->ident()));
646             naVec_append(comms, commHash);
647         }
648     }
649     
650     return comms;
651 }
652
653 static naRef f_airport_runway(naContext c, naRef me, int argc, naRef* args)
654 {
655   FGAirport* apt = airportGhost(me);
656   if (!apt) {
657     naRuntimeError(c, "airport.runway called on non-airport object");
658   }
659   
660   if ((argc < 1) || !naIsString(args[0])) {
661     naRuntimeError(c, "airport.runway expects a runway ident argument");
662   }
663   
664   std::string ident(naStr_data(args[0]));
665   if (!apt->hasRunwayWithIdent(ident)) {
666     return naNil();
667   }
668   
669   return ghostForRunway(c, apt->getRunwayByIdent(ident));
670 }
671
672 static naRef f_airport_sids(naContext c, naRef me, int argc, naRef* args)
673 {
674   FGAirport* apt = airportGhost(me);
675   if (!apt) {
676     naRuntimeError(c, "airport.sids called on non-airport object");
677   }
678   
679   naRef sids = naNewVector(c);
680   
681   if (argc > 0 && naIsString(args[0])) {
682     if (!apt->hasRunwayWithIdent(naStr_data(args[0]))) {
683       return naNil();
684     }
685
686     FGRunway* rwy = apt->getRunwayByIdent(naStr_data(args[0]));
687     BOOST_FOREACH(flightgear::SID* sid, rwy->getSIDs()) {
688       naRef procId = stringToNasal(c, sid->ident());
689       naVec_append(sids, procId);
690     }
691   } else {
692     for (unsigned int s=0; s<apt->numSIDs(); ++s) {
693       flightgear::SID* sid = apt->getSIDByIndex(s);
694       naRef procId = stringToNasal(c, sid->ident());
695       naVec_append(sids, procId);
696     }
697   }
698   
699   return sids;
700 }
701
702 static naRef f_airport_stars(naContext c, naRef me, int argc, naRef* args)
703 {
704   FGAirport* apt = airportGhost(me);
705   if (!apt) {
706     naRuntimeError(c, "airport.stars called on non-airport object");
707   }
708   
709   naRef stars = naNewVector(c);
710   
711   if (argc > 0 && naIsString(args[0])) {
712     if (!apt->hasRunwayWithIdent(naStr_data(args[0]))) {
713       return naNil();
714     }
715         
716     FGRunway* rwy = apt->getRunwayByIdent(naStr_data(args[0]));
717     BOOST_FOREACH(flightgear::STAR* s, rwy->getSTARs()) {
718       naRef procId = stringToNasal(c, s->ident());
719       naVec_append(stars, procId);
720     }
721   } else {
722     for (unsigned int s=0; s<apt->numSTARs(); ++s) {
723       flightgear::STAR* star = apt->getSTARByIndex(s);
724       naRef procId = stringToNasal(c, star->ident());
725       naVec_append(stars, procId);
726     }
727   }
728   
729   return stars;
730 }
731
732 static naRef f_airport_parking(naContext c, naRef me, int argc, naRef* args)
733 {
734   FGAirport* apt = airportGhost(me);
735   if (!apt) {
736     naRuntimeError(c, "airport.parking called on non-airport object");
737   }
738   
739   naRef r = naNewVector(c);
740   std::string type;
741   bool onlyAvailable = false;
742   
743   if (argc > 0 && naIsString(args[0])) {
744     type = naStr_data(args[0]);
745   }
746   
747   if ((argc > 1) && naIsNum(args[1])) {
748     onlyAvailable = (args[1].num != 0.0);
749   }
750   
751   FGAirportDynamics* dynamics = apt->getDynamics();
752   for (int i=0; i<dynamics->getNrOfParkings(); ++i) {
753     FGParking* park = dynamics->getParking(i);
754   // filter out based on availability and type
755     if (onlyAvailable && !park->isAvailable()) {
756       continue;
757     }
758     
759     if (!type.empty() && (park->getType() != type)) {
760       continue;
761     }
762     
763     naRef nm = stringToNasal(c, park->getName());
764     naVec_append(r, nm);
765   }
766   
767   return r;
768 }
769
770 // Returns vector of data hash for navaid of a <type>, nil on error
771 // navaids sorted by ascending distance 
772 // navinfo([<lat>,<lon>],[<type>],[<id>])
773 // lat/lon (numeric): use latitude/longitude instead of ac position
774 // type:              ("fix"|"vor"|"ndb"|"ils"|"dme"|"tacan"|"any")
775 // id:                (partial) id of the fix
776 // examples:
777 // navinfo("vor")     returns all vors
778 // navinfo("HAM")     return all navaids who's name start with "HAM"
779 // navinfo("vor", "HAM") return all vor who's name start with "HAM"
780 //navinfo(34,48,"vor","HAM") return all vor who's name start with "HAM" 
781 //                           sorted by distance relative to lat=34, lon=48
782 static naRef f_navinfo(naContext c, naRef me, int argc, naRef* args)
783 {
784   SGGeod pos;
785   
786   if(argc >= 2 && naIsNum(args[0]) && naIsNum(args[1])) {
787     pos = SGGeod::fromDeg(args[1].num, args[0].num);
788     args += 2;
789     argc -= 2;
790   } else {
791     pos = globals->get_aircraft_position();
792   }
793   
794   FGPositioned::Type type = FGPositioned::INVALID;
795   nav_list_type navlist;
796   const char * id = "";
797   
798   if(argc > 0 && naIsString(args[0])) {
799     const char *s = naStr_data(args[0]);
800     if(!strcmp(s, "any")) type = FGPositioned::INVALID;
801     else if(!strcmp(s, "fix")) type = FGPositioned::FIX;
802     else if(!strcmp(s, "vor")) type = FGPositioned::VOR;
803     else if(!strcmp(s, "ndb")) type = FGPositioned::NDB;
804     else if(!strcmp(s, "ils")) type = FGPositioned::ILS;
805     else if(!strcmp(s, "dme")) type = FGPositioned::DME;
806     else if(!strcmp(s, "tacan")) type = FGPositioned::TACAN;
807     else id = s; // this is an id
808     ++args;
809     --argc;
810   } 
811   
812   if(argc > 0 && naIsString(args[0])) {
813     if( *id != 0 ) {
814       naRuntimeError(c, "navinfo() called with navaid id");
815       return naNil();
816     }
817     id = naStr_data(args[0]);
818     ++args;
819     --argc;
820   }
821   
822   if( argc > 0 ) {
823     naRuntimeError(c, "navinfo() called with too many arguments");
824     return naNil();
825   }
826   
827   navlist = globals->get_navlist()->findByIdentAndFreq( pos, id, 0.0, type );
828   
829   naRef reply = naNewVector(c);
830   for( nav_list_type::const_iterator it = navlist.begin(); it != navlist.end(); ++it ) {
831     naVec_append( reply, ghostForNavaid(c, *it) );
832   }
833   return reply;
834 }
835
836 static naRef f_findNavaidsWithinRange(naContext c, naRef me, int argc, naRef* args)
837 {
838   int argOffset = 0;
839   SGGeod pos = globals->get_aircraft_position();
840   argOffset += geodFromArgs(args, 0, argc, pos);
841   
842   if (!naIsNum(args[argOffset])) {
843     naRuntimeError(c, "findNavaidsWithinRange expected range (in nm) as arg %d", argOffset);
844   }
845   
846   FGPositioned::Type type = FGPositioned::INVALID;
847   double rangeNm = args[argOffset++].num;
848   if (argOffset < argc) {
849     type = FGPositioned::typeFromName(naStr_data(args[argOffset]));
850   }
851   
852   naRef r = naNewVector(c);
853   FGNavList::TypeFilter filter(type);
854   FGPositioned::List navs = FGPositioned::findWithinRange(pos, rangeNm, &filter);
855   FGPositioned::sortByRange(navs, pos);
856   
857   BOOST_FOREACH(FGPositionedRef a, navs) {
858     FGNavRecord* nav = (FGNavRecord*) a.get();
859     naVec_append(r, ghostForNavaid(c, nav));
860   }
861   
862   return r;
863 }
864
865 static naRef f_findNavaidByFrequency(naContext c, naRef me, int argc, naRef* args)
866 {
867   int argOffset = 0;
868   SGGeod pos = globals->get_aircraft_position();
869   argOffset += geodFromArgs(args, 0, argc, pos);
870   
871   if (!naIsNum(args[argOffset])) {
872     naRuntimeError(c, "findNavaidByFrequency expectes frequency (in Mhz) as arg %d", argOffset);
873   }
874   
875   FGPositioned::Type type = FGPositioned::INVALID;
876   double freqMhz = args[argOffset++].num;
877   if (argOffset < argc) {
878     type = FGPositioned::typeFromName(naStr_data(args[argOffset]));
879   }
880   
881   nav_list_type navs = globals->get_navlist()->findAllByFreq(freqMhz, pos, type);
882   if (navs.empty()) {
883     return naNil();
884   }
885   
886   return ghostForNavaid(c, navs.front().ptr());
887 }
888
889 static naRef f_findNavaidsByFrequency(naContext c, naRef me, int argc, naRef* args)
890 {
891   int argOffset = 0;
892   SGGeod pos = globals->get_aircraft_position();
893   argOffset += geodFromArgs(args, 0, argc, pos);
894   
895   if (!naIsNum(args[argOffset])) {
896     naRuntimeError(c, "findNavaidsByFrequency expectes frequency (in Mhz) as arg %d", argOffset);
897   }
898   
899   FGPositioned::Type type = FGPositioned::INVALID;
900   double freqMhz = args[argOffset++].num;
901   if (argOffset < argc) {
902     type = FGPositioned::typeFromName(naStr_data(args[argOffset]));
903   }
904   
905   naRef r = naNewVector(c);
906   nav_list_type navs = globals->get_navlist()->findAllByFreq(freqMhz, pos, type);
907   
908   BOOST_FOREACH(nav_rec_ptr a, navs) {
909     naVec_append(r, ghostForNavaid(c, a.ptr()));
910   }
911   
912   return r;
913 }
914
915 static naRef f_findNavaidsByIdent(naContext c, naRef me, int argc, naRef* args)
916 {
917   int argOffset = 0;
918   SGGeod pos = globals->get_aircraft_position();
919   argOffset += geodFromArgs(args, 0, argc, pos);
920   
921   if (!naIsString(args[argOffset])) {
922     naRuntimeError(c, "findNavaidsByIdent expectes ident string as arg %d", argOffset);
923   }
924   
925   FGPositioned::Type type = FGPositioned::INVALID;
926   string ident = naStr_data(args[argOffset++]);
927   if (argOffset < argc) {
928     type = FGPositioned::typeFromName(naStr_data(args[argOffset]));
929   }
930   
931   naRef r = naNewVector(c);
932   nav_list_type navs = globals->get_navlist()->findByIdentAndFreq(pos, ident, 0.0, type);
933   
934   BOOST_FOREACH(nav_rec_ptr a, navs) {
935     naVec_append(r, ghostForNavaid(c, a.ptr()));
936   }
937   
938   return r;
939 }
940
941
942 // Convert a cartesian point to a geodetic lat/lon/altitude.
943 static naRef f_magvar(naContext c, naRef me, int argc, naRef* args)
944 {
945   SGGeod pos = globals->get_aircraft_position();
946   if (argc == 0) {
947     // fine, use aircraft position
948   } else if (geodFromArgs(args, 0, argc, pos)) {
949     // okay
950   } else {
951     naRuntimeError(c, "magvar() expects no arguments, a positioned hash or lat,lon pair");
952   }
953   
954   double jd = globals->get_time_params()->getJD();
955   double magvarDeg = sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
956   return naNum(magvarDeg);
957 }
958
959 static naRef f_courseAndDistance(naContext c, naRef me, int argc, naRef* args)
960 {
961     SGGeod from = globals->get_aircraft_position(), to, p;
962     int argOffset = geodFromArgs(args, 0, argc, p);
963     if (geodFromArgs(args, argOffset, argc, to)) {
964       from = p; // we parsed both FROM and TO args, so first was from
965     } else {
966       to = p; // only parsed one arg, so FROM is current
967     }
968   
969     if (argOffset == 0) {
970         naRuntimeError(c, "invalid arguments to courseAndDistance");
971     }
972     
973     double course, course2, d;
974     SGGeodesy::inverse(from, to, course, course2, d);
975     
976     naRef result = naNewVector(c);
977     naVec_append(result, naNum(course));
978     naVec_append(result, naNum(d * SG_METER_TO_NM));
979     return result;
980 }
981
982 static naRef f_greatCircleMove(naContext c, naRef me, int argc, naRef* args)
983 {
984   SGGeod from = globals->get_aircraft_position(), to;
985   int argOffset = 0;
986   
987   // complication - don't inerpret two doubles (as the only args)
988   // as a lat,lon pair - only do so if we have at least three args.
989   if (argc > 2) {
990     argOffset = geodFromArgs(args, 0, argc, from);
991   }
992   
993   if ((argOffset + 1) >= argc) {
994     naRuntimeError(c, "isufficent arguments to greatCircleMove");
995   }
996   
997   if (!naIsNum(args[argOffset]) || !naIsNum(args[argOffset+1])) {
998     naRuntimeError(c, "invalid arguments %d and %d to greatCircleMove",
999                    argOffset, argOffset + 1);
1000   }
1001   
1002   double course = args[argOffset].num, course2;
1003   double distanceNm = args[argOffset + 1].num;
1004   SGGeodesy::direct(from, course, distanceNm * SG_NM_TO_METER, to, course2);
1005   
1006   // return geo.Coord
1007   naRef coord = naNewHash(c);
1008   hashset(c, coord, "lat", naNum(to.getLatitudeDeg()));
1009   hashset(c, coord, "lon", naNum(to.getLongitudeDeg()));
1010   return coord;
1011 }
1012
1013 static naRef f_tilePath(naContext c, naRef me, int argc, naRef* args)
1014 {
1015     SGGeod pos = globals->get_aircraft_position();
1016     geodFromArgs(args, 0, argc, pos);
1017     SGBucket b(pos);
1018     return stringToNasal(c, b.gen_base_path());
1019 }
1020
1021 static naRef f_tileIndex(naContext c, naRef me, int argc, naRef* args)
1022 {
1023   SGGeod pos = globals->get_aircraft_position();
1024   geodFromArgs(args, 0, argc, pos);
1025   SGBucket b(pos);
1026   return naNum(b.gen_index());
1027 }
1028
1029 static naRef f_route(naContext c, naRef me, int argc, naRef* args)
1030 {
1031   naRef route = naNewHash(c);
1032   
1033   // return active route hash by default,
1034   // other routes in the future
1035   
1036   naRef parents = naNewVector(c);
1037   naVec_append(parents, routePrototype);
1038   hashset(c, route, "parents", parents);
1039   
1040   return route;
1041 }
1042
1043 static naRef f_route_getWP(naContext c, naRef me, int argc, naRef* args)
1044 {
1045   FGRouteMgr* rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
1046   
1047   int index;
1048   if (argc == 0) {
1049     index = rm->currentIndex();
1050   } else {
1051     index = (int) naNumValue(args[0]).num;
1052   }
1053   
1054   if ((index < 0) || (index >= rm->numWaypts())) {
1055     return naNil();
1056   }
1057   
1058   return ghostForWaypt(c, rm->wayptAtIndex(index));
1059 }
1060
1061 static naRef f_route_currentWP(naContext c, naRef me, int argc, naRef* args)
1062 {
1063   FGRouteMgr* rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
1064   return ghostForWaypt(c, rm->currentWaypt());
1065 }
1066
1067 static naRef f_route_nextWP(naContext c, naRef me, int argc, naRef* args)
1068 {
1069   FGRouteMgr* rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
1070   flightgear::WayptRef wp = rm->nextWaypt();
1071   if (!wp) {
1072     return naNil();
1073   }
1074   return ghostForWaypt(c, wp);
1075 }
1076
1077 static naRef f_route_currentIndex(naContext c, naRef me, int argc, naRef* args)
1078 {
1079   FGRouteMgr* rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
1080   return naNum(rm->currentIndex());
1081 }
1082
1083 static naRef f_route_numWaypoints(naContext c, naRef me, int argc, naRef* args)
1084 {
1085   FGRouteMgr* rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
1086   return naNum(rm->numWaypts());
1087 }
1088
1089 static naRef f_waypoint_navaid(naContext c, naRef me, int argc, naRef* args)
1090 {
1091   flightgear::Waypt* w = wayptGhost(me);
1092   if (!w) {
1093     naRuntimeError(c, "waypoint.navaid called on non-waypoint object");
1094   }
1095   
1096   FGPositioned* pos = w->source();
1097   if (!pos) {
1098     return naNil();
1099   }
1100   
1101   switch (pos->type()) {
1102   case FGPositioned::VOR:
1103   case FGPositioned::NDB:
1104   case FGPositioned::ILS:
1105   case FGPositioned::LOC:
1106   case FGPositioned::GS:
1107   case FGPositioned::DME:
1108   case FGPositioned::TACAN: {
1109     FGNavRecord* nav = (FGNavRecord*) pos;
1110     return ghostForNavaid(c, nav);
1111   }
1112       
1113   default:
1114     return naNil();
1115   }
1116 }
1117
1118 static naRef f_waypoint_airport(naContext c, naRef me, int argc, naRef* args)
1119 {
1120   flightgear::Waypt* w = wayptGhost(me);
1121   if (!w) {
1122     naRuntimeError(c, "waypoint.navaid called on non-waypoint object");
1123   }
1124   
1125   FGPositioned* pos = w->source();
1126   if (!pos || FGAirport::isAirportType(pos)) {
1127     return naNil();
1128   }
1129   
1130   return ghostForAirport(c, (FGAirport*) pos);
1131 }
1132
1133 static naRef f_waypoint_runway(naContext c, naRef me, int argc, naRef* args)
1134 {
1135   flightgear::Waypt* w = wayptGhost(me);
1136   if (!w) {
1137     naRuntimeError(c, "waypoint.navaid called on non-waypoint object");
1138   }
1139   
1140   FGPositioned* pos = w->source();
1141   if (!pos || (pos->type() != FGPositioned::RUNWAY)) {
1142     return naNil();
1143   }
1144   
1145   return ghostForRunway(c, (FGRunway*) pos);
1146 }
1147
1148 // Table of extension functions.  Terminate with zeros.
1149 static struct { const char* name; naCFunction func; } funcs[] = {
1150   { "carttogeod", f_carttogeod },
1151   { "geodtocart", f_geodtocart },
1152   { "geodinfo", f_geodinfo },
1153   { "airportinfo", f_airportinfo },
1154   { "findAirportsWithinRange", f_findAirportsWithinRange },
1155   { "findAirportsByICAO", f_findAirportsByICAO },
1156   { "navinfo", f_navinfo },
1157   { "findNavaidsWithinRange", f_findNavaidsWithinRange },
1158   { "findNavaidByFrequency", f_findNavaidByFrequency },
1159   { "findNavaidsByFrequency", f_findNavaidsByFrequency },
1160   { "findNavaidsByID", f_findNavaidsByIdent },
1161   { "route", f_route },
1162   { "magvar", f_magvar },
1163   { "courseAndDistance", f_courseAndDistance },
1164   { "greatCircleMove", f_greatCircleMove },
1165   { "tileIndex", f_tileIndex },
1166   { "tilePath", f_tilePath },
1167   { 0, 0 }
1168 };
1169
1170
1171 naRef initNasalPositioned(naRef globals, naContext c, naRef gcSave)
1172 {
1173     airportPrototype = naNewHash(c);
1174     hashset(c, gcSave, "airportProto", airportPrototype);
1175   
1176     hashset(c, airportPrototype, "runway", naNewFunc(c, naNewCCode(c, f_airport_runway)));
1177     hashset(c, airportPrototype, "tower", naNewFunc(c, naNewCCode(c, f_airport_tower)));
1178     hashset(c, airportPrototype, "comms", naNewFunc(c, naNewCCode(c, f_airport_comms)));
1179     hashset(c, airportPrototype, "sids", naNewFunc(c, naNewCCode(c, f_airport_sids)));
1180     hashset(c, airportPrototype, "stars", naNewFunc(c, naNewCCode(c, f_airport_stars)));
1181     hashset(c, airportPrototype, "parking", naNewFunc(c, naNewCCode(c, f_airport_parking)));
1182   
1183     routePrototype = naNewHash(c);
1184     hashset(c, gcSave, "routeProto", routePrototype);
1185       
1186     hashset(c, routePrototype, "getWP", naNewFunc(c, naNewCCode(c, f_route_getWP)));
1187     hashset(c, routePrototype, "currentWP", naNewFunc(c, naNewCCode(c, f_route_currentWP))); 
1188     hashset(c, routePrototype, "nextWP", naNewFunc(c, naNewCCode(c, f_route_nextWP))); 
1189     hashset(c, routePrototype, "currentIndex", naNewFunc(c, naNewCCode(c, f_route_currentIndex)));
1190     hashset(c, routePrototype, "getPlanSize", naNewFunc(c, naNewCCode(c, f_route_numWaypoints)));
1191     
1192     waypointPrototype = naNewHash(c);
1193     hashset(c, gcSave, "wayptProto", waypointPrototype);
1194     
1195     hashset(c, waypointPrototype, "navaid", naNewFunc(c, naNewCCode(c, f_waypoint_navaid)));
1196     hashset(c, waypointPrototype, "runway", naNewFunc(c, naNewCCode(c, f_waypoint_runway)));
1197     hashset(c, waypointPrototype, "airport", naNewFunc(c, naNewCCode(c, f_waypoint_airport)));
1198   
1199     for(int i=0; funcs[i].name; i++) {
1200       hashset(c, globals, funcs[i].name,
1201       naNewFunc(c, naNewCCode(c, funcs[i].func)));
1202     }
1203   
1204   return naNil();
1205 }
1206
1207 void postinitNasalPositioned(naRef globals, naContext c)
1208 {
1209   naRef geoModule = naHash_cget(globals, (char*) "geo");
1210   if (naIsNil(geoModule)) {
1211     SG_LOG(SG_GENERAL, SG_WARN, "postinitNasalPositioned: geo.nas not loaded");
1212     return;
1213   }
1214   
1215   geoCoordClass = naHash_cget(geoModule, (char*) "Coord");
1216 }
1217
1218