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