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