A GPX flight logger in Nasal
The FlightGear forum has a subforum related to: Nasal Scripting |
Nasal scripting |
---|
Nasal internals |
---|
Memory Management (GC) |
GPX is a XML format used by GPS devices to save tracks and other data for exchange with map viewers and other applications. There are web sites where its possible to view and share your tracks. This tutorial describes how to:
- Write a Nasal script that logs your flight track as a GPX file
- Make a dialog accessible from the menu to set logging options
- Bind a keyboard key to start and end the logging.
The Script
The GPX file we want to create looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.0" creator="Flightgear">
<trk>
<name>Flight</name>
<trkseg>
<trkpt lat="59.90739806491311" lon="17.60183847242109">
<ele>26.1556207079425</ele>
<time>2013-02-06T13:08:10Z</time>
</trkpt>
<trkpt lat="59.90739806491307" lon="17.60183847242183">
<ele>26.15562070510105</ele>
<time>2013-02-06T13:08:18Z</time>
</trkpt>
.... More trackpoints ....
</trkseg>
</trk>
</gpx>
The track has a name (Flight) and consists of trackpoints with lat, long, elevation and time.
We want to be able to choose file name, track name and time between new trackpoints in a dialog. We want to start and stop the logging with a keyboard key. For his we will create and use properties. For safety reasons Nasal can only write to files in a few directories. We will use the directory $FG_HOMEExport/ to write our GPX-files.
The script consists of three functions:
- start_track: This reads properties with the logging options, if no options are set it uses standard settings. The function also writes the GPX tags in the beginning of the GPX-file. Last it calls the function write_track.
- write_track: This writes a new trackpoint to the GPX-file. It then checks if the logging still runs and if so calls itself through the settimer function with the delay chosen by the user. If not running it calls end_track.
- end_track: Writes the closing GPX tags and closes the file.
As we want the script to be loaded as we start Flightgear we save it as gpx.nas in $FG_ROOT/Nasal (See note).
The Nasal code:
# GPX track writer
var start_track = func {
print("Start tracking");
var file = getprop("/logging/gpx/outputfile") or "track.gpx";
var trackname = getprop("/logging/gpx/trackname") or "Track";
var dt = num(getprop("/logging/gpx/dt")) or 5;
var fh = io.open(getprop("/sim/fg-home")~"/Export/"~file~".gpx", "w");
setprop("/logging/gpx/running", 1);
io.write(fh, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n");
io.write(fh, "<gpx version=\"1.0\" creator=\"Flightgear\">\n");
io.write(fh, " <trk>\n <name>"~trackname~"</name>\n <trkseg>\n");
write_track(fh, dt);
}
var write_track = func(fh, dt) {
var lat = getprop("/position/latitude-deg") or 0;
var lon = getprop("/position/longitude-deg") or 0;
var elv = getprop("/position/altitude-ft")*0.305 or 0;
var t = getprop("/sim/time/gmt") or 0;
io.write(fh, " <trkpt lat=\""~lat~"\" lon=\""~lon~"\">\n");
io.write(fh," <ele>"~elv~"</ele>\n");
io.write(fh," <time>"~t~"Z</time>\n </trkpt>\n");
print("Added trackpoint");
if (getprop("/logging/gpx/running")) settimer(func { write_track (fh, dt); }, dt);
else end_track(fh);
}
var end_track = func(fh) {
io.write(fh, " </trkseg>\n </trk>\n");
io.write(fh, "</gpx>\n");
io.close(fh);
print("Ended tracking");
}
Rather than creating the markup explicitly, the XML helpers in $FG_ROOT/Nasal/io.nas could also be used, such as io.write_properties(). There's also a lower-level helper in the form of io.writexml() available, which is still more abstract than doing the whole thing manually. To be able to simply use io.write_properties(), you'll also need to use some helpers from $FG_ROOT/Nasal/props.nas to set the gpx-specific attributes that are otherwise not used by the PropertyList XML File format.
Dialog
To be able to set output file, track name and time between the trackpoints from the simulator we create a dialog. Flightgear dialogs are written in XML. The dialogs are located in $FG_ROOT/gui/dialogs.
To communicate the logging options between the dialog and the Nasal script. We let the dialog create properties under /logging/gpx for outputfile, trackname and dt (time between trackpoints). These are the one read by the script at start-up.
The following code creates the dialog. We save it in $FG_ROOT/gui/dialogs as gpx_settings.xml
<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<name>gpxtrack</name>
<width>300</width>
<height>240</height>
<modal>false</modal>
<layout>vbox</layout>
<resizable>false</resizable>
<default-padding>3</default-padding>
<group>
<layout>hbox</layout>
<default-padding>1</default-padding>
<empty>
<stretch>true</stretch>
</empty>
<text>
<label>Settings for GPX output</label>
</text>
<empty>
<stretch>true</stretch>
</empty>
</group>
<group>
<layout>hbox</layout>
<text>
<halign>left</halign>
<label>File name*:</label>
</text>
<input>
<halign>right</halign>
<width>300</width>
<height>25</height>
<property>/logging/gpx/outputfile</property>>
</input>
</group>
<group>
<layout>hbox</layout>
<text>
<halign>left</halign>
<label>Track name:</label>
</text>
<input>
<halign>right</halign>
<width>300</width>
<height>25</height>
<property>/logging/gpx/trackname</property>
</input>
</group>
<group>
<layout>hbox</layout>
<text>
<halign>left</halign>
<label>Time between waypoints (s):</label>
</text>
<input>
<halign>right</halign>
<width>100</width>
<height>25</height>
<property>/logging/gpx/dt</property>
</input>
</group>
<group>
<layout>vbox</layout>
<text>
<halign>left</halign>
<label>*Files written to [[$FG_HOME]]Export, .gpx added.</label>
</text>
</group>
<group>
<layout>hbox</layout>
<halign>fill</halign>
<empty><stretch>true</stretch></empty>
<button>
<legend>Cancel</legend>
<equal>true</equal>
<default>true</default>
<key>Esc</key>
<binding>
<command>dialog-close</command>
</binding>
</button>
<button>
<legend>OK</legend>
<equal>true</equal>
<default>false</default>
<binding>
<command>dialog-apply</command>
</binding>
<binding>
<command>dialog-close</command>
</binding>
</button>
</group>
</PropertyList>
Menu entry
Now we must be able to show the dialog. We do that by inserting a new item in the simulator menu under the heading Equipment. The menu is controlled by the file menubar.xml found in $FG_ROOT/gui. We add a new item, GPX Settings, under the GPS item. Like this:
....
<menu>
<name>equipment</name>
....
<item>
<name>gps</name>
<binding>
<command>dialog-show</command>
<dialog-name>gps</dialog-name>
</binding>
</item>
<!-- Item to set GPX output -->
<item>
<label>GPX Settings</label>
<name>gpx_settings</name>
<binding>
<command>dialog-show</command>
<dialog-name>gpx_settings</dialog-name>
</binding>
</item>
....
</menu>
....
Note that menu items can also be procedurally added at runtime, see [1].
Now we are almost done. We just want to be able to start and stop the logging while flying.
The keybinding
To easily start and stop the logging we bind this function to a keyboard key. Most keys are already bound to different functions but the r-key is free and might be remembered as standing for "Record". The keyboard bindings are found in $FG_ROOT/keyboard.xml (See note). In this file we insert the code to bind 'r' (ASCII character no: 114) to start/stop the logging.
If the logging is running it should signal it to stop and if not running it should start it. The code:
<key n="114">
<name>r</name>
<desc>GPSrecorder to gpx-file</desc>
<binding>
<command>nasal</command>
<script>
if (getprop("/logging/gpx/running")) setprop("/logging/gpx/running", 0);
else gpx.start_track();
</script>
</binding>
</key>
Now everything is ready to test.
Note: Flightgear can also load Nasal files and keyboard.xml from the $FG_HOME directory but not dialogs, you can however create the whole dialog procedurally from Nasal, for examples check the multiplayer.nas file in $FG_ROOT/Nasal and monitor.nas in $FG_ROOT/Nasal/performance_monitor