]> git.mxchange.org Git - flightgear.git/blob - src/Scripting/NasalPositioned.cxx
18f2b1032ce839f94bc6b1d2662545d592320eb9
[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_sids(naContext c, naRef me, int argc, naRef* args)
593 {
594   FGAirport* apt = airportFromMe(me);
595   if (!apt) {
596     naRuntimeError(c, "airport.sids called on non-airport object");
597   }
598   
599   naRef sids = naNewVector(c);
600   
601   if (argc > 0 && naIsString(args[0])) {
602     if (!apt->hasRunwayWithIdent(naStr_data(args[0]))) {
603       return naNil();
604     }
605
606     FGRunway* rwy = apt->getRunwayByIdent(naStr_data(args[0]));
607     BOOST_FOREACH(flightgear::SID* sid, rwy->getSIDs()) {
608       naRef procId = stringToNasal(c, sid->ident());
609       naVec_append(sids, procId);
610     }
611   } else {
612     for (unsigned int s=0; s<apt->numSIDs(); ++s) {
613       flightgear::SID* sid = apt->getSIDByIndex(s);
614       naRef procId = stringToNasal(c, sid->ident());
615       naVec_append(sids, procId);
616     }
617   }
618   
619   return sids;
620 }
621
622 static naRef f_airport_stars(naContext c, naRef me, int argc, naRef* args)
623 {
624   FGAirport* apt = airportFromMe(me);
625   if (!apt) {
626     naRuntimeError(c, "airport.stars called on non-airport object");
627   }
628   
629   naRef stars = naNewVector(c);
630   
631   if (argc > 0 && naIsString(args[0])) {
632     if (!apt->hasRunwayWithIdent(naStr_data(args[0]))) {
633       return naNil();
634     }
635         
636     FGRunway* rwy = apt->getRunwayByIdent(naStr_data(args[0]));
637     BOOST_FOREACH(flightgear::STAR* s, rwy->getSTARs()) {
638       naRef procId = stringToNasal(c, s->ident());
639       naVec_append(stars, procId);
640     }
641   } else {
642     for (unsigned int s=0; s<apt->numSTARs(); ++s) {
643       flightgear::STAR* star = apt->getSTARByIndex(s);
644       naRef procId = stringToNasal(c, star->ident());
645       naVec_append(stars, procId);
646     }
647   }
648   
649   return stars;
650 }
651
652 static naRef f_airport_parking(naContext c, naRef me, int argc, naRef* args)
653 {
654   FGAirport* apt = airportFromMe(me);
655   if (!apt) {
656     naRuntimeError(c, "airport.parking called on non-airport object");
657   }
658   
659   naRef r = naNewVector(c);
660   std::string type;
661   bool onlyAvailable = false;
662   
663   if (argc > 0 && naIsString(args[0])) {
664     type = naStr_data(args[0]);
665   }
666   
667   if ((argc > 1) && naIsNum(args[1])) {
668     onlyAvailable = (args[1].num != 0.0);
669   }
670   
671   FGAirportDynamics* dynamics = apt->getDynamics();
672   for (int i=0; i<dynamics->getNrOfParkings(); ++i) {
673     FGParking* park = dynamics->getParking(i);
674   // filter out based on availability and type
675     if (onlyAvailable && !park->isAvailable()) {
676       continue;
677     }
678     
679     if (!type.empty() && (park->getType() != type)) {
680       continue;
681     }
682     
683     naRef nm = stringToNasal(c, park->getName());
684     naVec_append(r, nm);
685   }
686   
687   return r;
688 }
689
690 // Returns vector of data hash for navaid of a <type>, nil on error
691 // navaids sorted by ascending distance 
692 // navinfo([<lat>,<lon>],[<type>],[<id>])
693 // lat/lon (numeric): use latitude/longitude instead of ac position
694 // type:              ("fix"|"vor"|"ndb"|"ils"|"dme"|"tacan"|"any")
695 // id:                (partial) id of the fix
696 // examples:
697 // navinfo("vor")     returns all vors
698 // navinfo("HAM")     return all navaids who's name start with "HAM"
699 // navinfo("vor", "HAM") return all vor who's name start with "HAM"
700 //navinfo(34,48,"vor","HAM") return all vor who's name start with "HAM" 
701 //                           sorted by distance relative to lat=34, lon=48
702 static naRef f_navinfo(naContext c, naRef me, int argc, naRef* args)
703 {
704   SGGeod pos;
705   
706   if(argc >= 2 && naIsNum(args[0]) && naIsNum(args[1])) {
707     pos = SGGeod::fromDeg(args[1].num, args[0].num);
708     args += 2;
709     argc -= 2;
710   } else {
711     pos = globals->get_aircraft_position();
712   }
713   
714   FGPositioned::Type type = FGPositioned::INVALID;
715   nav_list_type navlist;
716   const char * id = "";
717   
718   if(argc > 0 && naIsString(args[0])) {
719     const char *s = naStr_data(args[0]);
720     if(!strcmp(s, "any")) type = FGPositioned::INVALID;
721     else if(!strcmp(s, "fix")) type = FGPositioned::FIX;
722     else if(!strcmp(s, "vor")) type = FGPositioned::VOR;
723     else if(!strcmp(s, "ndb")) type = FGPositioned::NDB;
724     else if(!strcmp(s, "ils")) type = FGPositioned::ILS;
725     else if(!strcmp(s, "dme")) type = FGPositioned::DME;
726     else if(!strcmp(s, "tacan")) type = FGPositioned::TACAN;
727     else id = s; // this is an id
728     ++args;
729     --argc;
730   } 
731   
732   if(argc > 0 && naIsString(args[0])) {
733     if( *id != 0 ) {
734       naRuntimeError(c, "navinfo() called with navaid id");
735       return naNil();
736     }
737     id = naStr_data(args[0]);
738     ++args;
739     --argc;
740   }
741   
742   if( argc > 0 ) {
743     naRuntimeError(c, "navinfo() called with too many arguments");
744     return naNil();
745   }
746   
747   navlist = globals->get_navlist()->findByIdentAndFreq( pos, id, 0.0, type );
748   
749   naRef reply = naNewVector(c);
750   for( nav_list_type::const_iterator it = navlist.begin(); it != navlist.end(); ++it ) {
751     naVec_append( reply, hashForNavRecord(c, *it, pos) );
752   }
753   return reply;
754 }
755
756 static naRef f_findNavaidsWithinRange(naContext c, naRef me, int argc, naRef* args)
757 {
758   int argOffset = 0;
759   SGGeod pos = globals->get_aircraft_position();
760   argOffset += geodFromArgs(args, 0, argc, pos);
761   
762   if (!naIsNum(args[argOffset])) {
763     naRuntimeError(c, "findNavaidsWithinRange expected range (in nm) as arg %d", argOffset);
764   }
765   
766   FGPositioned::Type type = FGPositioned::INVALID;
767   double rangeNm = args[argOffset++].num;
768   if (argOffset < argc) {
769     type = FGPositioned::typeFromName(naStr_data(args[argOffset]));
770   }
771   
772   naRef r = naNewVector(c);
773   FGNavList::TypeFilter filter(type);
774   FGPositioned::List navs = FGPositioned::findWithinRange(pos, rangeNm, &filter);
775   FGPositioned::sortByRange(navs, pos);
776   
777   BOOST_FOREACH(FGPositionedRef a, navs) {
778     FGNavRecord* nav = (FGNavRecord*) a.get();
779     naVec_append(r, hashForNavRecord(c, nav, pos));
780   }
781   
782   return r;
783 }
784
785 static naRef f_findNavaidByFrequency(naContext c, naRef me, int argc, naRef* args)
786 {
787   int argOffset = 0;
788   SGGeod pos = globals->get_aircraft_position();
789   argOffset += geodFromArgs(args, 0, argc, pos);
790   
791   if (!naIsNum(args[argOffset])) {
792     naRuntimeError(c, "findNavaidByFrequency expectes frequency (in Mhz) as arg %d", argOffset);
793   }
794   
795   FGPositioned::Type type = FGPositioned::INVALID;
796   double freqMhz = args[argOffset++].num;
797   if (argOffset < argc) {
798     type = FGPositioned::typeFromName(naStr_data(args[argOffset]));
799   }
800   
801   nav_list_type navs = globals->get_navlist()->findAllByFreq(freqMhz, pos, type);
802   if (navs.empty()) {
803     return naNil();
804   }
805   
806   return hashForNavRecord(c, navs.front().ptr(), pos);
807 }
808
809 static naRef f_findNavaidsByFrequency(naContext c, naRef me, int argc, naRef* args)
810 {
811   int argOffset = 0;
812   SGGeod pos = globals->get_aircraft_position();
813   argOffset += geodFromArgs(args, 0, argc, pos);
814   
815   if (!naIsNum(args[argOffset])) {
816     naRuntimeError(c, "findNavaidsByFrequency expectes frequency (in Mhz) as arg %d", argOffset);
817   }
818   
819   FGPositioned::Type type = FGPositioned::INVALID;
820   double freqMhz = args[argOffset++].num;
821   if (argOffset < argc) {
822     type = FGPositioned::typeFromName(naStr_data(args[argOffset]));
823   }
824   
825   naRef r = naNewVector(c);
826   nav_list_type navs = globals->get_navlist()->findAllByFreq(freqMhz, pos, type);
827   
828   BOOST_FOREACH(nav_rec_ptr a, navs) {
829     naVec_append(r, hashForNavRecord(c, a.ptr(), pos));
830   }
831   
832   return r;
833 }
834
835 static naRef f_findNavaidsByIdent(naContext c, naRef me, int argc, naRef* args)
836 {
837   int argOffset = 0;
838   SGGeod pos = globals->get_aircraft_position();
839   argOffset += geodFromArgs(args, 0, argc, pos);
840   
841   if (!naIsString(args[argOffset])) {
842     naRuntimeError(c, "findNavaidsByIdent expectes ident string as arg %d", argOffset);
843   }
844   
845   FGPositioned::Type type = FGPositioned::INVALID;
846   string ident = naStr_data(args[argOffset++]);
847   if (argOffset < argc) {
848     type = FGPositioned::typeFromName(naStr_data(args[argOffset]));
849   }
850   
851   naRef r = naNewVector(c);
852   nav_list_type navs = globals->get_navlist()->findByIdentAndFreq(pos, ident, 0.0, type);
853   
854   BOOST_FOREACH(nav_rec_ptr a, navs) {
855     naVec_append(r, hashForNavRecord(c, a.ptr(), pos));
856   }
857   
858   return r;
859 }
860
861
862 // Convert a cartesian point to a geodetic lat/lon/altitude.
863 static naRef f_magvar(naContext c, naRef me, int argc, naRef* args)
864 {
865   SGGeod pos = globals->get_aircraft_position();
866   if (argc == 0) {
867     // fine, use aircraft position
868   } else if (geodFromArgs(args, 0, argc, pos)) {
869     // okay
870   } else {
871     naRuntimeError(c, "magvar() expects no arguments, a positioned hash or lat,lon pair");
872   }
873   
874   double jd = globals->get_time_params()->getJD();
875   double magvarDeg = sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
876   return naNum(magvarDeg);
877 }
878
879 static naRef f_courseAndDistance(naContext c, naRef me, int argc, naRef* args)
880 {
881     SGGeod from = globals->get_aircraft_position(), to, p;
882     int argOffset = geodFromArgs(args, 0, argc, p);
883     if (geodFromArgs(args, argOffset, argc, to)) {
884       from = p; // we parsed both FROM and TO args, so first was from
885     } else {
886       to = p; // only parsed one arg, so FROM is current
887     }
888   
889     if (argOffset == 0) {
890         naRuntimeError(c, "invalid arguments to courseAndDistance");
891     }
892     
893     double course, course2, d;
894     SGGeodesy::inverse(from, to, course, course2, d);
895     
896     naRef result = naNewVector(c);
897     naVec_append(result, naNum(course));
898     naVec_append(result, naNum(d * SG_METER_TO_NM));
899     return result;
900 }
901
902 static naRef f_greatCircleMove(naContext c, naRef me, int argc, naRef* args)
903 {
904   SGGeod from = globals->get_aircraft_position(), to;
905   int argOffset = 0;
906   
907   // complication - don't inerpret two doubles (as the only args)
908   // as a lat,lon pair - only do so if we have at least three args.
909   if (argc > 2) {
910     argOffset = geodFromArgs(args, 0, argc, from);
911   }
912   
913   if ((argOffset + 1) >= argc) {
914     naRuntimeError(c, "isufficent arguments to greatCircleMove");
915   }
916   
917   if (!naIsNum(args[argOffset]) || !naIsNum(args[argOffset+1])) {
918     naRuntimeError(c, "invalid arguments %d and %d to greatCircleMove",
919                    argOffset, argOffset + 1);
920   }
921   
922   double course = args[argOffset].num, course2;
923   double distanceNm = args[argOffset + 1].num;
924   SGGeodesy::direct(from, course, distanceNm * SG_NM_TO_METER, to, course2);
925   
926   // return geo.Coord
927   naRef coord = naNewHash(c);
928   hashset(c, coord, "lat", naNum(to.getLatitudeDeg()));
929   hashset(c, coord, "lon", naNum(to.getLongitudeDeg()));
930   return coord;
931 }
932
933 static naRef f_tilePath(naContext c, naRef me, int argc, naRef* args)
934 {
935     SGGeod pos = globals->get_aircraft_position();
936     geodFromArgs(args, 0, argc, pos);
937     SGBucket b(pos);
938     return stringToNasal(c, b.gen_base_path());
939 }
940
941 static naRef f_route(naContext c, naRef me, int argc, naRef* args)
942 {
943   naRef route = naNewHash(c);
944   
945   // return active route hash by default,
946   // other routes in the future
947   
948   naRef parents = naNewVector(c);
949   naVec_append(parents, routePrototype);
950   hashset(c, route, "parents", parents);
951   
952   return route;
953 }
954
955 static naRef f_route_getWP(naContext c, naRef me, int argc, naRef* args)
956 {
957   FGRouteMgr* rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
958   
959   int index;
960   if (argc == 0) {
961     index = rm->currentIndex();
962   } else {
963     index = (int) naNumValue(args[0]).num;
964   }
965   
966   if ((index < 0) || (index >= rm->numWaypts())) {
967     return naNil();
968   }
969   
970   flightgear::Waypt* next = NULL;
971   if (index < (rm->numWaypts() - 1)) {
972     next = rm->wayptAtIndex(index + 1);
973   }
974   return hashForWaypoint(c, rm->wayptAtIndex(index), next);
975 }
976
977 static naRef f_route_currentWP(naContext c, naRef me, int argc, naRef* args)
978 {
979   FGRouteMgr* rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
980   flightgear::Waypt* next = NULL;
981   if (rm->currentIndex() < (rm->numWaypts() - 1)) {
982     next = rm->wayptAtIndex(rm->currentIndex() + 1);
983   }
984   return hashForWaypoint(c, rm->currentWaypt(), next);
985 }
986
987 static naRef f_route_currentIndex(naContext c, naRef me, int argc, naRef* args)
988 {
989   FGRouteMgr* rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
990   return naNum(rm->currentIndex());
991 }
992
993 static naRef f_route_numWaypoints(naContext c, naRef me, int argc, naRef* args)
994 {
995   FGRouteMgr* rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));
996   return naNum(rm->numWaypts());
997 }
998
999 static flightgear::Waypt* wayptFromMe(naRef me)
1000 {  
1001   naRef ghost = naHash_cget(me, (char*) "_waypt");
1002   if (naIsNil(ghost)) {
1003     return NULL;
1004   }
1005   
1006   return wayptGhost(ghost);
1007 }
1008
1009 static naRef f_waypoint_navaid(naContext c, naRef me, int argc, naRef* args)
1010 {
1011   flightgear::Waypt* w = wayptFromMe(me);
1012   if (!w) {
1013     naRuntimeError(c, "waypoint.navaid called on non-waypoint object");
1014   }
1015   
1016   FGPositioned* pos = w->source();
1017   if (!pos) {
1018     return naNil();
1019   }
1020   
1021   switch (pos->type()) {
1022   case FGPositioned::VOR:
1023   case FGPositioned::NDB:
1024   case FGPositioned::ILS:
1025   case FGPositioned::LOC:
1026   case FGPositioned::GS:
1027   case FGPositioned::DME:
1028   case FGPositioned::TACAN: {
1029     FGNavRecord* nav = (FGNavRecord*) pos;
1030     return hashForNavRecord(c, nav, globals->get_aircraft_position());
1031   }
1032       
1033   default:
1034     return naNil();
1035   }
1036 }
1037
1038 static naRef f_waypoint_airport(naContext c, naRef me, int argc, naRef* args)
1039 {
1040   flightgear::Waypt* w = wayptFromMe(me);
1041   if (!w) {
1042     naRuntimeError(c, "waypoint.navaid called on non-waypoint object");
1043   }
1044   
1045   FGPositioned* pos = w->source();
1046   if (!pos || FGAirport::isAirportType(pos)) {
1047     return naNil();
1048   }
1049   
1050   return hashForAirport(c, (FGAirport*) pos);
1051 }
1052
1053 static naRef f_waypoint_runway(naContext c, naRef me, int argc, naRef* args)
1054 {
1055   flightgear::Waypt* w = wayptFromMe(me);
1056   if (!w) {
1057     naRuntimeError(c, "waypoint.navaid called on non-waypoint object");
1058   }
1059   
1060   FGPositioned* pos = w->source();
1061   if (!pos || (pos->type() != FGPositioned::RUNWAY)) {
1062     return naNil();
1063   }
1064   
1065   return hashForRunway(c, (FGRunway*) pos);
1066 }
1067
1068 // Table of extension functions.  Terminate with zeros.
1069 static struct { const char* name; naCFunction func; } funcs[] = {
1070   { "carttogeod", f_carttogeod },
1071   { "geodtocart", f_geodtocart },
1072   { "geodinfo", f_geodinfo },
1073   { "airportinfo", f_airportinfo },
1074   { "findAirportsWithinRange", f_findAirportsWithinRange },
1075   { "findAirportsByICAO", f_findAirportsByICAO },
1076   { "navinfo", f_navinfo },
1077   { "findNavaidsWithinRange", f_findNavaidsWithinRange },
1078   { "findNavaidByFrequency", f_findNavaidByFrequency },
1079   { "findNavaidsByFrequency", f_findNavaidsByFrequency },
1080   { "findNavaidsByID", f_findNavaidsByIdent },
1081   { "route", f_route },
1082   { "magvar", f_magvar },
1083   { "courseAndDistance", f_courseAndDistance },
1084   { "greatCircleMove", f_greatCircleMove },
1085   { "bucketPath", f_tilePath },
1086   { 0, 0 }
1087 };
1088
1089
1090 naRef initNasalPositioned(naRef globals, naContext c, naRef gcSave)
1091 {
1092     airportPrototype = naNewHash(c);
1093     hashset(c, gcSave, "airportProto", airportPrototype);
1094   
1095     hashset(c, airportPrototype, "tower", naNewFunc(c, naNewCCode(c, f_airport_tower)));
1096     hashset(c, airportPrototype, "comms", naNewFunc(c, naNewCCode(c, f_airport_comms)));
1097     hashset(c, airportPrototype, "sids", naNewFunc(c, naNewCCode(c, f_airport_sids)));
1098     hashset(c, airportPrototype, "stars", naNewFunc(c, naNewCCode(c, f_airport_stars)));
1099     hashset(c, airportPrototype, "parking", naNewFunc(c, naNewCCode(c, f_airport_parking)));
1100   
1101     routePrototype = naNewHash(c);
1102     hashset(c, gcSave, "routeProto", routePrototype);
1103       
1104     hashset(c, routePrototype, "getWP", naNewFunc(c, naNewCCode(c, f_route_getWP)));
1105     hashset(c, routePrototype, "currentWP", naNewFunc(c, naNewCCode(c, f_route_currentWP)));
1106     hashset(c, routePrototype, "currentIndex", naNewFunc(c, naNewCCode(c, f_route_currentIndex)));
1107     hashset(c, routePrototype, "getPlanSize", naNewFunc(c, naNewCCode(c, f_route_numWaypoints)));
1108     
1109     waypointPrototype = naNewHash(c);
1110     hashset(c, gcSave, "wayptProto", waypointPrototype);
1111     
1112     hashset(c, waypointPrototype, "navaid", naNewFunc(c, naNewCCode(c, f_waypoint_navaid)));
1113     hashset(c, waypointPrototype, "runway", naNewFunc(c, naNewCCode(c, f_waypoint_runway)));
1114     hashset(c, waypointPrototype, "airport", naNewFunc(c, naNewCCode(c, f_waypoint_airport)));
1115   
1116     for(int i=0; funcs[i].name; i++) {
1117       hashset(c, globals, funcs[i].name,
1118             naNewFunc(c, naNewCCode(c, funcs[i].func)));
1119     }
1120   
1121   return naNil();
1122 }
1123
1124 void postinitNasalPositioned(naRef globals, naContext c)
1125 {
1126   naRef geoModule = naHash_cget(globals, (char*) "geo");
1127   if (naIsNil(geoModule)) {
1128     SG_LOG(SG_GENERAL, SG_WARN, "postinitNasalPositioned: geo.nas not loaded");
1129     return;
1130   }
1131   
1132   geoCoordClass = naHash_cget(geoModule, (char*) "Coord");
1133 }
1134
1135