+// Parse XML file.
+// parsexml(<path> [, <start-tag> [, <end-tag> [, <data> [, <pi>]]]]);
+//
+// <path> ... absolute path to an XML file
+// <start-tag> ... callback function with two args: tag name, attribute hash
+// <end-tag> ... callback function with one arg: tag name
+// <data> ... callback function with one arg: data
+// <pi> ... callback function with two args: target, data
+// (pi = "processing instruction")
+// All four callback functions are optional and default to nil.
+// The function returns nil on error, or the validated file name otherwise.
+static naRef f_parsexml(naContext c, naRef me, int argc, naRef* args)
+{
+ if(argc < 1 || !naIsString(args[0]))
+ naRuntimeError(c, "parsexml(): path argument missing or not a string");
+ if(argc > 5) argc = 5;
+ for(int i=1; i<argc; i++)
+ if(!(naIsNil(args[i]) || naIsFunc(args[i])))
+ naRuntimeError(c, "parsexml(): callback argument not a function");
+
+ const char* file = fgValidatePath(naStr_data(args[0]), false);
+ if(!file) {
+ naRuntimeError(c, "parsexml(): reading '%s' denied "
+ "(unauthorized access)", naStr_data(args[0]));
+ return naNil();
+ }
+ std::ifstream input(file);
+ NasalXMLVisitor visitor(c, argc, args);
+ try {
+ readXML(input, visitor);
+ } catch (const sg_exception& e) {
+ naRuntimeError(c, "parsexml(): file '%s' %s",
+ file, e.getFormattedMessage().c_str());
+ return naNil();
+ }
+ return naStr_fromdata(naNewString(c), const_cast<char*>(file), strlen(file));
+}
+
+// Return UNIX epoch time in seconds.
+static naRef f_systime(naContext c, naRef me, int argc, naRef* args)
+{
+#ifdef WIN32
+ FILETIME ft;
+ GetSystemTimeAsFileTime(&ft);
+ double t = (4294967296.0 * ft.dwHighDateTime + ft.dwLowDateTime);
+ // Converts from 100ns units in 1601 epoch to unix epoch in sec
+ return naNum((t * 1e-7) - 11644473600.0);
+#else
+ time_t t;
+ struct timeval td;
+ do { t = time(0); gettimeofday(&td, 0); } while(t != time(0));
+ return naNum(t + 1e-6 * td.tv_usec);
+#endif
+}
+
+// Convert a cartesian point to a geodetic lat/lon/altitude.
+static naRef f_carttogeod(naContext c, naRef me, int argc, naRef* args)
+{
+ double lat, lon, alt, xyz[3];
+ if(argc != 3) naRuntimeError(c, "carttogeod() expects 3 arguments");
+ for(int i=0; i<3; i++)
+ xyz[i] = naNumValue(args[i]).num;
+ sgCartToGeod(xyz, &lat, &lon, &alt);
+ lat *= SG_RADIANS_TO_DEGREES;
+ lon *= SG_RADIANS_TO_DEGREES;
+ naRef vec = naNewVector(c);
+ naVec_append(vec, naNum(lat));
+ naVec_append(vec, naNum(lon));
+ naVec_append(vec, naNum(alt));
+ return vec;
+}
+
+// Convert a geodetic lat/lon/altitude to a cartesian point.
+static naRef f_geodtocart(naContext c, naRef me, int argc, naRef* args)
+{
+ if(argc != 3) naRuntimeError(c, "geodtocart() expects 3 arguments");
+ double lat = naNumValue(args[0]).num * SG_DEGREES_TO_RADIANS;
+ double lon = naNumValue(args[1]).num * SG_DEGREES_TO_RADIANS;
+ double alt = naNumValue(args[2]).num;
+ double xyz[3];
+ sgGeodToCart(lat, lon, alt, xyz);
+ naRef vec = naNewVector(c);
+ naVec_append(vec, naNum(xyz[0]));
+ naVec_append(vec, naNum(xyz[1]));
+ naVec_append(vec, naNum(xyz[2]));
+ return vec;
+}
+
+// For given geodetic point return array with elevation, and a material data
+// hash, or nil if there's no information available (tile not loaded). If
+// information about the material isn't available, then nil is returned instead
+// of the hash.
+static naRef f_geodinfo(naContext c, naRef me, int argc, naRef* args)
+{
+#define HASHSET(s,l,n) naHash_set(matdata, naStr_fromdata(naNewString(c),s,l),n)
+ if(argc < 2 || argc > 3)
+ naRuntimeError(c, "geodinfo() expects 2 or 3 arguments: lat, lon [, maxalt]");
+ double lat = naNumValue(args[0]).num;
+ double lon = naNumValue(args[1]).num;
+ double elev = argc == 3 ? naNumValue(args[2]).num : 10000;
+ const SGMaterial *mat;
+ SGGeod geod = SGGeod::fromDegM(lon, lat, elev);
+ if(!globals->get_scenery()->get_elevation_m(geod, elev, &mat))
+ return naNil();
+ naRef vec = naNewVector(c);
+ naVec_append(vec, naNum(elev));
+ naRef matdata = naNil();
+ if(mat) {
+ matdata = naNewHash(c);
+ naRef names = naNewVector(c);
+ const vector<string> n = mat->get_names();
+ for(unsigned int i=0; i<n.size(); i++)
+ naVec_append(names, naStr_fromdata(naNewString(c),
+ const_cast<char*>(n[i].c_str()), n[i].size()));
+ HASHSET("names", 5, names);
+ HASHSET("solid", 5, naNum(mat->get_solid()));
+ HASHSET("friction_factor", 15, naNum(mat->get_friction_factor()));
+ HASHSET("rolling_friction", 16, naNum(mat->get_rolling_friction()));
+ HASHSET("load_resistance", 15, naNum(mat->get_load_resistance()));
+ HASHSET("bumpiness", 9, naNum(mat->get_bumpiness()));
+ HASHSET("light_coverage", 14, naNum(mat->get_light_coverage()));
+ }
+ naVec_append(vec, matdata);
+ return vec;
+#undef HASHSET
+}
+
+
+class AirportInfoFilter : public FGAirport::AirportFilter
+{
+public:
+ AirportInfoFilter() : type(FGPositioned::AIRPORT) {
+ }
+
+ virtual FGPositioned::Type minType() const {
+ return type;
+ }
+
+ virtual FGPositioned::Type maxType() const {
+ return type;
+ }
+
+ FGPositioned::Type type;
+};
+
+// Returns data hash for particular or nearest airport of a <type>, or nil
+// on error.
+//
+// airportinfo(<id>); e.g. "KSFO"
+// airportinfo(<type>); type := ("airport"|"seaport"|"heliport")
+// airportinfo() same as airportinfo("airport")
+// airportinfo(<lat>, <lon> [, <type>]);
+static naRef f_airportinfo(naContext c, naRef me, int argc, naRef* args)
+{
+ static SGConstPropertyNode_ptr latn = fgGetNode("/position/latitude-deg", true);
+ static SGConstPropertyNode_ptr lonn = fgGetNode("/position/longitude-deg", true);
+ SGGeod pos;
+ FGAirport* apt = NULL;
+
+ if(argc >= 2 && naIsNum(args[0]) && naIsNum(args[1])) {
+ pos = SGGeod::fromDeg(args[1].num, args[0].num);
+ args += 2;
+ argc -= 2;
+ } else {
+ pos = SGGeod::fromDeg(lonn->getDoubleValue(), latn->getDoubleValue());
+ }
+
+ double maxRange = 10000.0; // expose this? or pick a smaller value?
+
+ AirportInfoFilter filter; // defaults to airports only
+
+ if(argc == 0) {
+ // fall through and use AIRPORT
+ } else if(argc == 1 && naIsString(args[0])) {
+ const char *s = naStr_data(args[0]);
+ if(!strcmp(s, "airport")) filter.type = FGPositioned::AIRPORT;
+ else if(!strcmp(s, "seaport")) filter.type = FGPositioned::SEAPORT;
+ else if(!strcmp(s, "heliport")) filter.type = FGPositioned::HELIPORT;
+ else {
+ // user provided an <id>, hopefully
+ apt = FGAirport::findByIdent(s);
+ if (!apt) {
+ // return nil here, but don't raise a runtime error; this is a
+ // legitamate way to validate an ICAO code, for example in a
+ // dialog box or similar.
+ return naNil();
+ }
+ }
+ } else {
+ naRuntimeError(c, "airportinfo() with invalid function arguments");
+ return naNil();
+ }
+
+ if(!apt) {
+ apt = FGAirport::findClosest(pos, maxRange, &filter);
+ if(!apt) return naNil();
+ }
+
+ string id = apt->ident();
+ string name = apt->name();
+
+ // set runway hash
+ naRef rwys = naNewHash(c);
+ for(unsigned int r=0; r<apt->numRunways(); ++r) {
+ FGRunway* rwy(apt->getRunwayByIndex(r));
+
+ naRef rwyid = naStr_fromdata(naNewString(c),
+ const_cast<char *>(rwy->ident().c_str()),
+ rwy->ident().length());
+
+ naRef rwydata = naNewHash(c);
+#define HASHSET(s,l,n) naHash_set(rwydata, naStr_fromdata(naNewString(c),s,l),n)
+ HASHSET("id", 2, rwyid);
+ HASHSET("lat", 3, naNum(rwy->latitude()));
+ HASHSET("lon", 3, naNum(rwy->longitude()));
+ HASHSET("heading", 7, naNum(rwy->headingDeg()));
+ HASHSET("length", 6, naNum(rwy->lengthM()));
+ HASHSET("width", 5, naNum(rwy->widthM()));
+ HASHSET("threshold", 9, naNum(rwy->displacedThresholdM()));
+ HASHSET("stopway", 7, naNum(rwy->stopwayM()));
+#undef HASHSET
+ naHash_set(rwys, rwyid, rwydata);
+ }
+
+ // set airport hash
+ naRef aptdata = naNewHash(c);
+#define HASHSET(s,l,n) naHash_set(aptdata, naStr_fromdata(naNewString(c),s,l),n)
+ HASHSET("id", 2, naStr_fromdata(naNewString(c),
+ const_cast<char *>(id.c_str()), id.length()));
+ HASHSET("name", 4, naStr_fromdata(naNewString(c),
+ const_cast<char *>(name.c_str()), name.length()));
+ HASHSET("lat", 3, naNum(apt->getLatitude()));
+ HASHSET("lon", 3, naNum(apt->getLongitude()));
+ HASHSET("elevation", 9, naNum(apt->getElevation() * SG_FEET_TO_METER));
+ HASHSET("has_metar", 9, naNum(apt->getMetar()));
+ HASHSET("runways", 7, rwys);
+#undef HASHSET
+ return aptdata;
+}
+
+