FGRadar

IMPORTANT: Some, and possibly most, of the features/ideas discussed here are likely to be affected, and possibly even deprecated, by the ongoing work on providing a property tree-based 2D drawing API accessible from Nasal using the new Canvas system available since FlightGear 2.80 (08/2012). Please see: Canvas Radar for further information

You are advised not to start working on anything directly related to this without first discussing/coordinating your ideas with other FlightGear contributors using the FlightGear developers mailing list or the Canvas subforum This is a link to the FlightGear forum.. Anything related to Canvas Core Development should be discussed first of all with TheTom and Zakalawe. Nasal-space frameworks are being maintained by Philosopher and Hooray currently. talk page.

Canvasready.png
FGRadar
FGRadar icon.png
Developed by Fernando García (Icecode GL)
Written in C++
OS Cross platform
Development status Put on hold (as of 01/2013)
Development progress 20}% completed
License GNU General Public License v3
[https://github.com/hamzaalloush/fgradar-clone Website]



Building

Regardless of the status of fgradar, the underlying framework related code may still be useful for related projects, so that it may make sense to maintain a working copy of this code [1]. As of 11/2016, the original code no longer builds against SimGear 2016.4.0, i.e. the following changes are required for the code to build again (which also updates the property module):

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 92342a1..eac8ff3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -30,7 +30,7 @@ string(TOLOWER ${PROJECT_NAME} PROJECT_NAME_LOWER)
 include(cpack_config)
 
 # Check for SimGear (REQUIRED).
-find_package(SimGear 2.9.0 REQUIRED)
+find_package(SimGear 2016.4.0 REQUIRED)
 if (SIMGEAR_FOUND)
   include_directories(${SIMGEAR_INCLUDE_DIR})
   set(LIBS ${LIBS} ${SIMGEAR_CORE_LIBRARIES}
diff --git a/src/scripting/nasal-props.cxx b/src/scripting/nasal-props.cxx
index d38401d..4075c22 100644
--- a/src/scripting/nasal-props.cxx
+++ b/src/scripting/nasal-props.cxx
@@ -1,4 +1,3 @@
-
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
 #endif
@@ -25,17 +24,17 @@ using namespace std;
 
 static void propNodeGhostDestroy(void* ghost)
 {
-    SGPropertyNode_ptr* prop = (SGPropertyNode_ptr*)ghost;
-    delete prop;
+  SGPropertyNode* prop = static_cast<SGPropertyNode*>(ghost);
+  if (!SGPropertyNode::put(prop)) delete prop;
 }
 
 naGhostType PropNodeGhostType = { propNodeGhostDestroy, "prop" };
 
-static naRef propNodeGhostCreate(naContext c, SGPropertyNode* n)
+naRef propNodeGhostCreate(naContext c, SGPropertyNode* ghost)
 {
-    if(!n) return naNil();
-    SGPropertyNode_ptr* ghost = new SGPropertyNode_ptr(n);
-    return naNewGhost(c, &PropNodeGhostType, ghost);
+  if(!ghost) return naNil();
+  SGPropertyNode::get(ghost);
+  return naNewGhost(c, &PropNodeGhostType, ghost);
 }
 
 naRef FGNasalSys::propNodeGhost(SGPropertyNode* handle)
@@ -47,36 +46,67 @@ SGPropertyNode* ghostToPropNode(naRef ref)
 {
   if (!naIsGhost(ref) || (naGhost_type(ref) != &PropNodeGhostType))
     return NULL;
-  
-  SGPropertyNode_ptr* pp = (SGPropertyNode_ptr*) naGhost_ptr(ref);
-  return pp->ptr();
+
+  return static_cast<SGPropertyNode*>(naGhost_ptr(ref));
 }
 
 #define NASTR(s) s ? naStr_fromdata(naNewString(c),(char*)(s),strlen(s)) : naNil()
 
 //
 // Standard header for the extension functions.  It turns the "ghost"
-// found in arg[0] into a SGPropertyNode_ptr*, and then "unwraps" the
+// found in arg[0] into a SGPropertyNode_ptr, and then "unwraps" the
 // vector found in the second argument into a normal-looking args
 // array.  This allows the Nasal handlers to do things like:
 //   Node.getChild = func { _getChild(me.ghost, arg) }
 //
-#define NODENOARG()                                                       \
-    if(argc < 2 || !naIsGhost(args[0]) ||                               \
-       naGhost_type(args[0]) != &PropNodeGhostType)                       \
-        naRuntimeError(c, "bad argument to props function");            \
-    SGPropertyNode_ptr* node = (SGPropertyNode_ptr*)naGhost_ptr(args[0]);
-
-#define NODEARG()                                                       \
-    NODENOARG(); 							\
+#define NODENOARG()                                                            \
+    if(argc < 2 || !naIsGhost(args[0]) ||                                      \
+        naGhost_type(args[0]) != &PropNodeGhostType)                           \
+        naRuntimeError(c, "bad argument to props function");                   \
+    SGPropertyNode_ptr node = static_cast<SGPropertyNode*>(naGhost_ptr(args[0]));
+
+#define NODEARG()                                                              \
+    NODENOARG();                                                               \
     naRef argv = args[1]
 
+//
+// Pops the first argument as a relative path if the first condition
+// is true (e.g. argc > 1 for getAttribute) and if it is a string.
+// If the second confition is true, then another is popped to specify
+// if the node should be created (i.e. like the second argument to
+// getNode())
+//
+// Note that this makes the function return nil if the node doesn't
+// exist, so all functions with a relative_path parameter will
+// return nil if the specified node does not exist.
+//
+#define MOVETARGET(cond1, create)                                              \
+    if(cond1) {                                                                \
+        naRef name = naVec_get(argv, 0);                                       \
+        if(naIsString(name)) {                                                 \
+            try {                                                              \
+                node = node->getNode(naStr_data(name), create);                \
+            } catch(const string& err) {                                       \
+                naRuntimeError(c, (char *)err.c_str());                        \
+                return naNil();                                                \
+            }                                                                  \
+            if(!node) return naNil();                                          \
+            naVec_removefirst(argv); /* pop only if we were successful */      \
+        }                                                                      \
+    }
+
+
+// Get the type of a property (returns a string).
+// Forms:
+//    props.Node.getType(string relative_path);
+//    props.Node.getType();
 static naRef f_getType(naContext c, naRef me, int argc, naRef* args)
 {
     using namespace simgear;
-    NODENOARG();
+    NODEARG();
+    MOVETARGET(naVec_size(argv) > 0, false);
     const char* t = "unknown";
-    switch((*node)->getType()) {
+    switch(node->getType()) {
     case props::NONE:   t = "NONE";   break;
     case props::ALIAS:  t = "ALIAS";  break;
     case props::BOOL:   t = "BOOL";   break;
@@ -93,20 +123,28 @@ static naRef f_getType(naContext c, naRef me, int argc, naRef* args)
     return NASTR(t);
 }
 
+
+// Get an attribute of a property by name (returns true/false).
+// Forms:
+//    props.Node.getType(string relative_path,
+//                       string attribute_name);
+//    props.Node.getType(string attribute_name);
 static naRef f_getAttribute(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
-    if(naVec_size(argv) == 0) return naNum(unsigned((*node)->getAttributes()));
+    if(naVec_size(argv) == 0) return naNum(unsigned(node->getAttributes()));
+    MOVETARGET(naVec_size(argv) > 1, false);
     naRef val = naVec_get(argv, 0);
     const char *a = naStr_data(val);
     SGPropertyNode::Attribute attr;
     if(!a) a = "";
     if(!strcmp(a, "last")) return naNum(SGPropertyNode::LAST_USED_ATTRIBUTE);
-    else if(!strcmp(a, "children"))    return naNum((*node)->nChildren());
-    else if(!strcmp(a, "listeners"))   return naNum((*node)->nListeners());
-    else if(!strcmp(a, "references"))  return naNum(node->getNumRefs());
-    else if(!strcmp(a, "tied"))        return naNum((*node)->isTied());
-    else if(!strcmp(a, "alias"))       return naNum((*node)->isAlias());
+    else if(!strcmp(a, "children"))    return naNum(node->nChildren());
+    else if(!strcmp(a, "listeners"))   return naNum(node->nListeners());
+    // Number of references without instance used in this function
+    else if(!strcmp(a, "references"))  return naNum(node.getNumRefs() - 1);
+    else if(!strcmp(a, "tied"))        return naNum(node->isTied());
+    else if(!strcmp(a, "alias"))       return naNum(node->isAlias());
     else if(!strcmp(a, "readable"))    attr = SGPropertyNode::READ;
     else if(!strcmp(a, "writable"))    attr = SGPropertyNode::WRITE;
     else if(!strcmp(a, "archive"))     attr = SGPropertyNode::ARCHIVE;
@@ -118,16 +156,26 @@ static naRef f_getAttribute(naContext c, naRef me, int argc, naRef* args)
         naRuntimeError(c, "props.getAttribute() with invalid attribute");
         return naNil();
     }
-    return naNum((*node)->getAttribute(attr));
+    return naNum(node->getAttribute(attr));
 }
 
+
+// Set an attribute by name and boolean value or raw (bitmasked) number.
+// Forms:
+//    props.Node.setAttribute(string relative_path,
+//                            string attribute_name,
+//                            bool value);
+//    props.Node.setAttribute(string attribute_name,
+//                            bool value);
+//    props.Node.setArtribute(int attributes);
 static naRef f_setAttribute(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
+    MOVETARGET(naVec_size(argv) > 2, false);
     naRef val = naVec_get(argv, 0);
     if(naVec_size(argv) == 1 && naIsNum(val)) {
-        naRef ret = naNum((*node)->getAttributes());
-        (*node)->setAttributes((int)val.num);
+        naRef ret = naNum(node->getAttributes());
+        node->setAttributes((int)val.num);
         return ret;
     }
     SGPropertyNode::Attribute attr;
@@ -144,21 +192,44 @@ static naRef f_setAttribute(naContext c, naRef me, int argc, naRef* args)
         naRuntimeError(c, "props.setAttribute() with invalid attribute");
         return naNil();
     }
-    naRef ret = naNum((*node)->getAttribute(attr));
-    (*node)->setAttribute(attr, naTrue(naVec_get(argv, 1)) ? true : false);
+    naRef ret = naNum(node->getAttribute(attr));
+    node->setAttribute(attr, naTrue(naVec_get(argv, 1)) ? true : false);
     return ret;
 }
 
+
+// Get the simple name of this node.
+// Forms:
+//    props.Node.getName();
 static naRef f_getName(naContext c, naRef me, int argc, naRef* args)
 {
     NODENOARG();
-    return NASTR((*node)->getName());
+    return NASTR(node->getName());
 }
 
+
+// Get the index of this node.
+// Forms:
+//    props.Node.getIndex();
 static naRef f_getIndex(naContext c, naRef me, int argc, naRef* args)
 {
     NODENOARG();
-    return naNum((*node)->getIndex());
+    return naNum(node->getIndex());
+}
+
+// Check if other_node refers to the same as this node.
+// Forms:
+//    props.Node.equals(other_node);
+static naRef f_equals(naContext c, naRef me, int argc, naRef* args)
+{
+    NODEARG();
+
+    naRef rhs = naVec_get(argv, 0);
+    if( !naIsGhost(rhs) || naGhost_type(rhs) != &PropNodeGhostType )
+      return naNum(false);
+
+    SGPropertyNode* node_rhs = static_cast<SGPropertyNode*>(naGhost_ptr(rhs));
+    return naNum(node.ptr() == node_rhs);
 }
 
 template<typename T>
@@ -173,18 +244,24 @@ naRef makeVectorFromVec(naContext c, const T& vec)
     return vector;
 }
 
+
+// Get the value of a node, with or without a relative path.
+// Forms:
+//    props.Node.getValue(string relative_path);
+//    props.Node.getValue();
 static naRef f_getValue(naContext c, naRef me, int argc, naRef* args)
 {
     using namespace simgear;
-    NODENOARG();
-    switch((*node)->getType()) {
+    NODEARG();
+    MOVETARGET(naVec_size(argv) > 0, false);
+    switch(node->getType()) {
     case props::BOOL:   case props::INT:
     case props::LONG:   case props::FLOAT:
     case props::DOUBLE:
     {
-        double dv = (*node)->getDoubleValue();
-        if (osg::isNaN(dv)) {
-          SG_LOG(SG_NASAL, SG_ALERT, "Nasal getValue: property " << (*node)->getPath() << " is NaN");
+        double dv = node->getDoubleValue();
+        if (SGMisc<double>::isNaN(dv)) {
+          SG_LOG(SG_NASAL, SG_ALERT, "Nasal getValue: property " << node->getPath() << " is NaN");
           return naNil();
         }
 
@@ -193,11 +270,11 @@ static naRef f_getValue(naContext c, naRef me, int argc, naRef* args)
 
     case props::STRING:
     case props::UNSPECIFIED:
-        return NASTR((*node)->getStringValue());
+        return NASTR(node->getStringValue());
     case props::VEC3D:
-        return makeVectorFromVec(c, (*node)->getValue<SGVec3d>());
+        return makeVectorFromVec(c, node->getValue<SGVec3d>());
     case props::VEC4D:
-        return makeVectorFromVec(c, (*node)->getValue<SGVec4d>());
+        return makeVectorFromVec(c, node->getValue<SGVec4d>());
     default:
         return naNil();
     }
@@ -220,31 +297,39 @@ T makeVecFromVector(naRef vector)
     return vec;
 }
 
+
+// Set the value of a node; returns true if it succeeded or
+// false if it failed. <val> can be a string, number, or a
+// vector or numbers (for SGVec3D/4D types).
+// Forms:
+//    props.Node.setValue(string relative_path,
+//                        val);
+//    props.Node.setValue(val);
 static naRef f_setValue(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
+    MOVETARGET(naVec_size(argv) > 1, true);
     naRef val = naVec_get(argv, 0);
     bool result = false;
     if(naIsString(val)) {
-        result = (*node)->setStringValue(naStr_data(val));
+         result = node->setStringValue(naStr_data(val));
     } else if(naIsVector(val)) {
         if(naVec_size(val) == 3)
-            result = (*node)->setValue(makeVecFromVector<SGVec3d>(val));
+            result = node->setValue(makeVecFromVector<SGVec3d>(val));
         else if(naVec_size(val) == 4)
-            result = (*node)->setValue(makeVecFromVector<SGVec4d>(val));
+            result = node->setValue(makeVecFromVector<SGVec4d>(val));
         else
             naRuntimeError(c, "props.setValue() vector value has wrong size");
     } else {
-        naRef n = naNumValue(val);
-        if(naIsNil(n))
+        if(!naIsNum(val))
             naRuntimeError(c, "props.setValue() with non-number");
 
         double d = naNumValue(val).num;
-        if (osg::isNaN(d)) {
+        if (SGMisc<double>::isNaN(d)) {
           naRuntimeError(c, "props.setValue() passed a NaN");
         }
 
-        result = (*node)->setDoubleValue(d);
+        result = node->setDoubleValue(d);
     }
     return naNum(result);
 }
@@ -252,6 +337,7 @@ static naRef f_setValue(naContext c, naRef me, int argc, naRef* args)
 static naRef f_setIntValue(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
+    MOVETARGET(naVec_size(argv) > 1, true);
     // Original code:
     //   int iv = (int)naNumValue(naVec_get(argv, 0)).num;
 
@@ -263,38 +349,52 @@ static naRef f_setIntValue(naContext c, naRef me, int argc, naRef* args)
     double tmp2 = tmp1.num;
     int iv = (int)tmp2;
 
-    return naNum((*node)->setIntValue(iv));
+    return naNum(node->setIntValue(iv));
 }
 
 static naRef f_setBoolValue(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
+    MOVETARGET(naVec_size(argv) > 1, true);
     naRef val = naVec_get(argv, 0);
-    return naNum((*node)->setBoolValue(naTrue(val) ? true : false));
+    return naNum(node->setBoolValue(naTrue(val) ? true : false));
 }
 
 static naRef f_setDoubleValue(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
+    MOVETARGET(naVec_size(argv) > 1, true);
     naRef r = naNumValue(naVec_get(argv, 0));
     if (naIsNil(r))
         naRuntimeError(c, "props.setDoubleValue() with non-number");
 
-    if (osg::isNaN(r.num)) {
+    if (SGMisc<double>::isNaN(r.num)) {
       naRuntimeError(c, "props.setDoubleValue() passed a NaN");
     }
 
-    return naNum((*node)->setDoubleValue(r.num));
+    return naNum(node->setDoubleValue(r.num));
 }
 
+
+// Get the parent of this node as a ghost.
+// Forms:
+//    props.Node.getParent();
 static naRef f_getParent(naContext c, naRef me, int argc, naRef* args)
 {
     NODENOARG();
-    SGPropertyNode* n = (*node)->getParent();
+    SGPropertyNode* n = node->getParent();
     if(!n) return naNil();
     return propNodeGhostCreate(c, n);
 }
 
+
+// Get a child by name and optional index=0, creating if specified (by default it
+// does not create it). If the node does not exist and create is false, then it
+// returns nil, else it returns a (possibly new) property ghost.
+// Forms:
+//    props.Node.getChild(string relative_path,
+//                        int index=0,
+//                        bool create=false);
 static naRef f_getChild(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
@@ -304,10 +404,10 @@ static naRef f_getChild(naContext c, naRef me, int argc, naRef* args)
     bool create = naTrue(naVec_get(argv, 2)) != 0;
     SGPropertyNode* n;
     try {
-        if(naIsNil(idx) || !naIsNum(idx)) {
-            n = (*node)->getChild(naStr_data(child), create);
+        if(naIsNil(idx)) {
+            n = node->getChild(naStr_data(child), create);
         } else {
-            n = (*node)->getChild(naStr_data(child), (int)idx.num, create);
+            n = node->getChild(naStr_data(child), (int)idx.num, create);
         }
     } catch (const string& err) {
         naRuntimeError(c, (char *)err.c_str());
@@ -317,21 +417,26 @@ static naRef f_getChild(naContext c, naRef me, int argc, naRef* args)
     return propNodeGhostCreate(c, n);
 }
 
+
+// Get all children with a specified name as a vector of ghosts.
+// Forms:
+//    props.Node.getChildren(string relative_path);
+//    props.Node.getChildren(); #get all children
 static naRef f_getChildren(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
     naRef result = naNewVector(c);
     if(naIsNil(argv) || naVec_size(argv) == 0) {
         // Get all children
-        for(int i=0; i<(*node)->nChildren(); i++)
-            naVec_append(result, propNodeGhostCreate(c, (*node)->getChild(i)));
+        for(int i=0; i<node->nChildren(); i++)
+            naVec_append(result, propNodeGhostCreate(c, node->getChild(i)));
     } else {
         // Get all children of a specified name
         naRef name = naVec_get(argv, 0);
         if(!naIsString(name)) return naNil();
         try {
             vector<SGPropertyNode_ptr> children
-                = (*node)->getChildren(naStr_data(name));
+                = node->getChildren(naStr_data(name));
             for(unsigned int i=0; i<children.size(); i++)
                 naVec_append(result, propNodeGhostCreate(c, children[i]));
         } catch (const string& err) {
@@ -342,6 +447,12 @@ static naRef f_getChildren(naContext c, naRef me, int argc, naRef* args)
     return result;
 }
 
+
+// Append a named child at the first unused index...
+// Forms:
+//    props.Node.addChild(string name,
+//                        int min_index=0,
+//                        bool append=true);
 static naRef f_addChild(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
@@ -353,14 +464,14 @@ static naRef f_addChild(naContext c, naRef me, int argc, naRef* args)
     try
     {
       int min_index = 0;
-      if( !naIsNil(ref_min_index) && naIsNum(ref_min_index) )
+      if(!naIsNil(ref_min_index))
         min_index = ref_min_index.num;
 
       bool append = true;
-      if( !naIsNil(ref_append) )
+      if(!naIsNil(ref_append))
         append = naTrue(ref_append) != 0;
 
-      n = (*node)->addChild(naStr_data(child), min_index, append);
+      n = node->addChild(naStr_data(child), min_index, append);
     }
     catch (const string& err)
     {
@@ -387,15 +498,15 @@ static naRef f_addChildren(naContext c, naRef me, int argc, naRef* args)
       count = ref_count.num;
 
       int min_index = 0;
-      if( !naIsNil(ref_min_index) && naIsNum(ref_min_index) )
+      if(!naIsNil(ref_min_index))
         min_index = ref_min_index.num;
 
       bool append = true;
-      if( !naIsNil(ref_append) )
+      if(!naIsNil(ref_append))
         append = naTrue(ref_append) != 0;
 
       const simgear::PropertyList& nodes =
-        (*node)->addChildren(naStr_data(child), count, min_index, append);
+        node->addChildren(naStr_data(child), count, min_index, append);
 
       naRef result = naNewVector(c);
       for( size_t i = 0; i < nodes.size(); ++i )
@@ -410,36 +521,47 @@ static naRef f_addChildren(naContext c, naRef me, int argc, naRef* args)
     return naNil();
 }
 
+
+// Remove a child by name and index. Returns it as a ghost.
+// Forms:
+//    props.Node.removeChild(string relative_path,
+//                           int index);
 static naRef f_removeChild(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
     naRef child = naVec_get(argv, 0);
     naRef index = naVec_get(argv, 1);
     if(!naIsString(child) || !naIsNum(index)) return naNil();
-    SGPropertyNode_ptr n = 0;
+    SGPropertyNode_ptr n;
     try {
-        n = (*node)->removeChild(naStr_data(child), (int)index.num, false);
+        n = node->removeChild(naStr_data(child), (int)index.num);
     } catch (const string& err) {
         naRuntimeError(c, (char *)err.c_str());
     }
     return propNodeGhostCreate(c, n);
 }
 
+
+// Remove all children with specified name. Returns a vector of all the nodes
+// removed as ghosts.
+// Forms:
+//    props.Node.removeChildren(string relative_path);
+//    props.Node.removeChildren(); #remove all children
 static naRef f_removeChildren(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
     naRef result = naNewVector(c);
     if(naIsNil(argv) || naVec_size(argv) == 0) {
         // Remove all children
-        for(int i = (*node)->nChildren() - 1; i >=0; i--)
-            naVec_append(result, propNodeGhostCreate(c, (*node)->removeChild(i)));
+        for(int i = node->nChildren() - 1; i >=0; i--)
+            naVec_append(result, propNodeGhostCreate(c, node->removeChild(i)));
     } else {
         // Remove all children of a specified name
         naRef name = naVec_get(argv, 0);
         if(!naIsString(name)) return naNil();
         try {
             vector<SGPropertyNode_ptr> children
-                = (*node)->removeChildren(naStr_data(name), false);
+                = node->removeChildren(naStr_data(name));
             for(unsigned int i=0; i<children.size(); i++)
                 naVec_append(result, propNodeGhostCreate(c, children[i]));
         } catch (const string& err) {
@@ -450,36 +572,67 @@ static naRef f_removeChildren(naContext c, naRef me, int argc, naRef* args)
     return result;
 }
 
+// Remove all children of a property node.
+// Forms:
+//    props.Node.removeAllChildren();
+static naRef f_removeAllChildren(naContext c, naRef me, int argc, naRef* args)
+{
+  NODENOARG();
+  node->removeAllChildren();
+  return propNodeGhostCreate(c, node);
+}
+
+// Alias this property to another one; returns 1 on success or 0 on failure
+// (only applicable to tied properties).
+// Forms:
+//    props.Node.alias(string global_path);
+//    props.Node.alias(prop_ghost node);
+//    props.Node.alias(props.Node node); #added by props.nas
 static naRef f_alias(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
     SGPropertyNode* al;
     naRef prop = naVec_get(argv, 0);
     try {
-	#if 0 // FIXME
+	#if 0
         if(naIsString(prop)) al = globals->get_props()->getNode(naStr_data(prop), true);
-        else if(naIsGhost(prop)) al = *(SGPropertyNode_ptr*)naGhost_ptr(prop);
+        else if(naIsGhost(prop)) al = static_cast<SGPropertyNode*>(naGhost_ptr(prop));
         else throw string("props.alias() with bad argument");
 	#endif
     } catch (const string& err) {
         naRuntimeError(c, (char *)err.c_str());
         return naNil();
     }
-    return naNum((*node)->alias(al));
+    return naNum(node->alias(al));
 }
 
+
+// Un-alias this property. Returns 1 on success or 0 on failure (only
+// applicable to tied properties).
+// Forms:
+//    props.Node.unalias();
 static naRef f_unalias(naContext c, naRef me, int argc, naRef* args)
 {
     NODENOARG();
-    return naNum((*node)->unalias());
+    return naNum(node->unalias());
 }
 
+
+// Get the alias of this node as a ghost.
+// Forms:
+//    props.Node.getAliasTarget();
 static naRef f_getAliasTarget(naContext c, naRef me, int argc, naRef* args)
 {
     NODENOARG();
-    return propNodeGhostCreate(c, (*node)->getAliasTarget());
+    return propNodeGhostCreate(c, node->getAliasTarget());
 }
 
+
+// Get a relative node. Returns nil if it does not exist and create is false,
+// or a ghost object otherwise (wrapped into a props.Node object by props.nas).
+// Forms:
+//    props.Node.getNode(string relative_path,
+//                       bool create=false);
 static naRef f_getNode(naContext c, naRef me, int argc, naRef* args)
 {
     NODEARG();
@@ -488,7 +641,7 @@ static naRef f_getNode(naContext c, naRef me, int argc, naRef* args)
     if(!naIsString(path)) return naNil();
     SGPropertyNode* n;
     try {
-        n = (*node)->getNode(naStr_data(path), create);
+        n = node->getNode(naStr_data(path), create);
     } catch (const string& err) {
         naRuntimeError(c, (char *)err.c_str());
         return naNil();
@@ -496,45 +649,136 @@ static naRef f_getNode(naContext c, naRef me, int argc, naRef* args)
     return propNodeGhostCreate(c, n);
 }
 
+
+// Create a new property node.
+// Forms:
+//    props.Node.new();
 static naRef f_new(naContext c, naRef me, int argc, naRef* args)
 {
     return propNodeGhostCreate(c, new SGPropertyNode());
 }
 
+
+// Get the global root node (cached by props.nas so that it does
+// not require a function call).
+// Forms:
+//    props._globals()
+//    props.globals
 static naRef f_globals(naContext c, naRef me, int argc, naRef* args)
 {
-	#if 0 //FIXME
+    return naNil();
+    #if 0
     return propNodeGhostCreate(c, globals->get_props());
-	#endif
+    #endif
+}
+
+#if 0
+##
+# Private function to do the work of setValues().
+# The first argument is a child name, the second a nasal scalar,
+# vector, or hash.
+#
+Node._setChildren = func(name, val) {
+    # print("setChildren call for:",name);
+    var subnode = me.getNode(name, 1);
+    if(typeof(val) == "scalar") { subnode.setValue(val); }
+    elsif(typeof(val) == "hash") { subnode.setValues(val); }
+    elsif(typeof(val) == "vector") {
+        for(var i=0; i<size(val); i+=1) {
+            var iname = name ~ "[" ~ i ~ "]";
+            me._setChildren(iname, val[i]);
+        }
+    }
+}
+
+
+#endif 
+static naRef f_setChildren(naContext c, naRef me, naRef name, naRef value)
+{
+#if 0
+    // subnode = me.getNode(name, 1);
+    for (int i=0;i<naVec_size(keysVector);i++) {
+    // naHash_get(args[1], naRef key, naRef* out);
+    if(naIsScalar(value) || naIsHash(value) ) {
+	// subnode.setValue
+    } // scalar or hash
+    else if(naIsVector(value)) {
+	for(int v=0;v<=naVec_size(value);v++) {
+	//f_setChildren(v, me, naVec_get(c,value,v));
+	}
+    }
+    } // for each key 
+#endif
+    return naNil(); 
+} // setChildren()
+
+
+#if 0
+Node.setValues = func(val) {
+    foreach(var k; keys(val)) { me._setChildren(k, val[k]); }
+}
+#endif 
+
+// http://wiki.flightgear.org/Howto:Extend_Nasal
+// TODO: use try/catch naRuntimeError, SG_LOG
+static naRef f_setValues(naContext c, naRef me, int argc, naRef* args)
+{
+#if 0
+    NODEARG();
+
+    naRef hash = naVec_get(argv, 0);
+    // allocate a new vector to hold the keys in the hash
+    naRef keys = naNewVector(c);
+    // copy all keys into the vector
+    naHash_keys(keys, hash);
+    naRef key, value;
+    
+    SG_LOG(SG_NASAL, SG_ALERT, "keys in hash " << naVec_size(keys) );
+
+    for (int i=0;i<naVec_size(keys);i++) {
+    key = naVec_get(keys,i);
+    naHash_get(hash, key, &value);
+    
+    SG_LOG(SG_NASAL, SG_ALERT, "key/value:" << naStr_data(key) <<"/"<<naStr_data(value) );
+    }
+
+    f_setChildren(c, me, hash, keys);
+#endif
+    return naNil();
 }
 
+
+
 static struct {
     naCFunction func;
     const char* name;
 } propfuncs[] = {
-    { f_getType, "_getType" },
-    { f_getAttribute, "_getAttribute" },
-    { f_setAttribute, "_setAttribute" },
-    { f_getName, "_getName" },
-    { f_getIndex, "_getIndex" },
-    { f_getValue, "_getValue" },
-    { f_setValue, "_setValue" },
-    { f_setIntValue, "_setIntValue" },
-    { f_setBoolValue, "_setBoolValue" },
-    { f_setDoubleValue, "_setDoubleValue" },
-    { f_getParent, "_getParent" },
-    { f_getChild, "_getChild" },
-    { f_getChildren, "_getChildren" },
-    { f_addChild, "_addChild" },
-    { f_addChildren, "_addChildren" },
-    { f_removeChild, "_removeChild" },
-    { f_removeChildren, "_removeChildren" },
-    { f_alias, "_alias" },
-    { f_unalias, "_unalias" },
-    { f_getAliasTarget, "_getAliasTarget" },
-    { f_getNode, "_getNode" },
-    { f_new, "_new" },
-    { f_globals, "_globals" },
+    { f_getType,            "_getType"            },
+    { f_getAttribute,       "_getAttribute"       },
+    { f_setAttribute,       "_setAttribute"       },
+    { f_getName,            "_getName"            },
+    { f_getIndex,           "_getIndex"           },
+    { f_equals,             "_equals"             },
+    { f_getValue,           "_getValue"           },
+    { f_setValue,           "_setValue"           },
+    { f_setIntValue,        "_setIntValue"        },
+    { f_setBoolValue,       "_setBoolValue"       },
+    { f_setDoubleValue,     "_setDoubleValue"     },
+    { f_getParent,          "_getParent"          },
+    { f_getChild,           "_getChild"           },
+    { f_getChildren,        "_getChildren"        },
+    { f_addChild,           "_addChild"           },
+    { f_addChildren,        "_addChildren"        },
+    { f_removeChild,        "_removeChild"        },
+    { f_removeChildren,     "_removeChildren"     },
+    { f_removeAllChildren,  "_removeAllChildren"  },
+    { f_alias,              "_alias"              },
+    { f_unalias,            "_unalias"            },
+    { f_getAliasTarget,     "_getAliasTarget"     },
+    { f_getNode,            "_getNode"            },
+    { f_new,                "_new"                },
+    { f_globals,            "_globals"            },
+    { f_setValues,            "_setValues"        },
     { 0, 0 }
 };

Status (01/2013)

Project stopped for a while. Use OpenRadar or ATC-pie instead.

The project may be continued in the future by integrating its building blocks into fgfs mainline, using the new Radio Propagation framework:

Cquote1.png I've seen a couple of external radar clients for Flightgear being developed

right now. I think that these days, with the advent of Canvas and other improvements, it should be possible and desirable to have a realistic radar station inside Flightgear.

At the moment, I'm only concerned about radar ranges and interference of terrain obstructions, weather, ground clutter. For this purpose, I propose a template implementation of radar transceivers and transponders: It is not possible to provide a detailed terrain picture on every antenna rotation. Sampling all terrrain 360 degrees around the station would cripple performance. Thus, I would just take all positions of aircraft inside the nominal range and perform radio calculations on them, using larger sampling distances and simpler routines for aircraft above a treshhold flightlevel. If terrain around the antenna is obstructing the signal, or weather affects it, we can simulate that correctly.

Transponder responses are also a candidate for the new radio code. Like in reality, it is possible to have radar contact but lose transponder id because

of radio issues.[2]
— Adrian Musceac
Cquote2.png


  1. Hooray  (Nov 13th, 2016).  Re: FGTraffic 2020: Road map for a new AI traffic system des .
  2. Adrian Musceac (Tue, 27 Nov 2012 05:03:46 -0800). Implementing realistic radar inside the Flightgear engine.

Description

FGRadar is a free, open-source, multi-platform and stand-alone air traffic control (ATC) client for FlightGear currently under development (10/2012). It makes use of the SimGear library and the Nasal scripting language.

On the scripting side, it uses the GTK+ and Cairo wrappers for Nasal in order to perform GUI rendering and drawing operations, respectively.

Under development as of October 2012 (no public release yet):

FGRadar builds and starts up, running an SGSubsystemMgr-based main loop, the property tree and a fully working Nasal subsystem that is able to run Nasal scripts and extension functions - including the added gtk/cairo bindings.

In other words, FGRadar can already be easily augmented or implemented in scripting space using Nasal, including GTK-based GUI rendering and CAIRO-based 2D drawing. FGRadar also connects to mpserver01.flightgear.org:5001 and dumps a list of active clients to the console, at a configurable update rate.

Changes coming soon

  • Networking Support via Nasal-space SGSocket wrappers

Planned Features

  • multiplayer support by sending/recieving packages from the MP servers
  • reading shapefiles in order to create terrain maps (OpenRadar style)
  • FGCom protocol support
  • Flight Planning support (might include support in fgms too)
  • Nasal command line to execute extension functions easily without scripts

Roadmap

Milestone 1 Done Done 17.10.2012

  • Establish a multi-platform code base using CMake as the build system Done Done
  • Establish an SGSubsystem-based architecture Done Done
  • Implement Nasal Scripting support Done Done

Milestone 2 Done Done 19.10.2012

  • Implement an SGApplication framework for CLI/GLUT Done Done
  • Add a networking layer (subsystem) to get a list of fgms clients (aircraft) (ETA: 1 coding day) Done Done
  • Add a --data= parameter to specify the location of the data folder (Nasal scripts etc) Done Done

Milestone 3 Done Done 20.10.2012

  • fix the Nasal timer implementation after merge into master Done Done
  • use the --data= parameter in master Done Done
  • SGApplication: make program name configurable Done Done
  • SGApplication: make --data directory parameter configurable Done Done
  • SGApplication: make version file name configurable Done Done
  • SGApplication: add checkVersion() method Done Done


Milestone 4

  • finish the merge of the scripting branch into master Done Done
  • finish the SGApplication template and move stuff from FGRadarApplication/FGPanel Done Done
  • re-add the original FGMS/Telnet code from FGRadar for testing purposes Pending Pending

Milestone x

  • Parse the fgms response and convert it into internal data structures (callsign, lat,lon,alt etc) Pending Pending
  • Copy the fgms response to the property tree, for use by Nasal Pending Pending
  • Add FlightGear's navaid DB subsystems, so that airports, navaids, fixes etc can be accessed Pending Pending (ETA: 2 coding days)
  • Add scripting hooks (NasalPositioned) Pending Pending (ETA: 1 coding days)

Milestone x

  • Add an SG_OSGApplication framework for OSG-viewer based apps
  • Add support for the Canvas subsystem (once it's been moved to SimGear) (ETA: 1 coding week)

Contributing

Contributors are welcome to join the project. C++ Developers should ideally have some experience with:

  • C++
  • GIT
  • CMAKE
  • MULTI-PLATFORM development

In addition, a little familarity with the SimGear/FlightGear code bases is a definite plus, because FGRadar re-uses some code from those.

Note: As of 11/2012, it will also be possible for non-C++ developers to contribute to the project via Nasal scripting, because FGRadar re-uses FlightGear's scripting subsystem. So anybody familiar with Nasal scripting in FlightGear, should feel quite at home in FGRadar. In addition to many of the extension functions found in FlightGear (such as setprop/getprop etc), FGRadar also adds GTK/CAIRO bindings for powerful GUI/2D drawing support.

If someone wants to join the project, give ideas, requests or opinions, feel free to do so. The archived repository is at: https://gitorious.org/fgradar/fgradar?p=fgradar:fgradar.git;a=summary

For an alternative github clone visit fgradar-clone

Design

FGRadar uses a framework-centric design where the main architecture is based on a conventional SGSubsystemMgr loop that passes control to the Nasal scripting system early on.

  • Written in C++
  • Uses Doxygen for developer documentation
  • Multi-Platform support
  • Uses cmake as the build system
  • Uses Nasal based unit tests
  • favors the use of smart pointers for memory management to reduce the danger of memory leaks
  • uses the SimGear library
  • uses GTK 2.0
  • uses CAIRO
  • scripting support via Nasal
  • Implements an SGSubsystem-based main loop, to re-use existing SG/FG code
  • Uses the FlightGear/SimGear Property Tree as its internal main data structure
  • Uses the FlightGear/FGMS networking stack (based on SGSocket)

Changelog / Completed

  • cmake support Done Done
  • main loop cleanup to prepare scripting support Done Done
  • the CMAKE build system should probably be re-structured such that each source directory gets its own CMakeList? Done Done
  • implement an SGSubsystem-based main loop Done Done
  • absorb useful stuff (gtk/cairo bindings) from "Nasal standalone" into scripting module, and use SimGear's Nasal Done Done
  • implement an SGApplication framework for SGSubsystem-based CLI/GUI apps Done Done
  • start using the property-tree Done Done
  • start using the SimGear-based logging facilities (SG_LOG) Done Done
  • add a new SGSubsystem that connects to fgms and gets a list of aircraft, i.e. via http://mpserver01.flightgear.org:5001/ and SimGear's HTTPRequest Done Done
  • add a "data" folder for scripts etc (fg-root), needs to be installed via CMake Done Done
  • use simgear::ResourceManager::instance()->addBasePath(fg_root, simgear::ResourceManager::PRIORITY_DEFAULT) for data folder access Done Done
  • merge Nasal progress from initial-scripting-support branch into master Done Done


TODO (Nobody working on any of these)

  • We need to recieve chat messages too, not provided by fgms telnet.
  • refactor and generalize the scripting implementation and move it to SGScriptableApplication ?
  • apt.dat version 850 parsing (currently only 810 is supported)
  • more map projections (only the Mercator projection is available right now)
  • Improve the GTK bindings such that they can be used in multi-threaded apps, which boils down to wrapping certain gtk functions in between gdk_threads_enter() and gdk_threads_leave() calls - also glib's threads_init() will need to be called in the gtk init() function in gtklib.c [1] [2]
  • document the available gtk/cairo bindings so that contributors can get started more easily
  • support the FG performance monitor
  • add a dedicated memory management subsystem that tracks RAM usage per subsystem
  • support dynamic subsystem initialization (see flightgear/flightgear/8608a4 commit view )

Known Issues

Regressions while merging the scripting branch

  • fix the realtime property so that the events mgr can use it
  • data folder support using the simgear::resourceManager (used by Nasal) Done Done
  • global property tree handle Done Done
  • use FGPanel's sleep() implementation to fix the busy loop Done Done
  • command line parsing (--prop: not yet supported!) Done Done

Coding

  • Determine if we need to find a better solution for GTK's blocking main loop than jusing doing gtk_main_iteration_do() (threading? )Pending Pending

Build System

  • PKGLIBDIR is not being set properly via cmake yet (see FGRadarApplication.cxx)
  • The NasalSys.cxx file uses gettimeofday(), which may not be available on Windows - we need to add CMake magic to detect availability of it (already done by FG!) Pending Pending
  • The build system has only been tested on Linux so far

Data

  • we should really be using import() from driver.nas

Work in Progress

  • add FlightGear's NavDB subsystem to FGRadar (Icecode) Pending Pending
  • expose SimGear's SGSocket to Nasal via a separate Nasal ghost, so that Nasal can use sockets (Hooray) 10}% completed
  • improved cross-platform support, so that FGRadar also works on Windows 50}% completed
  • Nasal scripting support (Hooray) 80}% completed
  • re-add and port submodule support for scripting, so that multiple files can be loaded into the same namespace (Hooray) Pending Pending
  • add a SG_OSGApplication inherited from SGApplication which provides an osgviewer-based window (see $FG_SRC/utils/fgviewer/fgviewer.cxx) to support Canvas 20}% completed
  • improved Nasal GTK wrapper (port from GTK 2.0 to 3.x) Pending Pending
  • An XML configurable GUI using Gtk (glade) Pending Pending

SGApplication

  • enforce use of smart pointers Done Done
  • use real singletons to manage resource access
  • start working on SGScriptableApplication
  • support environment variables
  • start working on config file support (~/.fgfsrc)
  • fix all compiler warnings
  • add DoxyGen comments

Canvas Integration

We may also want to take a look at osggtk and http://www.openscenegraph.org/projects/osg/browser/OpenSceneGraph/trunk/examples/osgviewerGTK .

The Canvas subsystem will need something:

  • to attach its cameras to (osg::Group).
  • also, there has to be a property tree which will be used to control the canvas and also by the canvas itself to parse informations back.
  • For user interaction, and displaying windows there will be needed an osgViewer and a GUI camera (camera with orthogonal projection whose output goes to the application window/screen.
  • For initializing the Nasal support it would be good to have support for the same kind of initialization as it's currently used in the fg/Scripting files.
  • the map layers (airports, towers, runways, taxiways etc) need full navdb access, i.e. the NasalPositioned bindings


Moving the Canvas to SG should be finished for now. Basically we need something like the canvas_mgr (Only the constructor and something like GUIMgr::addPlacement is needed) and FGCanvasSystemAdapter. Everything else should now be part of SimGear.

The Scripting System

The scripting system in FGRadar is largely based on FlightGear's FGNasalSys implementation, unneeded methods and extension functions are removed however - this affects mostly the scenery related features, various loading hooks (models, XML dialogs) and a handful of FlightGear-specific extension functions that depend on FlightGear systems that are not available in FGRadar (i.e. geodinfo, which requires the tile manager).

However, most general-purpose functions are also available in FGRadar, such as for example:

  • print()
  • rand(), srand(), abort()
  • cmdarg()
  • systime()
  • setprop()/getprop()
  • setlistener()/removelistener()

In the future, we'll probably also support the NasalPositioned extension functions from $FG_SRC/Scripting to also access navdb related information from Nasal within FGRadar. In addition, all core-language features and library functions of the Nasal language itself are obviously also supported in FGRadar (i.e. sort(), append(), contains() etc).

As of 10/2012, the Nasal scripts are loaded from a directory in $FG_RADAR/data/Nasal. The folder contains two sub directories:

  • lib - for libraries, i.e. code that doesn't run
  • app - for scripts that implement applications

Each script in any of these two folders is loaded into a namespace based on the file name - which is identical to FlightGear. However, the lib folder is processed prior to the app folder obviously.

In addition to these two folders, there's a third folder named tests this includes Nasal-based unit tests that are executed after loading the lib directory, and prior to running the app directory.

Note that support for Nasal sub modules (where all Nasal files in a single folder are loaded into the same namespace) hasn't yet been implemented, but will soon be added to make it easier to support the Canvas scripting layer. That will probably mean another folder in data/Nasal (loaded prior to app, but after lib), namely:

  • modules

Related content