]> git.mxchange.org Git - flightgear.git/blob - src/Scripting/NasalPositioned.cxx
Initial work on native file dialog support.
[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 #include <boost/algorithm/string/case_conv.hpp>
31 #include <boost/tuple/tuple.hpp> // for boost::tie
32
33 #include <simgear/sg_inlines.h>
34 #include <simgear/scene/material/mat.hxx>
35 #include <simgear/magvar/magvar.hxx>
36 #include <simgear/timing/sg_time.hxx>
37 #include <simgear/bucket/newbucket.hxx>
38
39 #include <Airports/runways.hxx>
40 #include <Airports/simple.hxx>
41 #include <Airports/dynamics.hxx>
42 #include <Airports/parking.hxx>
43 #include <Scripting/NasalSys.hxx>
44 #include <Navaids/navlist.hxx>
45 #include <Navaids/procedure.hxx>
46 #include <Main/globals.hxx>
47 #include <Main/fg_props.hxx>
48 #include <Scenery/scenery.hxx>
49 #include <ATC/CommStation.hxx>
50 #include <Navaids/FlightPlan.hxx>
51 #include <Navaids/waypoint.hxx>
52 #include <Navaids/fix.hxx>
53 #include <Autopilot/route_mgr.hxx>
54 #include <Navaids/routePath.hxx>
55 #include <Navaids/procedure.hxx>
56 #include <Navaids/airways.hxx>
57 #include <Navaids/NavDataCache.hxx>
58
59 using namespace flightgear;
60
61 static void positionedGhostDestroy(void* g);
62 static void wayptGhostDestroy(void* g);
63 static void legGhostDestroy(void* g);
64 static void routeBaseGhostDestroy(void* g);
65
66 naGhostType PositionedGhostType = { positionedGhostDestroy, "positioned" };
67
68 static const char* airportGhostGetMember(naContext c, void* g, naRef field, naRef* out);
69 naGhostType AirportGhostType = { positionedGhostDestroy, "airport", airportGhostGetMember, 0 };
70
71 static const char* navaidGhostGetMember(naContext c, void* g, naRef field, naRef* out);
72 naGhostType NavaidGhostType = { positionedGhostDestroy, "navaid", navaidGhostGetMember, 0 };
73
74 static const char* runwayGhostGetMember(naContext c, void* g, naRef field, naRef* out);
75 naGhostType RunwayGhostType = { positionedGhostDestroy, "runway", runwayGhostGetMember, 0 };
76 naGhostType TaxiwayGhostType = { positionedGhostDestroy, "taxiway", runwayGhostGetMember, 0 };
77
78 static const char* fixGhostGetMember(naContext c, void* g, naRef field, naRef* out);
79 naGhostType FixGhostType = { positionedGhostDestroy, "fix", fixGhostGetMember, 0 };
80
81 static const char* wayptGhostGetMember(naContext c, void* g, naRef field, naRef* out);
82 static void waypointGhostSetMember(naContext c, void* g, naRef field, naRef value);
83
84 naGhostType WayptGhostType = { wayptGhostDestroy, 
85   "waypoint",
86   wayptGhostGetMember,
87   waypointGhostSetMember};
88
89 static const char* legGhostGetMember(naContext c, void* g, naRef field, naRef* out);
90 static void legGhostSetMember(naContext c, void* g, naRef field, naRef value);
91
92 naGhostType FPLegGhostType = { legGhostDestroy, 
93   "flightplan-leg",
94   legGhostGetMember,
95   legGhostSetMember};
96
97 static const char* flightplanGhostGetMember(naContext c, void* g, naRef field, naRef* out);
98 static void flightplanGhostSetMember(naContext c, void* g, naRef field, naRef value);
99
100 naGhostType FlightPlanGhostType = { routeBaseGhostDestroy, 
101   "flightplan",
102   flightplanGhostGetMember,
103   flightplanGhostSetMember
104 };
105
106 static const char* procedureGhostGetMember(naContext c, void* g, naRef field, naRef* out);
107 naGhostType ProcedureGhostType = { routeBaseGhostDestroy, 
108   "procedure",
109   procedureGhostGetMember,
110   0};
111
112 static void hashset(naContext c, naRef hash, const char* key, naRef val)
113 {
114   naRef s = naNewString(c);
115   naStr_fromdata(s, (char*)key, strlen(key));
116   naHash_set(hash, s, val);
117 }
118
119 static naRef stringToNasal(naContext c, const std::string& s)
120 {
121     return naStr_fromdata(naNewString(c),
122                    const_cast<char *>(s.c_str()), 
123                    s.length());
124 }
125
126 static WayptFlag wayptFlagFromString(const char* s)
127 {
128   if (!strcmp(s, "sid")) return WPT_DEPARTURE;
129   if (!strcmp(s, "star")) return WPT_ARRIVAL;
130   if (!strcmp(s, "approach")) return WPT_APPROACH;
131   if (!strcmp(s, "missed")) return WPT_MISS;
132   if (!strcmp(s, "pseudo")) return WPT_PSEUDO;
133   
134   return (WayptFlag) 0;
135 }
136
137 static naRef wayptFlagToNasal(naContext c, unsigned int flags)
138 {
139   if (flags & WPT_PSEUDO) return stringToNasal(c, "pseudo");
140   if (flags & WPT_DEPARTURE) return stringToNasal(c, "sid");
141   if (flags & WPT_ARRIVAL) return stringToNasal(c, "star");
142   if (flags & WPT_MISS) return stringToNasal(c, "missed");
143   if (flags & WPT_APPROACH) return stringToNasal(c, "approach");
144   return naNil();
145 }
146
147 static FGPositioned* positionedGhost(naRef r)
148 {
149     if ((naGhost_type(r) == &AirportGhostType) ||
150         (naGhost_type(r) == &NavaidGhostType) ||
151         (naGhost_type(r) == &RunwayGhostType) ||
152         (naGhost_type(r) == &FixGhostType))
153     {
154         return (FGPositioned*) naGhost_ptr(r);
155     }
156   
157     return 0;
158 }
159
160 static FGAirport* airportGhost(naRef r)
161 {
162   if (naGhost_type(r) == &AirportGhostType)
163     return (FGAirport*) naGhost_ptr(r);
164   return 0;
165 }
166
167 static FGNavRecord* navaidGhost(naRef r)
168 {
169   if (naGhost_type(r) == &NavaidGhostType)
170     return (FGNavRecord*) naGhost_ptr(r);
171   return 0;
172 }
173
174 static FGRunway* runwayGhost(naRef r)
175 {
176   if (naGhost_type(r) == &RunwayGhostType)
177     return (FGRunway*) naGhost_ptr(r);
178   return 0;
179 }
180
181 static FGTaxiway* taxiwayGhost(naRef r)
182 {
183   if (naGhost_type(r) == &TaxiwayGhostType)
184     return (FGTaxiway*) naGhost_ptr(r);
185   return 0;
186 }
187
188 static FGFix* fixGhost(naRef r)
189 {
190   if (naGhost_type(r) == &FixGhostType)
191     return (FGFix*) naGhost_ptr(r);
192   return 0;
193 }
194
195
196 static void positionedGhostDestroy(void* g)
197 {
198     FGPositioned* pos = (FGPositioned*)g;
199     if (!FGPositioned::put(pos)) // unref
200         delete pos;
201 }
202
203 static Waypt* wayptGhost(naRef r)
204 {
205   if (naGhost_type(r) == &WayptGhostType)
206     return (Waypt*) naGhost_ptr(r);
207   
208   if (naGhost_type(r) == &FPLegGhostType) {
209     FlightPlan::Leg* leg = (FlightPlan::Leg*) naGhost_ptr(r);
210     return leg->waypoint();
211   }
212   
213   return 0;
214 }
215
216 static void wayptGhostDestroy(void* g)
217 {
218   Waypt* wpt = (Waypt*)g;
219   if (!Waypt::put(wpt)) // unref
220     delete wpt;
221 }
222
223 static void legGhostDestroy(void* g)
224 {
225   // nothing for now
226 }
227
228
229 static FlightPlan::Leg* fpLegGhost(naRef r)
230 {
231   if (naGhost_type(r) == &FPLegGhostType)
232     return (FlightPlan::Leg*) naGhost_ptr(r);
233   return 0;
234 }
235
236 static Procedure* procedureGhost(naRef r)
237 {
238   if (naGhost_type(r) == &ProcedureGhostType)
239     return (Procedure*) naGhost_ptr(r);
240   return 0;
241 }
242
243 static FlightPlan* flightplanGhost(naRef r)
244 {
245   if (naGhost_type(r) == &FlightPlanGhostType)
246     return (FlightPlan*) naGhost_ptr(r);
247   return 0;
248 }
249
250 static void routeBaseGhostDestroy(void* g)
251 {
252   // nothing for now
253 }
254
255 static naRef airportPrototype;
256 static naRef flightplanPrototype;
257 static naRef waypointPrototype;
258 static naRef geoCoordClass;
259 static naRef fpLegPrototype;
260 static naRef procedurePrototype;
261
262 naRef ghostForAirport(naContext c, const FGAirport* apt)
263 {
264   if (!apt) {
265     return naNil();
266   }
267   
268   FGPositioned::get(apt); // take a ref
269   return naNewGhost2(c, &AirportGhostType, (void*) apt);
270 }
271
272 naRef ghostForNavaid(naContext c, const FGNavRecord* n)
273 {
274   if (!n) {
275     return naNil();
276   }
277   
278   FGPositioned::get(n); // take a ref
279   return naNewGhost2(c, &NavaidGhostType, (void*) n);
280 }
281
282 naRef ghostForRunway(naContext c, const FGRunway* r)
283 {
284   if (!r) {
285     return naNil();
286   }
287   
288   FGPositioned::get(r); // take a ref
289   return naNewGhost2(c, &RunwayGhostType, (void*) r);
290 }
291
292 naRef ghostForTaxiway(naContext c, const FGTaxiway* r)
293 {
294   if (!r) {
295     return naNil();
296   }
297   
298   FGPositioned::get(r); // take a ref
299   return naNewGhost2(c, &TaxiwayGhostType, (void*) r);
300 }
301
302 naRef ghostForFix(naContext c, const FGFix* r)
303 {
304   if (!r) {
305     return naNil();
306   }
307   
308   FGPositioned::get(r); // take a ref
309   return naNewGhost2(c, &FixGhostType, (void*) r);
310 }
311
312
313 naRef ghostForWaypt(naContext c, const Waypt* wpt)
314 {
315   if (!wpt) {
316     return naNil();
317   }
318
319   Waypt::get(wpt); // take a ref
320   return naNewGhost2(c, &WayptGhostType, (void*) wpt);
321 }
322
323 naRef ghostForLeg(naContext c, const FlightPlan::Leg* leg)
324 {
325   if (!leg) {
326     return naNil();
327   }
328   
329   return naNewGhost2(c, &FPLegGhostType, (void*) leg);
330 }
331
332 naRef ghostForFlightPlan(naContext c, const FlightPlan* fp)
333 {
334   if (!fp) {
335     return naNil();
336   }
337   
338   return naNewGhost2(c, &FlightPlanGhostType, (void*) fp);
339 }
340
341 naRef ghostForProcedure(naContext c, const Procedure* proc)
342 {
343   if (!proc) {
344     return naNil();
345   }
346   
347   return naNewGhost2(c, &ProcedureGhostType, (void*) proc);
348 }
349
350 static const char* airportGhostGetMember(naContext c, void* g, naRef field, naRef* out)
351 {
352   const char* fieldName = naStr_data(field);
353   FGAirport* apt = (FGAirport*) g;
354   
355   if (!strcmp(fieldName, "parents")) {
356     *out = naNewVector(c);
357     naVec_append(*out, airportPrototype);
358   } else if (!strcmp(fieldName, "id")) *out = stringToNasal(c, apt->ident());
359   else if (!strcmp(fieldName, "name")) *out = stringToNasal(c, apt->name());
360   else if (!strcmp(fieldName, "lat")) *out = naNum(apt->getLatitude());
361   else if (!strcmp(fieldName, "lon")) *out = naNum(apt->getLongitude());
362   else if (!strcmp(fieldName, "elevation")) {
363     *out = naNum(apt->getElevation() * SG_FEET_TO_METER);
364   } else if (!strcmp(fieldName, "has_metar")) {
365     *out = naNum(apt->getMetar());
366   } else if (!strcmp(fieldName, "runways")) {
367     *out = naNewHash(c);
368     double minLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft");
369     for(unsigned int r=0; r<apt->numRunways(); ++r) {
370       FGRunway* rwy(apt->getRunwayByIndex(r));
371       
372     // ignore unusably short runways
373       if (rwy->lengthFt() < minLengthFt) {
374         continue;
375       }
376       
377       naRef rwyid = stringToNasal(c, rwy->ident());
378       naRef rwydata = ghostForRunway(c, rwy);
379       naHash_set(*out, rwyid, rwydata);
380     }
381
382   } else if (!strcmp(fieldName, "taxiways")) {
383     *out = naNewVector(c);
384     for(unsigned int r=0; r<apt->numTaxiways(); ++r) {
385       FGTaxiway* taxi(apt->getTaxiwayByIndex(r));
386       naRef taxidata = ghostForTaxiway(c, taxi);
387       naVec_append(*out, taxidata);
388     }
389
390   } else {
391     return 0;
392   }
393   
394   return "";
395 }
396
397 static const char* waypointCommonGetMember(naContext c, Waypt* wpt, const char* fieldName, naRef* out)
398 {
399   if (!strcmp(fieldName, "wp_name")) *out = stringToNasal(c, wpt->ident());
400   else if (!strcmp(fieldName, "wp_type")) *out = stringToNasal(c, wpt->type());
401   else if (!strcmp(fieldName, "wp_role")) *out = wayptFlagToNasal(c, wpt->flags());
402   else if (!strcmp(fieldName, "wp_lat")) *out = naNum(wpt->position().getLatitudeDeg());
403   else if (!strcmp(fieldName, "wp_lon")) *out = naNum(wpt->position().getLongitudeDeg());
404   else if (!strcmp(fieldName, "wp_parent_name")) {
405     Procedure* proc = dynamic_cast<Procedure*>(wpt->owner());
406     *out = proc ? stringToNasal(c, proc->ident()) : naNil();
407   } else if (!strcmp(fieldName, "wp_parent")) {
408     Procedure* proc = dynamic_cast<Procedure*>(wpt->owner());
409     *out = ghostForProcedure(c, proc);
410   } else if (!strcmp(fieldName, "fly_type")) {
411     if (wpt->type() == "hold") {
412       *out = stringToNasal(c, "Hold");
413     } else {
414       *out = stringToNasal(c, wpt->flag(WPT_OVERFLIGHT) ? "flyOver" : "flyBy");
415     }
416   } else {
417     return NULL; // member not found
418   }
419
420   return "";
421 }
422
423 static void waypointCommonSetMember(naContext c, Waypt* wpt, const char* fieldName, naRef value)
424 {
425   if (!strcmp(fieldName, "wp_role")) {
426     if (!naIsString(value)) naRuntimeError(c, "wp_role must be a string");
427     if (wpt->owner() != NULL) naRuntimeError(c, "cannot override wp_role on waypoint with parent");
428     WayptFlag f = wayptFlagFromString(naStr_data(value));
429     if (f == 0) {
430       naRuntimeError(c, "unrecognized wp_role value %s", naStr_data(value));
431     }
432     
433     wpt->setFlag(f, true);
434   }
435 }
436
437 static const char* wayptGhostGetMember(naContext c, void* g, naRef field, naRef* out)
438 {
439   const char* fieldName = naStr_data(field);
440   Waypt* wpt = (flightgear::Waypt*) g;
441   return waypointCommonGetMember(c, wpt, fieldName, out);
442 }
443
444 static RouteRestriction routeRestrictionFromString(const char* s)
445 {
446   string u(s);
447   boost::to_lower(u);
448   if (u == "computed") return RESTRICT_COMPUTED;
449   if (u == "at") return RESTRICT_AT;
450   if (u == "mach") return SPEED_RESTRICT_MACH;
451   if (u == "computed-mach") return SPEED_COMPUTED_MACH;
452   if (u == "delete") return RESTRICT_DELETE;
453   return RESTRICT_NONE;
454 };
455
456 naRef routeRestrictionToNasal(naContext c, RouteRestriction rr)
457 {
458   switch (rr) {
459     case RESTRICT_NONE: return naNil();
460     case RESTRICT_AT: return stringToNasal(c, "at");
461     case RESTRICT_ABOVE: return stringToNasal(c, "above");
462     case RESTRICT_BELOW: return stringToNasal(c, "below");
463     case SPEED_RESTRICT_MACH: return stringToNasal(c, "mach");
464     case RESTRICT_COMPUTED: return stringToNasal(c, "computed");
465     case SPEED_COMPUTED_MACH: return stringToNasal(c, "computed-mach");
466     case RESTRICT_DELETE: return stringToNasal(c, "delete");
467   }
468   
469   return naNil();
470 }
471
472 static const char* legGhostGetMember(naContext c, void* g, naRef field, naRef* out)
473 {
474   const char* fieldName = naStr_data(field);
475   FlightPlan::Leg* leg = (FlightPlan::Leg*) g;
476   Waypt* wpt = leg->waypoint();
477   
478   if (!strcmp(fieldName, "parents")) {
479     *out = naNewVector(c);
480     naVec_append(*out, fpLegPrototype);
481   } else if (!strcmp(fieldName, "index")) {
482     *out = naNum(leg->index());
483   } else if (!strcmp(fieldName, "alt_cstr")) {
484     *out = naNum(leg->altitudeFt());
485   } else if (!strcmp(fieldName, "alt_cstr_type")) {
486     *out = routeRestrictionToNasal(c, leg->altitudeRestriction());
487   } else if (!strcmp(fieldName, "speed_cstr")) {
488     double s = isMachRestrict(leg->speedRestriction()) ? leg->speedMach() : leg->speedKts();
489     *out = naNum(s);
490   } else if (!strcmp(fieldName, "speed_cstr_type")) {
491     *out = routeRestrictionToNasal(c, leg->speedRestriction());  
492   } else if (!strcmp(fieldName, "leg_distance")) {
493     *out = naNum(leg->distanceNm());
494   } else if (!strcmp(fieldName, "leg_bearing")) {
495     *out = naNum(leg->courseDeg());
496   } else if (!strcmp(fieldName, "distance_along_route")) {
497     *out = naNum(leg->distanceAlongRoute());
498   } else { // check for fields defined on the underlying waypoint
499     return waypointCommonGetMember(c, wpt, fieldName, out);
500   }
501   
502   return ""; // success
503 }
504
505 static void waypointGhostSetMember(naContext c, void* g, naRef field, naRef value)
506 {
507   const char* fieldName = naStr_data(field);
508   Waypt* wpt = (Waypt*) g;
509   waypointCommonSetMember(c, wpt, fieldName, value);
510 }
511
512 static void legGhostSetMember(naContext c, void* g, naRef field, naRef value)
513 {
514   const char* fieldName = naStr_data(field);
515   FlightPlan::Leg* leg = (FlightPlan::Leg*) g;
516     
517   waypointCommonSetMember(c, leg->waypoint(), fieldName, value);
518 }
519
520 static const char* flightplanGhostGetMember(naContext c, void* g, naRef field, naRef* out)
521 {
522   const char* fieldName = naStr_data(field);
523   FlightPlan* fp = (FlightPlan*) g;
524   
525   if (!strcmp(fieldName, "parents")) {
526     *out = naNewVector(c);
527     naVec_append(*out, flightplanPrototype);
528   } else if (!strcmp(fieldName, "id")) *out = stringToNasal(c, fp->ident());
529   else if (!strcmp(fieldName, "departure")) *out = ghostForAirport(c, fp->departureAirport());
530   else if (!strcmp(fieldName, "destination")) *out = ghostForAirport(c, fp->destinationAirport());
531   else if (!strcmp(fieldName, "departure_runway")) *out = ghostForRunway(c, fp->departureRunway());
532   else if (!strcmp(fieldName, "destination_runway")) *out = ghostForRunway(c, fp->destinationRunway());
533   else if (!strcmp(fieldName, "sid")) *out = ghostForProcedure(c, fp->sid());
534   else if (!strcmp(fieldName, "sid_trans")) *out = ghostForProcedure(c, fp->sidTransition());
535   else if (!strcmp(fieldName, "star")) *out = ghostForProcedure(c, fp->star());
536   else if (!strcmp(fieldName, "star_trans")) *out = ghostForProcedure(c, fp->starTransition());
537   else if (!strcmp(fieldName, "approach")) *out = ghostForProcedure(c, fp->approach());
538   else if (!strcmp(fieldName, "current")) *out = naNum(fp->currentIndex());
539   else {
540     return 0;
541   }
542   
543   return "";
544 }
545
546 static void flightplanGhostSetMember(naContext c, void* g, naRef field, naRef value)
547 {
548   const char* fieldName = naStr_data(field);
549   FlightPlan* fp = (FlightPlan*) g;
550   
551   if (!strcmp(fieldName, "id")) {
552     if (!naIsString(value)) naRuntimeError(c, "flightplan.id must be a string");
553     fp->setIdent(naStr_data(value));
554   } else if (!strcmp(fieldName, "current")) {
555     int index = value.num;
556     if ((index < 0) || (index >= fp->numLegs())) {
557       return;
558     }
559     fp->setCurrentIndex(index);
560   } else if (!strcmp(fieldName, "departure")) {
561     FGAirport* apt = airportGhost(value);
562     if (apt) {
563       fp->setDeparture(apt);
564       return;
565     }
566     
567     FGRunway* rwy = runwayGhost(value);
568     if (rwy){
569       fp->setDeparture(rwy);
570       return;
571     }
572     
573     naRuntimeError(c, "bad argument type setting departure");
574   } else if (!strcmp(fieldName, "destination")) {
575     FGAirport* apt = airportGhost(value);
576     if (apt) {
577       fp->setDestination(apt);
578       return;
579     }
580     
581     FGRunway* rwy = runwayGhost(value);
582     if (rwy){
583       fp->setDestination(rwy);
584       return;
585     }
586     
587     naRuntimeError(c, "bad argument type setting destination");
588   } else if (!strcmp(fieldName, "departure_runway")) {
589     FGRunway* rwy = runwayGhost(value);
590     if (rwy){
591       fp->setDeparture(rwy);
592       return;
593     }
594     
595     naRuntimeError(c, "bad argument type setting departure");
596   } else if (!strcmp(fieldName, "destination_runway")) {
597     FGRunway* rwy = runwayGhost(value);
598     if (rwy){
599       fp->setDestination(rwy);
600       return;
601     }
602     
603     naRuntimeError(c, "bad argument type setting departure");
604   } else if (!strcmp(fieldName, "sid")) {
605     Procedure* proc = procedureGhost(value);
606     if (proc && (proc->type() == PROCEDURE_SID)) {
607       fp->setSID((flightgear::SID*) proc);
608       return;
609     }
610     // allow a SID transition to be set, implicitly include the SID itself
611     if (proc && (proc->type() == PROCEDURE_TRANSITION)) {
612       fp->setSID((Transition*) proc);
613       return;
614     }
615         
616     if (naIsString(value)) {
617       FGAirport* apt = fp->departureAirport();
618       fp->setSID(apt->findSIDWithIdent(naStr_data(value)));
619       return;
620     }
621     
622     naRuntimeError(c, "bad argument type setting SID");
623   } else if (!strcmp(fieldName, "star")) {
624     Procedure* proc = procedureGhost(value);
625     if (proc && (proc->type() == PROCEDURE_STAR)) {
626       fp->setSTAR((STAR*) proc);
627       return;
628     }
629     
630     if (proc && (proc->type() == PROCEDURE_TRANSITION)) {
631       fp->setSTAR((Transition*) proc);
632       return;
633     }
634     
635     if (naIsString(value)) {
636       FGAirport* apt = fp->destinationAirport();
637       fp->setSTAR(apt->findSTARWithIdent(naStr_data(value)));
638       return;
639     }
640     
641     naRuntimeError(c, "bad argument type setting STAR");
642   } else if (!strcmp(fieldName, "approach")) {
643     Procedure* proc = procedureGhost(value);
644     if (proc && Approach::isApproach(proc->type())) {
645       fp->setApproach((Approach*) proc);
646       return;
647     }
648     
649     if (naIsString(value)) {
650       FGAirport* apt = fp->destinationAirport();
651       fp->setApproach(apt->findApproachWithIdent(naStr_data(value)));
652       return;
653     }
654     
655     naRuntimeError(c, "bad argument type setting approach");
656   }
657 }
658
659
660 static naRef procedureTpType(naContext c, ProcedureType ty)
661 {
662   switch (ty) {
663     case PROCEDURE_SID: return stringToNasal(c, "sid");
664     case PROCEDURE_STAR: return stringToNasal(c, "star");
665     case PROCEDURE_APPROACH_VOR: 
666     case PROCEDURE_APPROACH_ILS: 
667     case PROCEDURE_APPROACH_RNAV: 
668     case PROCEDURE_APPROACH_NDB:
669       return stringToNasal(c, "IAP");
670     default:
671       return naNil();
672   }
673 }
674
675 static naRef procedureRadioType(naContext c, ProcedureType ty)
676 {
677   switch (ty) {
678     case PROCEDURE_APPROACH_VOR: return stringToNasal(c, "VOR");
679     case PROCEDURE_APPROACH_ILS: return stringToNasal(c, "ILS");
680     case PROCEDURE_APPROACH_RNAV: return stringToNasal(c, "RNAV");
681     case PROCEDURE_APPROACH_NDB: return stringToNasal(c, "NDB");
682     default:
683       return naNil();
684   }
685 }
686
687 static const char* procedureGhostGetMember(naContext c, void* g, naRef field, naRef* out)
688 {
689   const char* fieldName = naStr_data(field);
690   Procedure* proc = (Procedure*) g;
691   
692   if (!strcmp(fieldName, "parents")) {
693     *out = naNewVector(c);
694     naVec_append(*out, procedurePrototype);
695   } else if (!strcmp(fieldName, "id")) *out = stringToNasal(c, proc->ident());
696   else if (!strcmp(fieldName, "airport")) *out = ghostForAirport(c, proc->airport());
697   else if (!strcmp(fieldName, "tp_type")) *out = procedureTpType(c, proc->type());
698   else if (!strcmp(fieldName, "radio")) *out = procedureRadioType(c, proc->type());
699   else if (!strcmp(fieldName, "runways")) {
700     *out = naNewVector(c);
701     BOOST_FOREACH(FGRunwayRef rwy, proc->runways()) {
702       naVec_append(*out, stringToNasal(c, rwy->ident()));
703     }
704   } else if (!strcmp(fieldName, "transitions")) {
705     if ((proc->type() != PROCEDURE_SID) && (proc->type() != PROCEDURE_STAR)) {
706       *out = naNil();
707       return "";
708     }
709         
710     ArrivalDeparture* ad = static_cast<ArrivalDeparture*>(proc);
711     *out = naNewVector(c);
712     BOOST_FOREACH(string id, ad->transitionIdents()) {
713       naVec_append(*out, stringToNasal(c, id));
714     }
715   } else {
716     return 0;
717   }
718   
719   return "";
720 }
721
722 static const char* runwayGhostGetMember(naContext c, void* g, naRef field, naRef* out)
723 {
724   const char* fieldName = naStr_data(field);
725   FGRunwayBase* base = (FGRunwayBase*) g;
726   
727   if (!strcmp(fieldName, "id")) *out = stringToNasal(c, base->ident());
728   else if (!strcmp(fieldName, "lat")) *out = naNum(base->latitude());
729   else if (!strcmp(fieldName, "lon")) *out = naNum(base->longitude());
730   else if (!strcmp(fieldName, "heading")) *out = naNum(base->headingDeg());
731   else if (!strcmp(fieldName, "length")) *out = naNum(base->lengthM());
732   else if (!strcmp(fieldName, "width")) *out = naNum(base->widthM());
733   else if (!strcmp(fieldName, "surface")) *out = naNum(base->surface());
734   else if (base->type() == FGRunwayBase::RUNWAY) {  
735     FGRunway* rwy = (FGRunway*) g;
736     if (!strcmp(fieldName, "threshold")) *out = naNum(rwy->displacedThresholdM());
737     else if (!strcmp(fieldName, "stopway")) *out = naNum(rwy->stopwayM());
738     else if (!strcmp(fieldName, "ils_frequency_mhz")) {
739       *out = rwy->ILS() ? naNum(rwy->ILS()->get_freq() / 100.0) : naNil();
740     } else if (!strcmp(fieldName, "ils")) {
741       *out = ghostForNavaid(c, rwy->ILS());
742     } else {
743       return 0;
744     }
745   } else {
746     return 0;    
747   }
748   
749   return "";
750 }
751
752 static const char* navaidGhostGetMember(naContext c, void* g, naRef field, naRef* out)
753 {
754   const char* fieldName = naStr_data(field);
755   FGNavRecord* nav = (FGNavRecord*) g;
756   
757   if (!strcmp(fieldName, "id")) *out = stringToNasal(c, nav->ident());
758   else if (!strcmp(fieldName, "name")) *out = stringToNasal(c, nav->name());
759   else if (!strcmp(fieldName, "lat")) *out = naNum(nav->get_lat());
760   else if (!strcmp(fieldName, "lon")) *out = naNum(nav->get_lon());
761   else if (!strcmp(fieldName, "elevation")) {
762     *out = naNum(nav->get_elev_ft() * SG_FEET_TO_METER);
763   } else if (!strcmp(fieldName, "type")) {
764     *out = stringToNasal(c, nav->nameForType(nav->type()));
765   } else if (!strcmp(fieldName, "frequency")) {
766     *out = naNum(nav->get_freq()); 
767   } else if (!strcmp(fieldName, "range_nm")) {
768     *out = naNum(nav->get_range()); 
769   } else if (!strcmp(fieldName, "course")) {
770     if ((nav->type() == FGPositioned::ILS) || (nav->type() == FGPositioned::LOC)) {
771       double radial = nav->get_multiuse();
772       SG_NORMALIZE_RANGE(radial, 0.0, 360.0);
773       *out = naNum(radial);
774     } else {
775       *out = naNil();
776     }
777   } else {
778     return 0;
779   }
780   
781   return "";
782 }
783
784 static const char* fixGhostGetMember(naContext c, void* g, naRef field, naRef* out)
785 {
786   const char* fieldName = naStr_data(field);
787   FGFix* fix = (FGFix*) g;
788   
789   if (!strcmp(fieldName, "id")) *out = stringToNasal(c, fix->ident());
790   else if (!strcmp(fieldName, "lat")) *out = naNum(fix->get_lat());
791   else if (!strcmp(fieldName, "lon")) *out = naNum(fix->get_lon());
792   else {
793     return 0;
794   }
795   
796   return "";
797 }
798
799 static bool hashIsCoord(naRef h)
800 {
801   naRef parents = naHash_cget(h, (char*) "parents");
802   if (!naIsVector(parents)) {
803     return false;
804   }
805   
806   return naEqual(naVec_get(parents, 0), geoCoordClass) != 0;
807 }
808
809 bool geodFromHash(naRef ref, SGGeod& result)
810 {
811   if (!naIsHash(ref)) {
812     return false;
813   }
814
815   
816 // check for manual latitude / longitude names
817   naRef lat = naHash_cget(ref, (char*) "lat");
818   naRef lon = naHash_cget(ref, (char*) "lon");
819   if (naIsNum(lat) && naIsNum(lon)) {
820     result = SGGeod::fromDeg(naNumValue(lon).num, naNumValue(lat).num);
821     return true;
822   }
823   
824   if (hashIsCoord(ref)) {
825     naRef lat = naHash_cget(ref, (char*) "_lat");
826     naRef lon = naHash_cget(ref, (char*) "_lon");
827     if (naIsNum(lat) && naIsNum(lon)) {
828       result = SGGeod::fromRad(naNumValue(lon).num, naNumValue(lat).num);
829       return true;
830     }
831   }
832     
833 // check for any synonyms?
834     // latitude + longitude?
835   
836   return false;
837 }
838
839 static int geodFromArgs(naRef* args, int offset, int argc, SGGeod& result)
840 {
841   if (offset >= argc) {
842     return 0;
843   }
844   
845   if (naIsGhost(args[offset])) {
846     naGhostType* gt = naGhost_type(args[offset]);
847     if (gt == &AirportGhostType) {
848       result = airportGhost(args[offset])->geod();
849       return 1;
850     }
851     
852     if (gt == &NavaidGhostType) {
853       result = navaidGhost(args[offset])->geod();
854       return 1;
855     }
856     
857     if (gt == &RunwayGhostType) {
858       result = runwayGhost(args[offset])->geod();
859       return 1;
860     }
861     
862     if (gt == &TaxiwayGhostType) {
863       result = taxiwayGhost(args[offset])->geod();
864       return 1;
865     }
866     
867     if (gt == &FixGhostType) {
868       result = fixGhost(args[offset])->geod();
869       return 1;
870     }
871     
872     if (gt == &WayptGhostType) {
873       result = wayptGhost(args[offset])->position();
874       return 1;
875     }
876   }
877   
878   if (geodFromHash(args[offset], result)) {
879     return 1;
880   }
881   
882   if (((argc - offset) >= 2) && naIsNum(args[offset]) && naIsNum(args[offset + 1])) {
883     double lat = naNumValue(args[0]).num,
884     lon = naNumValue(args[1]).num;
885     result = SGGeod::fromDeg(lon, lat);
886     return 2;
887   }
888   
889   return 0;
890 }
891
892 // Convert a cartesian point to a geodetic lat/lon/altitude.
893 static naRef f_carttogeod(naContext c, naRef me, int argc, naRef* args)
894 {
895   double lat, lon, alt, xyz[3];
896   if(argc != 3) naRuntimeError(c, "carttogeod() expects 3 arguments");
897   for(int i=0; i<3; i++)
898     xyz[i] = naNumValue(args[i]).num;
899   sgCartToGeod(xyz, &lat, &lon, &alt);
900   lat *= SG_RADIANS_TO_DEGREES;
901   lon *= SG_RADIANS_TO_DEGREES;
902   naRef vec = naNewVector(c);
903   naVec_append(vec, naNum(lat));
904   naVec_append(vec, naNum(lon));
905   naVec_append(vec, naNum(alt));
906   return vec;
907 }
908
909 // Convert a geodetic lat/lon/altitude to a cartesian point.
910 static naRef f_geodtocart(naContext c, naRef me, int argc, naRef* args)
911 {
912   if(argc != 3) naRuntimeError(c, "geodtocart() expects 3 arguments");
913   double lat = naNumValue(args[0]).num * SG_DEGREES_TO_RADIANS;
914   double lon = naNumValue(args[1]).num * SG_DEGREES_TO_RADIANS;
915   double alt = naNumValue(args[2]).num;
916   double xyz[3];
917   sgGeodToCart(lat, lon, alt, xyz);
918   naRef vec = naNewVector(c);
919   naVec_append(vec, naNum(xyz[0]));
920   naVec_append(vec, naNum(xyz[1]));
921   naVec_append(vec, naNum(xyz[2]));
922   return vec;
923 }
924
925 // For given geodetic point return array with elevation, and a material data
926 // hash, or nil if there's no information available (tile not loaded). If
927 // information about the material isn't available, then nil is returned instead
928 // of the hash.
929 static naRef f_geodinfo(naContext c, naRef me, int argc, naRef* args)
930 {
931 #define HASHSET(s,l,n) naHash_set(matdata, naStr_fromdata(naNewString(c),s,l),n)
932   if(argc < 2 || argc > 3)
933     naRuntimeError(c, "geodinfo() expects 2 or 3 arguments: lat, lon [, maxalt]");
934   double lat = naNumValue(args[0]).num;
935   double lon = naNumValue(args[1]).num;
936   double elev = argc == 3 ? naNumValue(args[2]).num : 10000;
937   const simgear::BVHMaterial *material;
938   SGGeod geod = SGGeod::fromDegM(lon, lat, elev);
939   if(!globals->get_scenery()->get_elevation_m(geod, elev, &material))
940     return naNil();
941   const SGMaterial *mat = dynamic_cast<const SGMaterial *>(material);
942   naRef vec = naNewVector(c);
943   naVec_append(vec, naNum(elev));
944   naRef matdata = naNil();
945   if(mat) {
946     matdata = naNewHash(c);
947     naRef names = naNewVector(c);
948     BOOST_FOREACH(const std::string& n, mat->get_names())
949       naVec_append(names, stringToNasal(c, n));
950       
951     HASHSET("names", 5, names);
952     HASHSET("solid", 5, naNum(mat->get_solid()));
953     HASHSET("friction_factor", 15, naNum(mat->get_friction_factor()));
954     HASHSET("rolling_friction", 16, naNum(mat->get_rolling_friction()));
955     HASHSET("load_resistance", 15, naNum(mat->get_load_resistance()));
956     HASHSET("bumpiness", 9, naNum(mat->get_bumpiness()));
957     HASHSET("light_coverage", 14, naNum(mat->get_light_coverage()));
958   }
959   naVec_append(vec, matdata);
960   return vec;
961 #undef HASHSET
962 }
963
964
965 class AirportInfoFilter : public FGAirport::AirportFilter
966 {
967 public:
968   AirportInfoFilter() : type(FGPositioned::AIRPORT) {
969     minRunwayLengthFt = fgGetDouble("/sim/navdb/min-runway-length-ft", 0.0);
970   }
971   
972   bool fromArg(naRef arg)
973   {
974     const char *s = naStr_data(arg);
975     if(!strcmp(s, "airport")) type = FGPositioned::AIRPORT;
976     else if(!strcmp(s, "seaport")) type = FGPositioned::SEAPORT;
977     else if(!strcmp(s, "heliport")) type = FGPositioned::HELIPORT;
978     else
979       return false;
980     
981     return true;
982   }
983   
984   virtual FGPositioned::Type minType() const {
985     return type;
986   }
987   
988   virtual FGPositioned::Type maxType() const {
989     return type;
990   }
991     
992   virtual bool pass(FGPositioned* aPos) const
993   {
994     FGAirport* apt = (FGAirport*) aPos;
995     if ((apt->type() == FGPositioned::AIRPORT) && 
996         !apt->hasHardRunwayOfLengthFt(minRunwayLengthFt)) 
997     {
998       return false;
999     }
1000
1001     return true;
1002   }
1003   
1004   FGPositioned::Type type;
1005   double minRunwayLengthFt;
1006 };
1007
1008 // Returns data hash for particular or nearest airport of a <type>, or nil
1009 // on error.
1010 //
1011 // airportinfo(<id>);                   e.g. "KSFO"
1012 // airportinfo(<type>);                 type := ("airport"|"seaport"|"heliport")
1013 // airportinfo()                        same as  airportinfo("airport")
1014 // airportinfo(<lat>, <lon> [, <type>]);
1015 static naRef f_airportinfo(naContext c, naRef me, int argc, naRef* args)
1016 {
1017   SGGeod pos = globals->get_aircraft_position();
1018   FGAirport* apt = NULL;
1019   
1020   if(argc >= 2 && naIsNum(args[0]) && naIsNum(args[1])) {
1021     pos = SGGeod::fromDeg(args[1].num, args[0].num);
1022     args += 2;
1023     argc -= 2;
1024   }
1025   
1026   double maxRange = 10000.0; // expose this? or pick a smaller value?
1027   
1028   AirportInfoFilter filter; // defaults to airports only
1029   
1030   if(argc == 0) {
1031     // fall through and use AIRPORT
1032   } else if(argc == 1 && naIsString(args[0])) {
1033     if (filter.fromArg(args[0])) {
1034       // done!
1035     } else {
1036       // user provided an <id>, hopefully
1037       apt = FGAirport::findByIdent(naStr_data(args[0]));
1038       if (!apt) {
1039         // return nil here, but don't raise a runtime error; this is a
1040         // legitamate way to validate an ICAO code, for example in a
1041         // dialog box or similar.
1042         return naNil();
1043       }
1044     }
1045   } else {
1046     naRuntimeError(c, "airportinfo() with invalid function arguments");
1047     return naNil();
1048   }
1049   
1050   if(!apt) {
1051     apt = FGAirport::findClosest(pos, maxRange, &filter);
1052     if(!apt) return naNil();
1053   }
1054   
1055   return ghostForAirport(c, apt);
1056 }
1057
1058 static naRef f_findAirportsWithinRange(naContext c, naRef me, int argc, naRef* args)
1059 {
1060   int argOffset = 0;
1061   SGGeod pos = globals->get_aircraft_position();
1062   argOffset += geodFromArgs(args, 0, argc, pos);
1063   
1064   if (!naIsNum(args[argOffset])) {
1065     naRuntimeError(c, "findAirportsWithinRange expected range (in nm) as arg %d", argOffset);
1066   }
1067   
1068   AirportInfoFilter filter; // defaults to airports only
1069   double rangeNm = args[argOffset++].num;
1070   if (argOffset < argc) {
1071     filter.fromArg(args[argOffset++]);
1072   }
1073   
1074   naRef r = naNewVector(c);
1075   
1076   FGPositioned::List apts = FGPositioned::findWithinRange(pos, rangeNm, &filter);
1077   FGPositioned::sortByRange(apts, pos);
1078   
1079   BOOST_FOREACH(FGPositionedRef a, apts) {
1080     FGAirport* apt = (FGAirport*) a.get();
1081     naVec_append(r, ghostForAirport(c, apt));
1082   }
1083   
1084   return r;
1085 }
1086
1087 static naRef f_findAirportsByICAO(naContext c, naRef me, int argc, naRef* args)
1088 {
1089   if (!naIsString(args[0])) {
1090     naRuntimeError(c, "findAirportsByICAO expects string as arg 0");
1091   }
1092   
1093   int argOffset = 0;
1094   string prefix(naStr_data(args[argOffset++]));
1095   AirportInfoFilter filter; // defaults to airports only
1096   if (argOffset < argc) {
1097     filter.fromArg(args[argOffset++]);
1098   }
1099   
1100   naRef r = naNewVector(c);
1101   
1102   FGPositioned::List apts = FGPositioned::findAllWithIdent(prefix, &filter, false);
1103   
1104   BOOST_FOREACH(FGPositionedRef a, apts) {
1105     FGAirport* apt = (FGAirport*) a.get();
1106     naVec_append(r, ghostForAirport(c, apt));
1107   }
1108   
1109   return r;
1110 }
1111
1112 static naRef f_airport_tower(naContext c, naRef me, int argc, naRef* args)
1113 {
1114     FGAirport* apt = airportGhost(me);
1115     if (!apt) {
1116       naRuntimeError(c, "airport.tower called on non-airport object");
1117     }
1118   
1119     // build a hash for the tower position    
1120     SGGeod towerLoc = apt->getTowerLocation();
1121     naRef tower = naNewHash(c);
1122     hashset(c, tower, "lat", naNum(towerLoc.getLatitudeDeg()));
1123     hashset(c, tower, "lon", naNum(towerLoc.getLongitudeDeg()));
1124     hashset(c, tower, "elevation", naNum(towerLoc.getElevationM()));
1125     return tower;
1126 }
1127
1128 static naRef f_airport_comms(naContext c, naRef me, int argc, naRef* args)
1129 {
1130     FGAirport* apt = airportGhost(me);
1131     if (!apt) {
1132       naRuntimeError(c, "airport.comms called on non-airport object");
1133     }
1134     naRef comms = naNewVector(c);
1135     
1136 // if we have an explicit type, return a simple vector of frequencies
1137     if (argc > 0 && naIsScalar(args[0])) {
1138         std::string commName = naStr_data(args[0]);
1139         FGPositioned::Type commType = FGPositioned::typeFromName(commName);
1140         
1141         BOOST_FOREACH(flightgear::CommStation* comm, apt->commStationsOfType(commType)) {
1142             naVec_append(comms, naNum(comm->freqMHz()));
1143         }
1144     } else {
1145 // otherwise return a vector of hashes, one for each comm station.
1146         BOOST_FOREACH(flightgear::CommStation* comm, apt->commStations()) {
1147             naRef commHash = naNewHash(c);
1148             hashset(c, commHash, "frequency", naNum(comm->freqMHz()));
1149             hashset(c, commHash, "ident", stringToNasal(c, comm->ident()));
1150             naVec_append(comms, commHash);
1151         }
1152     }
1153     
1154     return comms;
1155 }
1156
1157 static naRef f_airport_runway(naContext c, naRef me, int argc, naRef* args)
1158 {
1159   FGAirport* apt = airportGhost(me);
1160   if (!apt) {
1161     naRuntimeError(c, "airport.runway called on non-airport object");
1162   }
1163   
1164   if ((argc < 1) || !naIsString(args[0])) {
1165     naRuntimeError(c, "airport.runway expects a runway ident argument");
1166   }
1167   
1168   std::string ident(naStr_data(args[0]));
1169   boost::to_upper(ident);
1170   if (!apt->hasRunwayWithIdent(ident)) {
1171     return naNil();
1172   }
1173   
1174   return ghostForRunway(c, apt->getRunwayByIdent(ident));
1175 }
1176
1177 static naRef f_airport_taxiway(naContext c, naRef me, int argc, naRef* args)
1178 {
1179   FGAirport* apt = airportGhost(me);
1180   if (!apt) {
1181     naRuntimeError(c, "airport.taxiway called on non-airport object");
1182   }
1183   
1184   if ((argc < 1) || !naIsString(args[0])) {
1185     naRuntimeError(c, "airport.taxiway expects a taxiway ident argument");
1186   }
1187   
1188   naRef taxiways = naNewVector(c);
1189   
1190   for (unsigned int i = 0; i < apt->numTaxiways(); i++) {
1191     naVec_append(taxiways, ghostForTaxiway(c, apt->getTaxiwayByIndex(i)));
1192   }
1193   
1194   return taxiways;  
1195 }
1196
1197 static naRef f_airport_sids(naContext c, naRef me, int argc, naRef* args)
1198 {
1199   FGAirport* apt = airportGhost(me);
1200   if (!apt) {
1201     naRuntimeError(c, "airport.sids called on non-airport object");
1202   }
1203   
1204   naRef sids = naNewVector(c);
1205   
1206   FGRunway* rwy = NULL;
1207   if (argc > 0 && naIsString(args[0])) {
1208     if (!apt->hasRunwayWithIdent(naStr_data(args[0]))) {
1209       return naNil();
1210     }
1211
1212     rwy = apt->getRunwayByIdent(naStr_data(args[0]));
1213   } else if (argc > 0) {
1214     rwy = runwayGhost(args[0]);
1215   }
1216
1217   if (rwy) {
1218     BOOST_FOREACH(flightgear::SID* sid, rwy->getSIDs()) {
1219       naRef procId = stringToNasal(c, sid->ident());
1220       naVec_append(sids, procId);
1221     }
1222   } else {
1223     for (unsigned int s=0; s<apt->numSIDs(); ++s) {
1224       flightgear::SID* sid = apt->getSIDByIndex(s);
1225       naRef procId = stringToNasal(c, sid->ident());
1226       naVec_append(sids, procId);
1227     }
1228   }
1229   
1230   return sids;
1231 }
1232
1233 static naRef f_airport_stars(naContext c, naRef me, int argc, naRef* args)
1234 {
1235   FGAirport* apt = airportGhost(me);
1236   if (!apt) {
1237     naRuntimeError(c, "airport.stars called on non-airport object");
1238   }
1239   
1240   naRef stars = naNewVector(c);
1241   
1242   FGRunway* rwy = NULL;
1243   if (argc > 0 && naIsString(args[0])) {
1244     if (!apt->hasRunwayWithIdent(naStr_data(args[0]))) {
1245       return naNil();
1246     }
1247         
1248     rwy = apt->getRunwayByIdent(naStr_data(args[0]));
1249   } else if (argc > 0) {
1250     rwy = runwayGhost(args[0]);
1251   }
1252   
1253   if (rwy) {
1254     BOOST_FOREACH(flightgear::STAR* s, rwy->getSTARs()) {
1255       naRef procId = stringToNasal(c, s->ident());
1256       naVec_append(stars, procId);
1257     }
1258   } else {
1259     for (unsigned int s=0; s<apt->numSTARs(); ++s) {
1260       flightgear::STAR* star = apt->getSTARByIndex(s);
1261       naRef procId = stringToNasal(c, star->ident());
1262       naVec_append(stars, procId);
1263     }
1264   }
1265   
1266   return stars;
1267 }
1268
1269 static naRef f_airport_approaches(naContext c, naRef me, int argc, naRef* args)
1270 {
1271   FGAirport* apt = airportGhost(me);
1272   if (!apt) {
1273     naRuntimeError(c, "airport.getApproachList called on non-airport object");
1274   }
1275   
1276   naRef approaches = naNewVector(c);
1277   
1278   ProcedureType ty = PROCEDURE_INVALID;
1279   if ((argc > 1) && naIsString(args[1])) {
1280     std::string u(naStr_data(args[1]));
1281     boost::to_upper(u);
1282     if (u == "NDB") ty = PROCEDURE_APPROACH_NDB;
1283     if (u == "VOR") ty = PROCEDURE_APPROACH_VOR;
1284     if (u == "ILS") ty = PROCEDURE_APPROACH_ILS;
1285     if (u == "RNAV") ty = PROCEDURE_APPROACH_RNAV;
1286   }
1287   
1288   FGRunway* rwy = NULL;
1289   if (argc > 0 && (rwy = runwayGhost(args[0]))) {
1290     // ok
1291   } else if (argc > 0 && naIsString(args[0])) {
1292     if (!apt->hasRunwayWithIdent(naStr_data(args[0]))) {
1293       return naNil();
1294     }
1295     
1296     rwy = apt->getRunwayByIdent(naStr_data(args[0]));
1297   }
1298   
1299   if (rwy) {
1300     BOOST_FOREACH(Approach* s, rwy->getApproaches()) {
1301       if ((ty != PROCEDURE_INVALID) && (s->type() != ty)) {
1302         continue;
1303       }
1304       
1305       naRef procId = stringToNasal(c, s->ident());
1306       naVec_append(approaches, procId);
1307     }
1308   } else {
1309     // no runway specified, report them all
1310     for (unsigned int s=0; s<apt->numApproaches(); ++s) {
1311       Approach* app = apt->getApproachByIndex(s);
1312       if ((ty != PROCEDURE_INVALID) && (app->type() != ty)) {
1313         continue;
1314       }
1315       
1316       naRef procId = stringToNasal(c, app->ident());
1317       naVec_append(approaches, procId);
1318     }
1319   }
1320   
1321   return approaches;
1322 }
1323
1324 static naRef f_airport_parking(naContext c, naRef me, int argc, naRef* args)
1325 {
1326   FGAirport* apt = airportGhost(me);
1327   if (!apt) {
1328     naRuntimeError(c, "airport.parking called on non-airport object");
1329   }
1330   
1331   naRef r = naNewVector(c);
1332   std::string type;
1333   bool onlyAvailable = false;
1334   
1335   if (argc > 0 && naIsString(args[0])) {
1336     type = naStr_data(args[0]);
1337   }
1338   
1339   if ((argc > 1) && naIsNum(args[1])) {
1340     onlyAvailable = (args[1].num != 0.0);
1341   }
1342   
1343   FGAirportDynamics* dynamics = apt->getDynamics();
1344   PositionedIDVec parkings = flightgear::NavDataCache::instance()->airportItemsOfType(apt->guid(),
1345                                                                                       FGPositioned::PARKING);
1346   
1347   BOOST_FOREACH(PositionedID parking, parkings) {
1348     // filter out based on availability and type
1349     if (onlyAvailable && !dynamics->isParkingAvailable(parking)) {
1350       continue;
1351     }
1352     
1353     FGParking* park = dynamics->getParking(parking);
1354     if (!type.empty() && (park->getType() != type)) {
1355       continue;
1356     }
1357     
1358     const SGGeod& parkLoc = park->geod();
1359     naRef ph = naNewHash(c);
1360     hashset(c, ph, "name", stringToNasal(c, park->getName()));
1361     hashset(c, ph, "lat", naNum(parkLoc.getLatitudeDeg()));
1362     hashset(c, ph, "lon", naNum(parkLoc.getLongitudeDeg()));
1363     hashset(c, ph, "elevation", naNum(parkLoc.getElevationM()));
1364     naVec_append(r, ph);
1365   }
1366   
1367   return r;
1368 }
1369
1370 static naRef f_airport_getSid(naContext c, naRef me, int argc, naRef* args)
1371 {
1372   FGAirport* apt = airportGhost(me);
1373   if (!apt) {
1374     naRuntimeError(c, "airport.getSid called on non-airport object");
1375   }
1376   
1377   if ((argc != 1) || !naIsString(args[0])) {
1378     naRuntimeError(c, "airport.getSid passed invalid argument");
1379   }
1380   
1381   string ident = naStr_data(args[0]);
1382   return ghostForProcedure(c, apt->findSIDWithIdent(ident));
1383 }
1384
1385 static naRef f_airport_getStar(naContext c, naRef me, int argc, naRef* args)
1386 {
1387   FGAirport* apt = airportGhost(me);
1388   if (!apt) {
1389     naRuntimeError(c, "airport.getStar called on non-airport object");
1390   }
1391   
1392   if ((argc != 1) || !naIsString(args[0])) {
1393     naRuntimeError(c, "airport.getStar passed invalid argument");
1394   }
1395   
1396   string ident = naStr_data(args[0]);
1397   return ghostForProcedure(c, apt->findSTARWithIdent(ident));
1398 }
1399
1400 static naRef f_airport_getApproach(naContext c, naRef me, int argc, naRef* args)
1401 {
1402   FGAirport* apt = airportGhost(me);
1403   if (!apt) {
1404     naRuntimeError(c, "airport.getIAP called on non-airport object");
1405   }
1406   
1407   if ((argc != 1) || !naIsString(args[0])) {
1408     naRuntimeError(c, "airport.getIAP passed invalid argument");
1409   }
1410   
1411   string ident = naStr_data(args[0]);
1412   return ghostForProcedure(c, apt->findApproachWithIdent(ident));
1413 }
1414
1415 static naRef f_airport_toString(naContext c, naRef me, int argc, naRef* args)
1416 {
1417   FGAirport* apt = airportGhost(me);
1418   if (!apt) {
1419     naRuntimeError(c, "airport.tostring called on non-airport object");
1420   }
1421   
1422   return stringToNasal(c, "an airport " + apt->ident());
1423 }
1424
1425 // Returns vector of data hash for navaid of a <type>, nil on error
1426 // navaids sorted by ascending distance 
1427 // navinfo([<lat>,<lon>],[<type>],[<id>])
1428 // lat/lon (numeric): use latitude/longitude instead of ac position
1429 // type:              ("fix"|"vor"|"ndb"|"ils"|"dme"|"tacan"|"any")
1430 // id:                (partial) id of the fix
1431 // examples:
1432 // navinfo("vor")     returns all vors
1433 // navinfo("HAM")     return all navaids who's name start with "HAM"
1434 // navinfo("vor", "HAM") return all vor who's name start with "HAM"
1435 //navinfo(34,48,"vor","HAM") return all vor who's name start with "HAM" 
1436 //                           sorted by distance relative to lat=34, lon=48
1437 static naRef f_navinfo(naContext c, naRef me, int argc, naRef* args)
1438 {
1439   SGGeod pos;
1440   
1441   if(argc >= 2 && naIsNum(args[0]) && naIsNum(args[1])) {
1442     pos = SGGeod::fromDeg(args[1].num, args[0].num);
1443     args += 2;
1444     argc -= 2;
1445   } else {
1446     pos = globals->get_aircraft_position();
1447   }
1448   
1449   FGPositioned::Type type = FGPositioned::INVALID;
1450   nav_list_type navlist;
1451   const char * id = "";
1452   
1453   if(argc > 0 && naIsString(args[0])) {
1454     const char *s = naStr_data(args[0]);
1455     if(!strcmp(s, "any")) type = FGPositioned::INVALID;
1456     else if(!strcmp(s, "fix")) type = FGPositioned::FIX;
1457     else if(!strcmp(s, "vor")) type = FGPositioned::VOR;
1458     else if(!strcmp(s, "ndb")) type = FGPositioned::NDB;
1459     else if(!strcmp(s, "ils")) type = FGPositioned::ILS;
1460     else if(!strcmp(s, "dme")) type = FGPositioned::DME;
1461     else if(!strcmp(s, "tacan")) type = FGPositioned::TACAN;
1462     else id = s; // this is an id
1463     ++args;
1464     --argc;
1465   } 
1466   
1467   if(argc > 0 && naIsString(args[0])) {
1468     if( *id != 0 ) {
1469       naRuntimeError(c, "navinfo() called with navaid id");
1470       return naNil();
1471     }
1472     id = naStr_data(args[0]);
1473     ++args;
1474     --argc;
1475   }
1476   
1477   if( argc > 0 ) {
1478     naRuntimeError(c, "navinfo() called with too many arguments");
1479     return naNil();
1480   }
1481   
1482   FGNavList::TypeFilter filter(type);
1483   navlist = FGNavList::findByIdentAndFreq( pos, id, 0.0, &filter );
1484   
1485   naRef reply = naNewVector(c);
1486   for( nav_list_type::const_iterator it = navlist.begin(); it != navlist.end(); ++it ) {
1487     naVec_append( reply, ghostForNavaid(c, *it) );
1488   }
1489   return reply;
1490 }
1491
1492 static naRef f_findNavaidsWithinRange(naContext c, naRef me, int argc, naRef* args)
1493 {
1494   int argOffset = 0;
1495   SGGeod pos = globals->get_aircraft_position();
1496   argOffset += geodFromArgs(args, 0, argc, pos);
1497   
1498   if (!naIsNum(args[argOffset])) {
1499     naRuntimeError(c, "findNavaidsWithinRange expected range (in nm) as arg %d", argOffset);
1500   }
1501   
1502   FGPositioned::Type type = FGPositioned::INVALID;
1503   double rangeNm = args[argOffset++].num;
1504   if (argOffset < argc) {
1505     type = FGPositioned::typeFromName(naStr_data(args[argOffset]));
1506   }
1507   
1508   naRef r = naNewVector(c);
1509   FGNavList::TypeFilter filter(type);
1510   FGPositioned::List navs = FGPositioned::findWithinRange(pos, rangeNm, &filter);
1511   FGPositioned::sortByRange(navs, pos);
1512   
1513   BOOST_FOREACH(FGPositionedRef a, navs) {
1514     FGNavRecord* nav = (FGNavRecord*) a.get();
1515     naVec_append(r, ghostForNavaid(c, nav));
1516   }
1517   
1518   return r;
1519 }
1520
1521 static naRef f_findNavaidByFrequency(naContext c, naRef me, int argc, naRef* args)
1522 {
1523   int argOffset = 0;
1524   SGGeod pos = globals->get_aircraft_position();
1525   argOffset += geodFromArgs(args, 0, argc, pos);
1526   
1527   if (!naIsNum(args[argOffset])) {
1528     naRuntimeError(c, "findNavaidByFrequency expectes frequency (in Mhz) as arg %d", argOffset);
1529   }
1530   
1531   FGPositioned::Type type = FGPositioned::INVALID;
1532   double freqMhz = args[argOffset++].num;
1533   if (argOffset < argc) {
1534     type = FGPositioned::typeFromName(naStr_data(args[argOffset]));
1535   }
1536   
1537   FGNavList::TypeFilter filter(type);
1538   nav_list_type navs = FGNavList::findAllByFreq(freqMhz, pos, &filter);
1539   if (navs.empty()) {
1540     return naNil();
1541   }
1542   
1543   return ghostForNavaid(c, navs.front().ptr());
1544 }
1545
1546 static naRef f_findNavaidsByFrequency(naContext c, naRef me, int argc, naRef* args)
1547 {
1548   int argOffset = 0;
1549   SGGeod pos = globals->get_aircraft_position();
1550   argOffset += geodFromArgs(args, 0, argc, pos);
1551   
1552   if (!naIsNum(args[argOffset])) {
1553     naRuntimeError(c, "findNavaidsByFrequency expectes frequency (in Mhz) as arg %d", argOffset);
1554   }
1555   
1556   FGPositioned::Type type = FGPositioned::INVALID;
1557   double freqMhz = args[argOffset++].num;
1558   if (argOffset < argc) {
1559     type = FGPositioned::typeFromName(naStr_data(args[argOffset]));
1560   }
1561   
1562   naRef r = naNewVector(c);
1563   
1564   FGNavList::TypeFilter filter(type);
1565   nav_list_type navs = FGNavList::findAllByFreq(freqMhz, pos, &filter);
1566   
1567   BOOST_FOREACH(nav_rec_ptr a, navs) {
1568     naVec_append(r, ghostForNavaid(c, a.ptr()));
1569   }
1570   
1571   return r;
1572 }
1573
1574 static naRef f_findNavaidsByIdent(naContext c, naRef me, int argc, naRef* args)
1575 {
1576   int argOffset = 0;
1577   SGGeod pos = globals->get_aircraft_position();
1578   argOffset += geodFromArgs(args, 0, argc, pos);
1579   
1580   if (!naIsString(args[argOffset])) {
1581     naRuntimeError(c, "findNavaidsByIdent expectes ident string as arg %d", argOffset);
1582   }
1583   
1584   FGPositioned::Type type = FGPositioned::INVALID;
1585   string ident = naStr_data(args[argOffset++]);
1586   if (argOffset < argc) {
1587     type = FGPositioned::typeFromName(naStr_data(args[argOffset]));
1588   }
1589   
1590   FGNavList::TypeFilter filter(type);
1591   naRef r = naNewVector(c);
1592   nav_list_type navs = FGNavList::findByIdentAndFreq(pos, ident, 0.0, &filter);
1593   
1594   BOOST_FOREACH(nav_rec_ptr a, navs) {
1595     naVec_append(r, ghostForNavaid(c, a.ptr()));
1596   }
1597   
1598   return r;
1599 }
1600
1601 static naRef f_findFixesByIdent(naContext c, naRef me, int argc, naRef* args)
1602 {
1603   int argOffset = 0;
1604   SGGeod pos = globals->get_aircraft_position();
1605   argOffset += geodFromArgs(args, 0, argc, pos);
1606   
1607   if (!naIsString(args[argOffset])) {
1608     naRuntimeError(c, "findFixesByIdent expectes ident string as arg %d", argOffset);
1609   }
1610   
1611   string ident(naStr_data(args[argOffset]));
1612   naRef r = naNewVector(c);
1613   
1614   FGPositioned::TypeFilter filter(FGPositioned::FIX);
1615   FGPositioned::List fixes = FGPositioned::findAllWithIdent(ident, &filter);
1616   FGPositioned::sortByRange(fixes, pos);
1617   
1618   BOOST_FOREACH(FGPositionedRef f, fixes) {
1619     naVec_append(r, ghostForFix(c, (FGFix*) f.ptr()));
1620   }
1621   
1622   return r;
1623 }
1624
1625 // Convert a cartesian point to a geodetic lat/lon/altitude.
1626 static naRef f_magvar(naContext c, naRef me, int argc, naRef* args)
1627 {
1628   SGGeod pos = globals->get_aircraft_position();
1629   if (argc == 0) {
1630     // fine, use aircraft position
1631   } else if (geodFromArgs(args, 0, argc, pos)) {
1632     // okay
1633   } else {
1634     naRuntimeError(c, "magvar() expects no arguments, a positioned hash or lat,lon pair");
1635   }
1636   
1637   double jd = globals->get_time_params()->getJD();
1638   double magvarDeg = sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
1639   return naNum(magvarDeg);
1640 }
1641
1642 static naRef f_courseAndDistance(naContext c, naRef me, int argc, naRef* args)
1643 {
1644     SGGeod from = globals->get_aircraft_position(), to, p;
1645     int argOffset = geodFromArgs(args, 0, argc, p);
1646     if (geodFromArgs(args, argOffset, argc, to)) {
1647       from = p; // we parsed both FROM and TO args, so first was from
1648     } else {
1649       to = p; // only parsed one arg, so FROM is current
1650     }
1651   
1652     if (argOffset == 0) {
1653         naRuntimeError(c, "invalid arguments to courseAndDistance");
1654     }
1655     
1656     double course, course2, d;
1657     SGGeodesy::inverse(from, to, course, course2, d);
1658     
1659     naRef result = naNewVector(c);
1660     naVec_append(result, naNum(course));
1661     naVec_append(result, naNum(d * SG_METER_TO_NM));
1662     return result;
1663 }
1664
1665 static naRef f_greatCircleMove(naContext c, naRef me, int argc, naRef* args)
1666 {
1667   SGGeod from = globals->get_aircraft_position(), to;
1668   int argOffset = 0;
1669   
1670   // complication - don't inerpret two doubles (as the only args)
1671   // as a lat,lon pair - only do so if we have at least three args.
1672   if (argc > 2) {
1673     argOffset = geodFromArgs(args, 0, argc, from);
1674   }
1675   
1676   if ((argOffset + 1) >= argc) {
1677     naRuntimeError(c, "isufficent arguments to greatCircleMove");
1678   }
1679   
1680   if (!naIsNum(args[argOffset]) || !naIsNum(args[argOffset+1])) {
1681     naRuntimeError(c, "invalid arguments %d and %d to greatCircleMove",
1682                    argOffset, argOffset + 1);
1683   }
1684   
1685   double course = args[argOffset].num, course2;
1686   double distanceNm = args[argOffset + 1].num;
1687   SGGeodesy::direct(from, course, distanceNm * SG_NM_TO_METER, to, course2);
1688   
1689   // return geo.Coord
1690   naRef coord = naNewHash(c);
1691   hashset(c, coord, "lat", naNum(to.getLatitudeDeg()));
1692   hashset(c, coord, "lon", naNum(to.getLongitudeDeg()));
1693   return coord;
1694 }
1695
1696 static naRef f_tilePath(naContext c, naRef me, int argc, naRef* args)
1697 {
1698     SGGeod pos = globals->get_aircraft_position();
1699     geodFromArgs(args, 0, argc, pos);
1700     SGBucket b(pos);
1701     return stringToNasal(c, b.gen_base_path());
1702 }
1703
1704 static naRef f_tileIndex(naContext c, naRef me, int argc, naRef* args)
1705 {
1706   SGGeod pos = globals->get_aircraft_position();
1707   geodFromArgs(args, 0, argc, pos);
1708   SGBucket b(pos);
1709   return naNum(b.gen_index());
1710 }
1711
1712 static naRef f_route(naContext c, naRef me, int argc, naRef* args)
1713 {
1714   if (argc == 0) {
1715     FGRouteMgr* rm = static_cast<FGRouteMgr*>(globals->get_subsystem("route-manager"));  
1716     return ghostForFlightPlan(c, rm->flightPlan());
1717   }
1718   
1719   if ((argc > 0) && naIsString(args[0])) {
1720     flightgear::FlightPlan* fp = new flightgear::FlightPlan;
1721     SGPath path(naStr_data(args[0]));
1722     if (!path.exists()) {
1723       naRuntimeError(c, "flightplan, no file at path %s", path.c_str());
1724     }
1725     
1726     if (!fp->load(path)) {
1727       SG_LOG(SG_NASAL, SG_WARN, "failed to load flight-plan from " << path);
1728       delete fp;
1729       return naNil();
1730     }
1731     
1732     return ghostForFlightPlan(c, fp);
1733   }
1734   
1735   naRuntimeError(c, "bad arguments to flightplan()");
1736   return naNil();
1737 }
1738
1739 class NasalFPDelegate : public FlightPlan::Delegate
1740 {
1741 public:
1742   NasalFPDelegate(FlightPlan* fp, FGNasalSys* sys, naRef ins) :
1743     _nasal(sys),
1744     _plan(fp),
1745     _instance(ins)
1746   {
1747     SG_LOG(SG_NASAL, SG_INFO, "created Nasal delegate for " << fp);
1748     _gcSaveKey = _nasal->gcSave(ins);
1749   }
1750   
1751   virtual ~NasalFPDelegate()
1752   {
1753     SG_LOG(SG_NASAL, SG_INFO, "destroying Nasal delegate for " << _plan);
1754     _nasal->gcRelease(_gcSaveKey);
1755   }
1756   
1757   virtual void departureChanged()
1758   {
1759     callDelegateMethod("departureChanged");
1760   }
1761   
1762   virtual void arrivalChanged()
1763   {
1764     callDelegateMethod("arrivalChanged");
1765   }
1766   
1767   virtual void waypointsChanged()
1768   {
1769     callDelegateMethod("waypointsChanged");
1770   }
1771   
1772   virtual void currentWaypointChanged()
1773   {
1774     callDelegateMethod("currentWaypointChanged");
1775   }
1776     
1777   virtual void cleared()
1778   {
1779     callDelegateMethod("cleared");
1780   }
1781 private:
1782   
1783   void callDelegateMethod(const char* method)
1784   {
1785     naRef f;
1786     naMember_cget(_nasal->context(), _instance, method, &f);
1787     if (naIsNil(f)) {
1788       return; // no method on the delegate
1789     }
1790     
1791     naRef arg[1];
1792     arg[0] = ghostForFlightPlan(_nasal->context(), _plan);
1793     _nasal->callMethod(f, _instance, 1, arg, naNil());
1794   }
1795   
1796   FGNasalSys* _nasal;
1797   FlightPlan* _plan;
1798   naRef _instance;
1799   int _gcSaveKey;
1800 };
1801
1802 class NasalFPDelegateFactory : public FlightPlan::DelegateFactory
1803 {
1804 public:
1805   NasalFPDelegateFactory(naRef code)
1806   {
1807     _nasal = (FGNasalSys*) globals->get_subsystem("nasal");
1808     _func = code;
1809     _gcSaveKey = _nasal->gcSave(_func);
1810   }
1811   
1812   ~NasalFPDelegateFactory()
1813   {
1814     _nasal->gcRelease(_gcSaveKey);
1815   }
1816   
1817   virtual FlightPlan::Delegate* createFlightPlanDelegate(FlightPlan* fp)
1818   {
1819     naRef args[1];
1820     args[0] = ghostForFlightPlan(_nasal->context(), fp);
1821     naRef instance = _nasal->call(_func, 1, args, naNil());
1822     if (naIsNil(instance)) {
1823       return NULL;
1824     }
1825     
1826     return new NasalFPDelegate(fp, _nasal, instance);
1827   }
1828 private:
1829   FGNasalSys* _nasal;
1830   naRef _func;
1831   int _gcSaveKey;
1832 };
1833
1834 static naRef f_registerFPDelegate(naContext c, naRef me, int argc, naRef* args)
1835 {
1836   if ((argc < 1) || !naIsFunc(args[0])) {
1837     naRuntimeError(c, "non-function argument to registerFlightPlanDelegate");
1838   }
1839   
1840   NasalFPDelegateFactory* factory = new NasalFPDelegateFactory(args[0]);
1841   FlightPlan::registerDelegateFactory(factory);
1842   
1843   return naNil();
1844 }
1845
1846 static WayptRef wayptFromArg(naRef arg)
1847 {
1848   WayptRef r = wayptGhost(arg);
1849   if (r.valid()) {
1850     return r;
1851   }
1852   
1853   FGPositioned* pos = positionedGhost(arg);
1854   if (!pos) {
1855     // let's check if the arg is hash, coudl extra a geod and hence build
1856     // a simple waypoint
1857     
1858     return WayptRef();
1859   }
1860   
1861 // special-case for runways
1862   if (pos->type() == FGPositioned::RUNWAY) {
1863     return new RunwayWaypt((FGRunway*) pos, NULL);
1864   }
1865   
1866   return new NavaidWaypoint(pos, NULL);
1867 }
1868
1869 static naRef convertWayptVecToNasal(naContext c, const WayptVec& wps)
1870 {
1871   naRef result = naNewVector(c);
1872   BOOST_FOREACH(WayptRef wpt, wps) {
1873     naVec_append(result, ghostForWaypt(c, wpt.get()));
1874   }
1875   return result;
1876 }
1877
1878 static naRef f_airwaySearch(naContext c, naRef me, int argc, naRef* args)
1879 {
1880   if (argc < 2) {
1881     naRuntimeError(c, "airwaysSearch needs at least two arguments");
1882   }
1883   
1884   WayptRef start = wayptFromArg(args[0]), 
1885     end = wayptFromArg(args[1]);
1886   
1887   if (!start || !end) {
1888     SG_LOG(SG_NASAL, SG_WARN, "airwaysSearch: start or end points are invalid");
1889     return naNil();
1890   }
1891   
1892   bool highLevel = true;
1893   if ((argc > 2) && naIsString(args[2])) {
1894     if (!strcmp(naStr_data(args[2]), "lowlevel")) {
1895       highLevel = false;
1896     }
1897   }
1898   
1899   WayptVec route;
1900   if (highLevel) {
1901     Airway::highLevel()->route(start, end, route);
1902   } else {
1903     Airway::lowLevel()->route(start, end, route);
1904   }
1905   
1906   return convertWayptVecToNasal(c, route);
1907 }
1908
1909 static naRef f_createWP(naContext c, naRef me, int argc, naRef* args)
1910 {
1911   SGGeod pos;
1912   int argOffset = geodFromArgs(args, 0, argc, pos);
1913   
1914   if (((argc - argOffset) < 1) || !naIsString(args[argOffset])) {
1915     naRuntimeError(c, "createWP: no identifier supplied");
1916   }
1917     
1918   string ident = naStr_data(args[argOffset++]);
1919   WayptRef wpt = new BasicWaypt(pos, ident, NULL);
1920   
1921 // set waypt flags - approach, departure, pseudo, etc
1922   if (argc > argOffset) {
1923     WayptFlag f = wayptFlagFromString(naStr_data(args[argOffset++]));
1924     wpt->setFlag(f);
1925   }
1926   
1927   return ghostForWaypt(c, wpt);
1928 }
1929
1930 static naRef f_createWPFrom(naContext c, naRef me, int argc, naRef* args)
1931 {
1932   if (argc < 1) {
1933     naRuntimeError(c, "createWPFrom: need at least one argument");
1934   }
1935   
1936   FGPositioned* positioned = positionedGhost(args[0]);
1937   if (!positioned) {
1938     naRuntimeError(c, "createWPFrom: couldn;t convert arg[0] to FGPositioned");
1939   }
1940   
1941   WayptRef wpt;
1942   if (positioned->type() == FGPositioned::RUNWAY) {
1943     wpt = new RunwayWaypt((FGRunway*) positioned, NULL);
1944   } else {
1945     wpt = new NavaidWaypoint(positioned, NULL);
1946   }
1947
1948   // set waypt flags - approach, departure, pseudo, etc
1949   if (argc > 1) {
1950     WayptFlag f = wayptFlagFromString(naStr_data(args[1]));
1951     wpt->setFlag(f);
1952   }
1953   
1954   return ghostForWaypt(c, wpt);
1955 }
1956
1957 static naRef f_flightplan_getWP(naContext c, naRef me, int argc, naRef* args)
1958 {
1959   FlightPlan* fp = flightplanGhost(me);
1960   if (!fp) {
1961     naRuntimeError(c, "flightplan.getWP called on non-flightplan object");
1962   }
1963
1964   int index;
1965   if (argc == 0) {
1966     index = fp->currentIndex();
1967   } else {
1968     index = (int) naNumValue(args[0]).num;
1969   }
1970   
1971   if ((index < 0) || (index >= fp->numLegs())) {
1972     return naNil();
1973   }
1974   
1975   return ghostForLeg(c, fp->legAtIndex(index));
1976 }
1977
1978 static naRef f_flightplan_currentWP(naContext c, naRef me, int argc, naRef* args)
1979 {
1980   FlightPlan* fp = flightplanGhost(me);
1981   if (!fp) {
1982     naRuntimeError(c, "flightplan.currentWP called on non-flightplan object");
1983   }
1984   return ghostForLeg(c, fp->currentLeg());
1985 }
1986
1987 static naRef f_flightplan_nextWP(naContext c, naRef me, int argc, naRef* args)
1988 {
1989   FlightPlan* fp = flightplanGhost(me);
1990   if (!fp) {
1991     naRuntimeError(c, "flightplan.nextWP called on non-flightplan object");
1992   }
1993   return ghostForLeg(c, fp->nextLeg());
1994 }
1995
1996 static naRef f_flightplan_numWaypoints(naContext c, naRef me, int argc, naRef* args)
1997 {
1998   FlightPlan* fp = flightplanGhost(me);
1999   if (!fp) {
2000     naRuntimeError(c, "flightplan.numWaypoints called on non-flightplan object");
2001   }
2002   return naNum(fp->numLegs());
2003 }
2004
2005 static naRef f_flightplan_appendWP(naContext c, naRef me, int argc, naRef* args)
2006 {
2007   FlightPlan* fp = flightplanGhost(me);
2008   if (!fp) {
2009     naRuntimeError(c, "flightplan.appendWP called on non-flightplan object");
2010   }
2011   
2012   WayptRef wp = wayptGhost(args[0]);
2013   int index = fp->numLegs();
2014   fp->insertWayptAtIndex(wp.get(), index);
2015   return naNum(index);
2016 }
2017
2018 static naRef f_flightplan_insertWP(naContext c, naRef me, int argc, naRef* args)
2019 {
2020   FlightPlan* fp = flightplanGhost(me);
2021   if (!fp) {
2022     naRuntimeError(c, "flightplan.insertWP called on non-flightplan object");
2023   }
2024   
2025   WayptRef wp = wayptGhost(args[0]);
2026   int index = -1; // append
2027   if ((argc > 1) && naIsNum(args[1])) {
2028     index = (int) args[1].num;
2029   }
2030   
2031   fp->insertWayptAtIndex(wp.get(), index);
2032   return naNil();
2033 }
2034
2035 static naRef f_flightplan_insertWPAfter(naContext c, naRef me, int argc, naRef* args)
2036 {
2037   FlightPlan* fp = flightplanGhost(me);
2038   if (!fp) {
2039     naRuntimeError(c, "flightplan.insertWPAfter called on non-flightplan object");
2040   }
2041   
2042   WayptRef wp = wayptGhost(args[0]);
2043   int index = -1; // append
2044   if ((argc > 1) && naIsNum(args[1])) {
2045     index = (int) args[1].num;
2046   }
2047   
2048   fp->insertWayptAtIndex(wp.get(), index + 1);
2049   return naNil();
2050 }
2051
2052 static naRef f_flightplan_insertWaypoints(naContext c, naRef me, int argc, naRef* args)
2053 {
2054   FlightPlan* fp = flightplanGhost(me);
2055   if (!fp) {
2056     naRuntimeError(c, "flightplan.insertWaypoints called on non-flightplan object");
2057   }
2058   
2059   WayptVec wps;
2060   if (!naIsVector(args[0])) {
2061     naRuntimeError(c, "flightplan.insertWaypoints expects vector as first arg");
2062   }
2063
2064   int count = naVec_size(args[0]);
2065   for (int i=0; i<count; ++i) {
2066     Waypt* wp = wayptGhost(naVec_get(args[0], i));
2067     if (wp) {
2068       wps.push_back(wp);
2069     }
2070   }
2071   
2072   int index = -1; // append
2073   if ((argc > 1) && naIsNum(args[1])) {
2074     index = (int) args[1].num;
2075   }
2076
2077   fp->insertWayptsAtIndex(wps, index);
2078   return naNil();
2079 }
2080
2081 static naRef f_flightplan_deleteWP(naContext c, naRef me, int argc, naRef* args)
2082 {
2083   FlightPlan* fp = flightplanGhost(me);
2084   if (!fp) {
2085     naRuntimeError(c, "flightplan.deleteWP called on non-flightplan object");
2086   }
2087   
2088   if ((argc < 1) || !naIsNum(args[0])) {
2089     naRuntimeError(c, "bad argument to flightplan.deleteWP");
2090   }
2091   
2092   int index = (int) args[0].num;
2093   fp->deleteIndex(index);
2094   return naNil();
2095 }
2096
2097 static naRef f_flightplan_clearPlan(naContext c, naRef me, int argc, naRef* args)
2098 {
2099   FlightPlan* fp = flightplanGhost(me);
2100   if (!fp) {
2101     naRuntimeError(c, "flightplan.clearPlan called on non-flightplan object");
2102   }
2103   
2104   fp->clear();
2105   return naNil();
2106 }
2107
2108 static naRef f_flightplan_clearWPType(naContext c, naRef me, int argc, naRef* args)
2109 {
2110   FlightPlan* fp = flightplanGhost(me);
2111   if (!fp) {
2112     naRuntimeError(c, "flightplan.clearWPType called on non-flightplan object");
2113   }
2114   
2115   if (argc < 1) {
2116     naRuntimeError(c, "insufficent args to flightplan.clearWPType");
2117   }
2118   
2119   WayptFlag flag = wayptFlagFromString(naStr_data(args[0]));
2120   fp->clearWayptsWithFlag(flag);
2121   return naNil();
2122 }
2123
2124 static naRef f_flightplan_clone(naContext c, naRef me, int argc, naRef* args)
2125 {
2126   FlightPlan* fp = flightplanGhost(me);
2127   if (!fp) {
2128     naRuntimeError(c, "flightplan.clone called on non-flightplan object");
2129   }
2130   
2131   return ghostForFlightPlan(c, fp->clone());
2132 }
2133
2134 static naRef f_flightplan_pathGeod(naContext c, naRef me, int argc, naRef* args)
2135 {
2136   FlightPlan* fp = flightplanGhost(me);
2137   if (!fp) {
2138     naRuntimeError(c, "flightplan.clone called on non-flightplan object");
2139   }
2140
2141   if ((argc < 1) || !naIsNum(args[0])) {
2142     naRuntimeError(c, "bad argument to flightplan.pathGeod");
2143   }
2144
2145   if ((argc > 1) && !naIsNum(args[1])) {
2146     naRuntimeError(c, "bad argument to flightplan.pathGeod");
2147   }
2148
2149   int index = (int) args[0].num;
2150   double offset = (argc > 1) ? args[1].num : 0.0;
2151   naRef result = naNewHash(c);
2152   SGGeod g = fp->pointAlongRoute(index, offset);
2153   hashset(c, result, "lat", naNum(g.getLatitudeDeg()));
2154   hashset(c, result, "lon", naNum(g.getLongitudeDeg()));
2155   return result;
2156 }
2157
2158
2159 static naRef f_leg_setSpeed(naContext c, naRef me, int argc, naRef* args)
2160 {
2161   FlightPlan::Leg* leg = fpLegGhost(me);
2162   if (!leg) {
2163     naRuntimeError(c, "leg.setSpeed called on non-flightplan-leg object");
2164   }
2165   
2166   if (argc < 2) {
2167     naRuntimeError(c, "bad arguments to leg.setSpeed");
2168   }
2169   
2170   RouteRestriction rr = routeRestrictionFromString(naStr_data(args[1]));
2171   leg->setSpeed(rr, args[0].num);
2172   return naNil();
2173 }
2174
2175 static naRef f_leg_setAltitude(naContext c, naRef me, int argc, naRef* args)
2176 {
2177   FlightPlan::Leg* leg = fpLegGhost(me);
2178   if (!leg) {
2179     naRuntimeError(c, "leg.setAltitude called on non-flightplan-leg object");
2180   }
2181   
2182   if (argc < 2) {
2183     naRuntimeError(c, "bad arguments to leg.setAltitude");
2184   }
2185   
2186   RouteRestriction rr = routeRestrictionFromString(naStr_data(args[1]));
2187   leg->setAltitude(rr, args[0].num);
2188   return naNil();
2189 }
2190
2191 static naRef f_leg_path(naContext c, naRef me, int argc, naRef* args)
2192 {
2193   FlightPlan::Leg* leg = fpLegGhost(me);
2194   if (!leg) {
2195     naRuntimeError(c, "leg.setAltitude called on non-flightplan-leg object");
2196   }
2197   
2198   RoutePath path(leg->owner());
2199   SGGeodVec gv(path.pathForIndex(leg->index()));
2200
2201   naRef result = naNewVector(c);
2202   BOOST_FOREACH(SGGeod p, gv) {
2203     // construct a geo.Coord!
2204     naRef coord = naNewHash(c);
2205     hashset(c, coord, "lat", naNum(p.getLatitudeDeg()));
2206     hashset(c, coord, "lon", naNum(p.getLongitudeDeg()));
2207     naVec_append(result, coord);
2208   }
2209
2210   return result;
2211 }
2212
2213 static naRef f_leg_courseAndDistanceFrom(naContext c, naRef me, int argc, naRef* args)
2214 {
2215     FlightPlan::Leg* leg = fpLegGhost(me);
2216     if (!leg) {
2217         naRuntimeError(c, "leg.courseAndDistanceFrom called on non-flightplan-leg object");
2218     }
2219     
2220     SGGeod pos;
2221     geodFromArgs(args, 0, argc, pos);
2222     
2223     double courseDeg;
2224     double distanceM;
2225     boost::tie(courseDeg, distanceM) = leg->waypoint()->courseAndDistanceFrom(pos);
2226     
2227     naRef result = naNewVector(c);
2228     naVec_append(result, naNum(courseDeg));
2229     naVec_append(result, naNum(distanceM * SG_METER_TO_NM));
2230     return result;
2231 }
2232
2233 static naRef f_waypoint_navaid(naContext c, naRef me, int argc, naRef* args)
2234 {
2235   flightgear::Waypt* w = wayptGhost(me);
2236   if (!w) {
2237     naRuntimeError(c, "waypoint.navaid called on non-waypoint object");
2238   }
2239   
2240   FGPositioned* pos = w->source();
2241   if (!pos) {
2242     return naNil();
2243   }
2244   
2245   switch (pos->type()) {
2246   case FGPositioned::VOR:
2247   case FGPositioned::NDB:
2248   case FGPositioned::ILS:
2249   case FGPositioned::LOC:
2250   case FGPositioned::GS:
2251   case FGPositioned::DME:
2252   case FGPositioned::TACAN: {
2253     FGNavRecord* nav = (FGNavRecord*) pos;
2254     return ghostForNavaid(c, nav);
2255   }
2256       
2257   default:
2258     return naNil();
2259   }
2260 }
2261
2262 static naRef f_waypoint_airport(naContext c, naRef me, int argc, naRef* args)
2263 {
2264   flightgear::Waypt* w = wayptGhost(me);
2265   if (!w) {
2266     naRuntimeError(c, "waypoint.navaid called on non-waypoint object");
2267   }
2268   
2269   FGPositioned* pos = w->source();
2270   if (!pos || FGAirport::isAirportType(pos)) {
2271     return naNil();
2272   }
2273   
2274   return ghostForAirport(c, (FGAirport*) pos);
2275 }
2276
2277 static naRef f_waypoint_runway(naContext c, naRef me, int argc, naRef* args)
2278 {
2279   flightgear::Waypt* w = wayptGhost(me);
2280   if (!w) {
2281     naRuntimeError(c, "waypoint.navaid called on non-waypoint object");
2282   }
2283   
2284   FGPositioned* pos = w->source();
2285   if (!pos || (pos->type() != FGPositioned::RUNWAY)) {
2286     return naNil();
2287   }
2288   
2289   return ghostForRunway(c, (FGRunway*) pos);
2290 }
2291
2292 static naRef f_procedure_transition(naContext c, naRef me, int argc, naRef* args)
2293 {
2294   Procedure* proc = procedureGhost(me);
2295   if (!proc) {
2296     naRuntimeError(c, "procedure.transition called on non-procedure object");
2297   }
2298   
2299   if ((proc->type() != PROCEDURE_SID) && (proc->type() != PROCEDURE_STAR)) {
2300     naRuntimeError(c, "procedure.transition called on non-SID or -STAR");
2301   }
2302   
2303   ArrivalDeparture* ad = (ArrivalDeparture*) proc;
2304   Transition* trans = ad->findTransitionByName(naStr_data(args[0]));
2305   
2306   return ghostForProcedure(c, trans);
2307 }
2308
2309 static naRef f_procedure_route(naContext c, naRef me, int argc, naRef* args)
2310 {
2311   Procedure* proc = procedureGhost(me);
2312   if (!proc) {
2313     naRuntimeError(c, "procedure.route called on non-procedure object");
2314   }
2315   
2316 // wrapping up tow different routines here - approach routing from the IAF
2317 // to the associated runway, and SID/STAR routing via an enroute transition
2318 // and possibly a runway transition or not.
2319   if (Approach::isApproach(proc->type())) {
2320     WayptRef iaf;
2321     if (argc > 0) {
2322       iaf = wayptFromArg(args[0]);
2323     }
2324     
2325     WayptVec r;
2326     Approach* app = (Approach*) proc;
2327     if (!app->route(iaf, r)) {
2328       SG_LOG(SG_NASAL, SG_WARN, "procedure.route failed for Approach somehow");
2329       return naNil();
2330     }
2331     
2332     return convertWayptVecToNasal(c, r);
2333   } else if ((proc->type() != PROCEDURE_SID) && (proc->type() != PROCEDURE_STAR)) {
2334     naRuntimeError(c, "procedure.route called on unsuitable procedure type");
2335   }
2336   
2337   int argOffset = 0;
2338   FGRunway* rwy = runwayGhost(args[0]);
2339   if (rwy) ++argOffset;
2340   
2341   ArrivalDeparture* ad = (ArrivalDeparture*) proc;
2342   Transition* trans = NULL;
2343   if (argOffset < argc) {
2344     trans = (Transition*) procedureGhost(args[argOffset]);
2345   }
2346   
2347   // note either runway or trans may be NULL - that's ok
2348   WayptVec r;
2349   if (!ad->route(rwy, trans, r)) {
2350     SG_LOG(SG_NASAL, SG_WARN, "prcoedure.route failed for ArrvialDeparture somehow");
2351     return naNil();
2352   }
2353   
2354   return convertWayptVecToNasal(c, r);
2355 }
2356
2357
2358 // Table of extension functions.  Terminate with zeros.
2359 static struct { const char* name; naCFunction func; } funcs[] = {
2360   { "carttogeod", f_carttogeod },
2361   { "geodtocart", f_geodtocart },
2362   { "geodinfo", f_geodinfo },
2363   { "airportinfo", f_airportinfo },
2364   { "findAirportsWithinRange", f_findAirportsWithinRange },
2365   { "findAirportsByICAO", f_findAirportsByICAO },
2366   { "navinfo", f_navinfo },
2367   { "findNavaidsWithinRange", f_findNavaidsWithinRange },
2368   { "findNavaidByFrequency", f_findNavaidByFrequency },
2369   { "findNavaidsByFrequency", f_findNavaidsByFrequency },
2370   { "findNavaidsByID", f_findNavaidsByIdent },
2371   { "findFixesByID", f_findFixesByIdent },
2372   { "flightplan", f_route },
2373   { "registerFlightPlanDelegate", f_registerFPDelegate },
2374   { "createWP", f_createWP },
2375   { "createWPFrom", f_createWPFrom },
2376   { "airwaysRoute", f_airwaySearch },
2377   { "magvar", f_magvar },
2378   { "courseAndDistance", f_courseAndDistance },
2379   { "greatCircleMove", f_greatCircleMove },
2380   { "tileIndex", f_tileIndex },
2381   { "tilePath", f_tilePath },
2382   { 0, 0 }
2383 };
2384
2385
2386 naRef initNasalPositioned(naRef globals, naContext c, naRef gcSave)
2387 {
2388     airportPrototype = naNewHash(c);
2389     hashset(c, gcSave, "airportProto", airportPrototype);
2390   
2391     hashset(c, airportPrototype, "runway", naNewFunc(c, naNewCCode(c, f_airport_runway)));
2392     hashset(c, airportPrototype, "taxiway", naNewFunc(c, naNewCCode(c, f_airport_taxiway)));
2393     hashset(c, airportPrototype, "tower", naNewFunc(c, naNewCCode(c, f_airport_tower)));
2394     hashset(c, airportPrototype, "comms", naNewFunc(c, naNewCCode(c, f_airport_comms)));
2395     hashset(c, airportPrototype, "sids", naNewFunc(c, naNewCCode(c, f_airport_sids)));
2396     hashset(c, airportPrototype, "stars", naNewFunc(c, naNewCCode(c, f_airport_stars)));
2397     hashset(c, airportPrototype, "getApproachList", naNewFunc(c, naNewCCode(c, f_airport_approaches)));
2398     hashset(c, airportPrototype, "parking", naNewFunc(c, naNewCCode(c, f_airport_parking)));
2399     hashset(c, airportPrototype, "getSid", naNewFunc(c, naNewCCode(c, f_airport_getSid)));
2400     hashset(c, airportPrototype, "getStar", naNewFunc(c, naNewCCode(c, f_airport_getStar)));
2401     hashset(c, airportPrototype, "getIAP", naNewFunc(c, naNewCCode(c, f_airport_getApproach)));
2402     hashset(c, airportPrototype, "tostring", naNewFunc(c, naNewCCode(c, f_airport_toString)));
2403   
2404     flightplanPrototype = naNewHash(c);
2405     hashset(c, gcSave, "flightplanProto", flightplanPrototype);
2406       
2407     hashset(c, flightplanPrototype, "getWP", naNewFunc(c, naNewCCode(c, f_flightplan_getWP)));
2408     hashset(c, flightplanPrototype, "currentWP", naNewFunc(c, naNewCCode(c, f_flightplan_currentWP))); 
2409     hashset(c, flightplanPrototype, "nextWP", naNewFunc(c, naNewCCode(c, f_flightplan_nextWP))); 
2410     hashset(c, flightplanPrototype, "getPlanSize", naNewFunc(c, naNewCCode(c, f_flightplan_numWaypoints)));
2411     hashset(c, flightplanPrototype, "appendWP", naNewFunc(c, naNewCCode(c, f_flightplan_appendWP))); 
2412     hashset(c, flightplanPrototype, "insertWP", naNewFunc(c, naNewCCode(c, f_flightplan_insertWP))); 
2413     hashset(c, flightplanPrototype, "deleteWP", naNewFunc(c, naNewCCode(c, f_flightplan_deleteWP))); 
2414     hashset(c, flightplanPrototype, "insertWPAfter", naNewFunc(c, naNewCCode(c, f_flightplan_insertWPAfter))); 
2415     hashset(c, flightplanPrototype, "insertWaypoints", naNewFunc(c, naNewCCode(c, f_flightplan_insertWaypoints))); 
2416     hashset(c, flightplanPrototype, "cleanPlan", naNewFunc(c, naNewCCode(c, f_flightplan_clearPlan))); 
2417     hashset(c, flightplanPrototype, "clearWPType", naNewFunc(c, naNewCCode(c, f_flightplan_clearWPType))); 
2418     hashset(c, flightplanPrototype, "clone", naNewFunc(c, naNewCCode(c, f_flightplan_clone))); 
2419     hashset(c, flightplanPrototype, "pathGeod", naNewFunc(c, naNewCCode(c, f_flightplan_pathGeod)));
2420     
2421     waypointPrototype = naNewHash(c);
2422     hashset(c, gcSave, "wayptProto", waypointPrototype);
2423     
2424     hashset(c, waypointPrototype, "navaid", naNewFunc(c, naNewCCode(c, f_waypoint_navaid)));
2425     hashset(c, waypointPrototype, "runway", naNewFunc(c, naNewCCode(c, f_waypoint_runway)));
2426     hashset(c, waypointPrototype, "airport", naNewFunc(c, naNewCCode(c, f_waypoint_airport)));
2427   
2428     procedurePrototype = naNewHash(c);
2429     hashset(c, gcSave, "procedureProto", procedurePrototype);
2430     hashset(c, procedurePrototype, "transition", naNewFunc(c, naNewCCode(c, f_procedure_transition)));
2431     hashset(c, procedurePrototype, "route", naNewFunc(c, naNewCCode(c, f_procedure_route)));
2432   
2433     fpLegPrototype = naNewHash(c);
2434     hashset(c, gcSave, "fpLegProto", fpLegPrototype);
2435     hashset(c, fpLegPrototype, "setSpeed", naNewFunc(c, naNewCCode(c, f_leg_setSpeed)));
2436     hashset(c, fpLegPrototype, "setAltitude", naNewFunc(c, naNewCCode(c, f_leg_setAltitude)));
2437     hashset(c, fpLegPrototype, "path", naNewFunc(c, naNewCCode(c, f_leg_path)));
2438     hashset(c, fpLegPrototype, "courseAndDistanceFrom", naNewFunc(c, naNewCCode(c, f_leg_courseAndDistanceFrom)));
2439   
2440     for(int i=0; funcs[i].name; i++) {
2441       hashset(c, globals, funcs[i].name,
2442       naNewFunc(c, naNewCCode(c, funcs[i].func)));
2443     }
2444   
2445   return naNil();
2446 }
2447
2448 void postinitNasalPositioned(naRef globals, naContext c)
2449 {
2450   naRef geoModule = naHash_cget(globals, (char*) "geo");
2451   if (naIsNil(geoModule)) {
2452     SG_LOG(SG_GENERAL, SG_WARN, "postinitNasalPositioned: geo.nas not loaded");
2453     return;
2454   }
2455   
2456   geoCoordClass = naHash_cget(geoModule, (char*) "Coord");
2457 }
2458
2459